集合

Vector 和 ArrayList的比较:
    vector是最早版本的动态数组,线程安全,不够扩容为原来的2倍,初始容量为10
    支持遍历的集合方式有:(1)foreach (2)Iterator (3)支持旧版的Enumeration迭代器
    
    ArrayList相对于vector新一点,线程不安全的,不够扩容为原来的1.5倍,初始容量为10
    支持遍历的集合方式有:(1)foreach (2)Iterator
    
    
    2倍造成空间浪费的可能性比较大
    1.5倍造成扩容次数增大
    为了比较空间浪费,和扩容次数太多,如果能够预估大概的元素个数,那么可以用ArrayList(int initialCapacity)和Vector(int initialCapacity)直接初始化为一定容量的数组
    
    
Stack:栈
    先进先出(FILO)或后进先出
    Stack是Vector的子类,比Vector多了几个方法,它的后进先出的特征,就是通过调用这几个方法实现的
    
LinkedList:双向链表
    内部有一个节点类型:
    class Node{
        Object data;
        Node previous;
        Node next;
    }
    class LinkedList{
        Node first;
        Node last;
    }
    
    LinkedList可以被当作双向链表、栈、队列、双端队列等数据结构使用
    
Vector源码:
    (1)、Vector v = new Vector();    //无参构造,初始化长度为10的数组,默认增量是0
    
    public Vector() {
        this(10);
    }
    
    
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
    
    (2)、v.add("haha")        
            // 默认扩容为原来的2倍,如果手动指定了capacityIncrement的值,那么可以按照你指定的增量进行扩容
    
    (3)、v.add(index,e)        
            // 1 考虑扩容
                2 移动元素
                3 添加元素
                4 元素个数增加
    (4)、v.remove(index)    
            // 1 计算要移动的个数
               2 如果需要移动,调用System.arraycopy方法进行移动
               3 将最后一位设置为Null
    
    (5)、v.remove(Object obj)    
            // 1 先获取要删除元素的下标  indexOf(Objec obj)
                2 再调用v.remove(index)方法
    
    
    
ArrayList源码:
    (1) ArrayList list = new ArrayList();
            JDK1.8版本:发现内部初始化为一个长度为0的空数组
            JDK1.7版本:也是初始化为长度为0的空数组
            JDK1.6版本:初始化为长度为10的数组
    
    为什么要初始化为空数组呢?
    因为开发中,很多时候创建了ArrayList的对象,但是没有装元素,但是这个时候如果初始化为10就浪费空间了
    
    (2) list.add(Object e)
            JDK1.8 第一次添加元素,扩容为长度为10的数组
            JDK1.8 如果不够了,再扩容为1.5倍
            
Stack源码:
    (1) Stack stack = new Stack();
    (1) stack.push()
            等价于Vector的add方法,因为他们是父子类关系
    
    (2) stack.peek()
            返回size-1位置的元素
            
    (3) stack.pop()
            先peek()返回栈顶元素
            删除size-1位置的元素
    
LinkedList源码:
    (1) LinkedList linkL = new LinkedList()
            啥也没干
            
    (2) linkL.add(Object e)
            
            public boolean add(E e) {
                linkLast(e);
                return true;
            }
            
            void linkLast(E e) {
                final Node<E> l = last;
                // 新节点的pre是刚刚的最后一个节点
                // 新节点的下一个节点是null
                final Node<E> newNode = new Node<>(l, e, null);
                // 新节点称为了最后一个节点
                last = newNode;
                // 如果之前的最后一个节点是null,表示刚才链表是空的,此时新节点也是第一个节点
                if (l == null)
                    first = newNode;
                else
                // 如果刚才链表不是空的,原来的最后一个节点的next指向新节点
                    l.next = newNode;
                // 元素个数加一
                size++;
                modCount++;
            }
    
Set源码:HashSet  TreeSet  LinkedHashSet
    (1) Set系列集合元素不能重复
    (2) LinkedHashSet可以保证元素的添加顺序
    (3) TreeSet可以保证元素的大小顺序
    
    (4) 添加到TreeSet必须的对象必须实现java.langComparable接口
    
    (5) 如何保证元素不可重复呢?
        换句话说,如何判断两个元素是重复的呢?
        HashSet和LinkedHashSet:
            (1) 先比较hash值,如果hash值不一样,说命一定不相同
            (2) 如果hash值一样,再调用equals方法比较
    
        TreeSet:
            按照元素的大小来决定是否是相同元素
    
    
    
