Jdk源码之:集合的扩容

57 阅读4分钟

基于JDK1.8的源码进行分析

1. ArrayList

1.1 重要的成员变量

class ArrayList<E> {
    
    // 底层数组的默认大小
    private static final int DEFAULT_CAPACITY = 10;
    
    // 底层数组的最大长度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
    // 空数组,当有以下其中一种情况时,ArrayList的底层数组就会设置成该数组
    // 1. new ArrayList<>(0)
    // 2. new ArrayList<>(EMPTY_COLLECTION)
    // 3. arrayList.trimToSize()时,arrayList的大小正好是0
    // EMPTY_ELEMENTDATA用来缓存空数组、节约空间、提高效率,避免new Object[0]这种无意义的操作
    // 之所以说new Object[0]无意义,一是因为空数组无法存储数据,二是一旦扩容,这个空数组就没用了
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    // 空数组,当且仅当new ArrayList<>()时,ArrayList的底层数组才会设置成该数组
    // 注意,虽然ArrayList的默认大小是10,但在new ArrayList<>()时,JDK并没有立刻将数组初始化为new Object[10]
    // 这种懒初始化的方式可以节约空间,毕竟"new ArrayList<>()后,没有往集合中添加元素"这种情况也是常有发生的
    // 这里又创建了一个空数组,主要是为了与EMPTY_ELEMENTDATA区分开,方便后续判断
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    // 底层数组
    transient Object[] elementData;
}

1.2 扩容方法

class ArrayList<E> {
    
    // 确保底层数组能够容纳minCapacity个元素;如果底层数组长度小于minCapacity,则进行扩容
    // 该方法后续还会提到,因此为了更好记住这个方法,不妨将该方法称为"请求扩容"方法
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    // 真正进行扩容的逻辑,注意本方法只被请求扩容方法调用
    // ArrayList的其它方法都是通过调用请求扩容方法来间接进行扩容的
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        
        // 新的长度是原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        
        // 如果1.5倍的长度还达不到minCapacity的要求,则直接扩展到minCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        
        // 如果新长度比最大长度还要大,则干脆直接将新长度指定为Integer.MAX_VALUE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        
        // 创建新数组,并将原来的数据移到新数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
}

1.3 new ArrayList<>()的懒初始化

class ArrayList<E> {
    
    // 调用add()或addAll()方法时都会调用本方法来确保底层数组容纳minCapacity个元素
    private void ensureCapacityInternal(int minCapacity) {
        
        // 如果是new ArrayList<>(),并且minCapacity小于10,则会将minCapacity指定为10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        
        // 请求将数组扩容到minCapacity
        ensureExplicitCapacity(minCapacity);
    }
    
    // 确保底层数组能够容纳minCapacity个元素,逻辑很简单:
    // 1. 如果不是new ArrayList<>(),则只要minCapacity大于0,就进行请求扩容
    // 2. 如果是new ArrayList<>(),则minCapacity大于10时才请求扩容(逻辑上认为new ArrayList<>()的底层数组长度是10)
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    
    // 可以发现,new ArrayList<>()的底层虽然是空数组,但在逻辑上,我们依然认为其长度是默认长度10
}

1.4 总结

  1. new ArrayList<>(positive)底层数组初始化为new Object[positive]
  2. new ArrayList<>(0)底层数组初始化为EMPTY_ELEMENTDATA
  3. new ArrayList<>()底层数组初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  4. new ArrayList<>()采用了懒初始化的思想,底层虽然是空数组,但逻辑上依然认为其长度是默认长度10
  5. 扩容时,新的长度 = 1.5倍旧长度;如果1.5倍旧长度还不满足要求,则直接扩容到用户指定的长度
  6. 如果新长度超过了MAX_ARRAY_SIZE,则直接将新长度指定为Integer.MAX_VALUE

2. HashMap

2.1 重要的成员变量

public class HashMap<K, V> {
    
    // 默认容量;HashMap的容量(即底层数组长度)必须是2的倍数,这样在计算桶下标和扩容时效率都会更高
    // 如果用户指定的容量不是2的幂,那么HashMap会找到第一个比指定容量大的2的幂,将其作为初始容量
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    
    // 最大容量;即使用户指定的容量比该值大,HashMap的容量也依然是MAXIMUM_CAPACITY
    static final int MAXIMUM_CAPACITY = 1 << 30;
    
    // 默认负载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    // 如果某个桶的元素个数达到了该值,则再往该桶添加一个元素后,会将该桶中的链表转成红黑树
    static final int TREEIFY_THRESHOLD = 8;
    