Map

    HashMap
    HashTable
    TreeMap
    LinkedHashMap
    Properties

    1、HashMap和Hashtable (哈希表)的区别
        Hashtable:旧版,线程安全的,它的key和value不能为null
        HashMap:相对Hashtable来说新一点,线程不安全的,它允许key和value为null
    
    2、LinkedHashMap 和 HashMap 的区别
        LinkedHashMap是HashMap的子类,多维护了映射关系的添加顺序
    
    3、HashMap和TreeMap的区别
        HashMap:无序的
        TreeMap:按照key排大小顺序
    
    4、Properties是Hashtable的子类,不允许key和value是null,并且key和value都是String类型
    
    5、所有的Map的key不能重复,如何实现不重复?
        HashMap、Hashtable、LinkedHashMap、Properties:依据key的hashCode和equals方法
        TreeMap:一句key的大小,认为大小相等的两个key就是重复的
    
    6、Set的底层实现是什么?
        HashSet         :     HashMap
        TreeSet         :     TreeMap
        LinkedHashSet :     LinkedHashMap
        
        Set添加元素是用add(key),而Map添加元素用put(key,value)
        发现add底层实现是调用的put,只是add底层调用put时给了同一个对象作为value
    
    7、Map的底层实现是什么:
        (1)、哈希表系列:
                数组 + 链表
                数组 + 链表/红黑树
        (2)、TreeMap:红黑树
        
        HashMap的底层实现:
            JDK1.7以及之前:数组 + 链表
            JDK1.8以及之后:数组 + 链表/红黑树
            
            HashMap根据key的hashCode--->公式/算法--->[index]
                因为不同的hashCode值,可能得到的[index]是相同的,那么冲突来了,那么只能把[index]的多个映射关系用链表链接起来
            
        二叉树特点:查找速度比链表快
            旧版的HashMap,如果key的hashCode算出了[index]相同的话,都在一个table[index]下面,
            如果严重的话,会导致[index]下面的链表很长,就会导致查询速度减慢,当链表长到一定程度时,就需要把链表变为二叉树,以提高我们查找速度

HashMap JDK1.7 : 数组 + 链表

    (1)、HashMap hs = new HashMap()

    public HashMap(){
    // 调用自身构造方法
        this(DFAULT_INITIAL_CAPACITY(16),DEFAULTY_LOAD_FACTOR(0.75));
    }
    
    this(int initialCapacity, float loadFactor){
        // 次构造方法中没看到声明一个有长度的容量,因为其默认是空容量的
        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }
    
    new HashMap()
    table数组初始化为了一个长度为0的空数组
    DFAULT_INITIAL_CAPACITY:16   默认容量
    DEFAULTY_LOAD_FACTOR:0.75    默认加载因子
    threshold: 阈值/临界值  数组需要考虑扩容的阈值
        threshold = capacity(容量,数组的长度)*load_factor(加载因子,默认是0.75) = 12
        最开始初始化时 threshold = initialCapacity=16
        例如:threshold = 16*0.75 当数组大概3/4满的时候就考虑扩容
    思考:load factor设置为0.9和0.1有什么区别?
        0.1:扩容太频繁
        0.9:会导致table[index]下面的链表会很长,查询速度就低
     
    (2)、初始容量为0,什么时候扩容为16?
        HashMap会首先初始化为一个0的容量,在第一次put时,threshold还不是12(没有使用 threshold = capacity(容量,数组的长度)*load_factor(加载因子,默认是0.75),使用的是16,
        过程为:先判断表是否为空表,如果是空表,就使用threshold=initialCapacity=16,随后会立刻修改threshold的值为两者相乘,即12)
        也就是说最开始初始化为0,后来第一次添加put元素时变成16的容量,并把阈值修改为12
    
        如果不是空数组:或者说第二次以后添加put元素 数
    
    
    
                    public V put(K key, V value) {
                        if (table == EMPTY_TABLE) {        // 判断table数组是否为空
                            inflateTable(threshold);    // 刚刚初始化后 threshold = 16
                        }
                        
                                        public void inflateTable(int toSize){
                                            int capacity = roundUpToPowerOf2(toSize);
                                            // 此时threshold变为 16 * 0.75 = 12
                                            threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
                                            // 此时才开始初始化一个容量为 16 的table
                                            table = new Entry[capacity];
                                        }
                        
                        
                        if (key == null)
                            return putForNullKey(value);
                            
                            
                        int hash = hash(key);        //根据key的hashCode用异或,无符号右移等各种运算,得到一个int类型的hash值
                        因为我们下面要用hash值来算[index],他的设计者认为用户重写的hashCode可能不够散列
                        
                        
                        int i = indexFor(hash, table.length);  //  hash & table.length-1 找到要插入table的下标
                        /*
                        table数组的长度一定是2的n次方,
                        table.length-1 的二进制前面都是0,后面都是连续的1
                        hash & table.length-1 做按位与运算的结果一定是在[0,table.length-1]范围内
                        */
                        
                        
                        // 判断table[i]下面的链表中是否有映射关系的key是和我重复的,如果有就替换成新的value
                        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                            Object k;
                            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                                V oldValue = e.value;
                                e.value = value;
                                e.recordAccess(this);
                                return oldValue;
                            }
                        }

                        modCount++;
                        addEntry(hash, key, value, i);
                        return null;
                    }
                    
                    
                    void addEntry(int hash, K key, V value, int bucketIndex) {
                        // 当元素的总个数达到阈值 && 并新的映射关系要添加的table[index]下面不是空的
                        if ((size >= threshold) && (null != table[bucketIndex])) {
                            resize(2 * table.length);    // 数组扩容为原来的两倍
                            hash = (null != key) ? hash(key) : 0;    // 重新计算hash
                            bucketIndex = indexFor(hash, table.length);    // index也重新计算
                        }

                        createEntry(hash, key, value, bucketIndex);
                    }
                    
                    void resize(int newCapacity){
                        Entry[] oldTable = table;                
                        int oldCapacity = oldTable.length;
                        if(oldCapacity == MAXIMUM_CAPACITY){
                            threshold = Integer.MAX_VALUE;
                            return;
                        }
                        
                        Entry[] newTable = new Entry[newCapacity];                // 开辟一个两倍于之前大小的数组
                        transfer(newTable,initHashSeedAsNeeded(newCapacity));    // 将之前数组的元素移动位置,因为要重新计算hash和index
                        table = newTable;
                        threshold = (int)Math.min(newCapacity * loadFactor,MAXIMUM_CAPACITY + 1);    // threshold 跟进改变
                    }
                    
                    
                    void createEntry(int hash, K key, V value, int bucketIndex) {
                        Entry<K,V> e = table[bucketIndex];
                        table[bucketIndex] = new Entry<>(hash, key, value, e);
                        // 把table[index]下面原来的Entry连接到新的Entry的next中
                        size++;
                    }
    
            a、发现数组table是空数组后,会把数组初始化为长度为16的Entry类型的数组,并且把threshold计算为12
        这里如果手动指定了数组的capacity,那么如果这个capacity不是2的n次方,会自动纠正为2的n次方
        
            为什么要纠正为2的n次方?
        后面算index = hash & table.length-1,这样才能保证[0,table.length-1]范围内
        2的n次方,根据它的散列算法,可以保证比较均匀的分散在他的数组的各个位置

            b、 hash = hash(key)
        为了干扰我们key的hashCode值
        
            c、index = hash & table.length-1
            d、先判断table[index]下面是否有映射关系的key是和我新添加的映射关系的key有重复的,如果有,就用新的value替换旧的value,就结束了
            e、如果没有重复的,决定添加新的映射关系
                (e1)、看是否需要扩容
                扩容条件:A:size达到阈值threshold B:table[index]下面已经有映射关系,即不为空
                如果扩容了,会重新计算hash和index
                
                (e2)把新的映射关系new为一个Entry的对象,放到table[index]中,原来table[index]的映射关系作为新的映射关系的next连接起来
            
                Entry相当于一个节点类型,是一个单向链表的节点类型
                class Entry{
                    int hash;
                    Object key;
                    Object value;
                    Entry next;
                }
    