    // 在进行扩容时,会将红黑树切分成high子树和low子树;如果子树的节点个数小于等于该值,则子树退化成链表
    // 
    // 这里大概说一下红黑树的切分,不妨假设HashMap的容量为2,且0号桶存放的是红黑树,其元素有[0, 2, 4, 6, 8]
    // 在进行扩容时,容量变为4,那么对于[0, 2, 4, 6, 8]这些元素,[0, 4, 8]依然位于0号桶,而[2, 6]会转移到2号桶
    // 这个过程就是红黑树的切分,并且[0, 4, 8]组成的树称为low树,[2, 6]组成的树称为high树
    //
    // 注意,在remove()操作时,如果删除的是红黑树上的元素,并且删除后红黑树节点个数过少,此时红黑树也会退化成链表
    // 但这里"过少"的判断条件为:根节点为空 || 右节点为空 || 左节点为空 || 左左节点为空,与UNTREEIFY_THRESHOLD无关
    static final int UNTREEIFY_THRESHOLD = 6;
    
    // 上面说的将链表转成红黑树是有另一个前提条件的,即HashMap底层数组的长度达到该值
    // 如果容量未达到该值,那么HashMap会优先通过扩容来减少桶的元素个数以提升效率,而不是将链表转成红黑树来提升效率
    static final int MIN_TREEIFY_CAPACITY = 64;
    
    // 底层数组;懒初始化;一般来说,table要么是null,要么长度大于0(2的幂),但也有长度等于0的情况:
    // Tolerate length zero in some operations to allow bootstrapping mechanics that are currently not needed.
    transient Node<K, V>[] table;
    
    // 该HashMap的负载因子
    final float loadFactor;
    
    // 如果元素个数大于该值(该值等于容量 * 负载因子),则进行扩容;后文我们将该变量称为"扩容阈值"
    // 另外,HashMap也是懒初始化的;当底层数组为null时,该变量的值就代表了初始容量
    int threshold;
}

2.2 扩容方法

public class HashMap<K, V> {
    
    // 该方法的名称是"resize",听起来好像可以用来扩容和缩容
    // 但实际上HashMap并没有缩容机制,这是因为与其进行缩容,不如直接创建新的HashMap
    // 因此,该方法就是纯粹的扩容操作,别被方法名误导了
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0; // 这里newThr为0表示新的扩容阈值还未计算出来
        
        // 如果底层数组已经初始化过了
        if (oldCap > 0) {
            
            // 如果当前容量已经是最大了,则无需扩容
            // 此时将扩容阈值更新为最大,这样的话,之后判断是否扩容都会返回false,避免再次调用本方法
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            
            // 否则,新的容量是旧容量的两倍
            // 并且如果新容量小于最大容量且旧容量大于等于默认容量(此处尚有疑问),则新的扩容阈值也为原来的两倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1;
        }
        
        // 如果底层数组还未初始化,则进行初始化;注意此时oldThr就代表初始容量
        else {
            
            // 如果用户指定了初始容量,则新容量为用户指定的这个值
            // 此时HashMap的负载因子可能是默认值或用户自定义的值,因此这里没有直接计算新的扩容阈值
            if (oldThr > 0)
                newCap = oldThr;
            
            // 否则,新容量为默认容量(new HashMap<>())
            // 并且此时HashMap的负载因子一定是默认值,因此可以直接计算新的扩容阈值
            else {
                newCap = DEFAULT_INITIAL_CAPACITY;
                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);
        }
        
        // 扩容,并更新扩容阈值
        threshold = newThr;
        Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
        table = newTab;
        
        // 迁移数据
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K, V> e;
                if ((e = oldTab[j]) != null) {
                    
                    // 将链表/红黑树从旧数组中删除
                    oldTab[j] = null;
                    
                    // 如果链表/红黑树只有一个元素,则直接将其放到新数组对应的下标
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    
                    // 如果是红黑树,则将红黑树切分成low子树和high子树,并将子树放到对应的下标
                    else if (e instanceof TreeNode)
                        ((TreeNode<K, V>)e).split(this, newTab, j, oldCap);
                    
                    // 如果是链表,则和红黑树切分类似,把链表分成low和high两部分,并将这两部分放到对应的位置
                    else {
                        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;
    }
}

2.3 总结

  1. new HashMap<>(initialCapacity, loadFactor)会给HashMapthresholdloadFactor变量赋值
  2. new HashMap<>(initialCapacity)会给HashMapthresholdloadFactor变量赋值,其中loadFactor为默认值
  3. new HashMap<>()会给loadFactor变量赋默认值
  4. HashMap的默认容量为16,扩容时容量变为2倍
  5. 如果容量已经达到最大容量限制,则不再进行扩容