HashMap JDK1.8 : 数组 + 链表/红黑树
    (1)、DEFAULT_INITIAL_CAPACITY = 16    默认初始化容量16
    (2)、MAXIMUM_CAPACITY = 2的30次方     最大容量
    (3)、DEFAULT_LOAD_FACTOR = 0.75       默认加载因子
    (4)、TREEIFY_THRESHOLD = 8              默认树化阈值为8,当链表长度达到这个值时,就考虑是否要树化
    (5)、UNTREEIFY_THRESHOLD = 6          默认反树化阈值为6,当链表长度小于这个值时就考虑将树转换为链表
    (6)、MIN_TREEIFY_CAPACITY = 64          最小树化容量64
                                            当单个链表的节点个数达到8,并且table的长度达到64,才会树化
                                            当单个链表的节点个数达到8,但是table的长度未达到64,会先扩容
    (7)、Node<K,V>[] table:            数组
    (8)、size:                            记录有效映射关系的对数,也是Entry对象的个数
    (9)、threshold:                    阈值,当size达到阈值时,考虑扩容
    (10)、loadFactor:                    加载因子,影响扩容的频率
    
    
    源码:
    1、HashMap hashMap = new HashMap();
    
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
        // 初始化只赋值了加载因子,其他都是默认值
        // threshold = 0
        // table = null
        // size = 0 
    }
    
    2、put(key,value)
    
    (1)、如果第一次添加时
    把table初始化为长度16的数组,threshold = 12
    (2)、如果不是第一次添加
    a、会考虑是否key有重复,那么就替换value
    b、如果table[i]下面不是树,统计table[i]的结点个数,添加之前达到7个,考虑树化
        当单个链表的节点个数添加之前达到7,并且table的长度达到64,才会树化
        当单个链表的结点个数添加之前达到7,table的长度未达到64,先扩容
    c、table[i]下面已经是树,单独处理,直接把新的映射关系连接到树的叶子结点
    d、添加后,size达到threshold,还要扩容
    一旦扩容,就会调整所有映射关系的位置
    
    
    
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
    // hash()目的是干扰hashCode值
    static final int hash(Object key) {
        // 如果key是null,hash是0
        // 如果key非Null,用key的hashCode值 与 key的hashCode值高16进行异或
        //      即就是用key的hashCode值高16位与低16位进行异或的干扰运算
        
        /*
        index = hash & table.length - 1
        如果用key的原始的hashCode值 与 table.length-1 进行按位与,那么基本上高16位不会参与进来
        这样就会增加重复的概率,为了降低冲突的概率,把高16位加入到hash信息中
        */
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab;    // 数组
        Node<K,V> p;         // 一个结点
        int n, i;            // n为数组长度   i是下标
        // tab和table等价
        // 如果table是空的
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        /*
        如果table是空的,resize()完成了
        a、创建了一个长度为16的数组
        b、threshold = 12
        n = 16
        */
        
        
        // i = (n - 1) & hash, 下标 = 数组数组长度 - 1 & hash
        //  p = tab[i]
        // if(p == null) 条件满足的话说命,table[i]还没有元素
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 把新的映射关系直接放入table[i]
            tab[i] = newNode(hash, key, value, null);
            // 根据传入的key和value生成一个新结点,此新结点的next是空,并把此新结点放入tab[i]
            
        else {
            Node<K,V> e; 
            K k;
            // p 是table[i]中的第一个结点
            // if(table[i]的第一个结点与新的映射关系的key重复)
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;    // 记录这个table[i]的第一个结点
            else if (p instanceof TreeNode)    // 如果table[i]第一个结点是树结点
                // 单独处理树结点
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                // table[i]的第一个结点不是树结点,也与新的映射关系的key不重复
                // binCount记录了table[i]下面的结点的个数
                for (int binCount = 0; ; ++binCount) {
                    // 如果p的下一个结点是空的,说命当前的P是最后一个结点
                    if ((e = p.next) == null) {
                        // 把新的结点连接到table[i]的最后
                        p.next = newNode(hash, key, value, null);
                        // 如果binCount>=8-1,达到7个时
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            // 要么扩容,要么树化
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 如果key重复了,就跳出for循环
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 如果e不是null,说命有key重复,就考虑替换
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        
        
        // 元素个数增加
        // size达到阈值
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    
    
    
    
    
    
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;    //oldTable原来的table
        // oldCap:原来数组长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
       
       // oldThr:原来阈值
       int oldThr = threshold;
       
       // newCap: 新容量
       // newThr:新阈值
        int newCap, newThr = 0;
        if (oldCap > 0) {    // 说命原来不是空数组
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                // newCap = 旧的容量*2,新的容量<最大数组容量限制
                // 新容量:32 64 ...
                // oldCap >= 初始容量16
                // 新阈值重新算 = 24,48 ,。。。
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;    //新容量是默认初始化容量16
            // 新阈值 = 默认加载因子 * 默认初始化容量 = 0.75 * 16 = 12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        // 阈值赋值为新阈值12
        threshold = newThr;
        
        // 创建一个新数组,长度为newCap16
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        
        if (oldTab != null) {
            // 把原来的table中映射关系,倒腾到新的table中
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {    // e是table下面的结点
                    oldTab[j] = null;    // 把旧的table[j]位置清空
                    if (e.next == null)    // 如果是最后一个结点
                        newTab[e.hash & (newCap - 1)] = e;    //重新计算e的在新的table中的存储位置,然后放入
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
    
    
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        // 创建一个新结点
        return new Node<>(hash, key, value, next);
    }
    
    
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index;
        Node<K,V> e;
        // MIN_TREEIFY_CAPACITY:最小树化容量64
        // 如果table是空的,或者,table的长度没有达到64
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();    // 先扩容
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
           /*
           do ... while,把table[index]链表变为红黑树
           */
           do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值