1.ArrayList的扩容机制
首先是ArrayList的默认初始容量是10;
private static final int DEFAULT_CAPACITY = 10;//数组默认初始容量
private static final Object[] EMPTY_ELEMENTDATA = {};//定义一个空的数组实例以供其他需要用到空数组的地方调用
ArrayList的构造方式有三种
默认的构造器就是用了参数DEFAULTCAPACITY_EMPTY_ELEMENTDATA返回了一个空的数组,所以可以了解到ArrayList在创建的时候如果没有指定初始容量的话就会返回一个长度为0的空数组.
ArrayList的扩容机制首先要说ensureCapacityInternal方法,这个ArrayList有两个默认的空数组,DEFAULTCAPACITY_EMPTY_ELEMENTDATA:是用来使用默认构造方法时候返回的空数组。如果第一次添加数据的话那么数组扩容长度为DEFAULT_CAPACITY=10。
EMPTY_ELEMENTDATA:出现在需要用到空数组的地方,其中一处就是使用自定义初始容量构造方法时候如果你指定初始容量为0的时候就会返回。
如果是使用了空数组EMPTY_ELEMENTDATA话,那么不会返回默认的初始容量。
//判断当前数组是否是默认构造方法生成的空数组,如果是的话minCapacity=10反之则根据原来的值传入下一个方法去完成下一步的扩容判断
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//minCapacitt表示修改后的数组容量,minCapacity = size + 1
private void ensureCapacityInternal(int minCapacity) {
//判断看看是否需要扩容
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
ensureExplicitCapacity方法
如果修改后的数组容量大于当前的数组长度那么就需要调用grow进行扩容,反之则不需要。
//判断当前ArrayList是否需要进行扩容
private void ensureExplicitCapacity(int minCapacity) {
//快速报错机制
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
ArrayList扩容的核心方法grow()
首先,如果当前数组是由默认构造方法生成的空数组并且第一次添加数据。此时minCapacity等于默认的容量(10),那么根据下面逻辑可以看出最后数组的容量会从0扩容成10。而后的数组扩容才是按照当前容量的1.5倍进行扩容;
其次,如果当前数组是由自定义初始容量构造方法创建并且指定初始容量为0。此时minCapacity等于1,那么根据下面逻辑可以看出最后数组的容量会从0变成1。这样会发现一个严重的问题,如果执行了初始容量为0,那么根据下面的算法前四次扩容每次都 +1,在第5次添加数据进行扩容的时候才是按照当前容量的1.5倍进行扩容。
然后,当扩容量(newCapacity)大于ArrayList数组定义的最大值后会调用hugeCapacity来进行判断。如果minCapacity已经大于Integer的最大值(溢出为负数)那么抛出OutOfMemoryError(内存溢出)否则的话根据与MAX_ARRAY_SIZE的比较情况确定是返回Integer最大值还是MAX_ARRAY_SIZE。这边也可以看到ArrayList允许的最大容量就是Integer的最大值(-2的31次方~2的31次方减1)
//ArrayList扩容的核心方法,此方法用来决定扩容量
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//集合最大容量
最后简单总结一下,底层使用数组,默认容量10,临界值是容量的0.75倍,每次1.5倍进行扩容,无序添加
而LinkedList与此不同,他的底层使用双向链表,由于它是一个双向链表,所以没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好。
2.Map的扩容机制
HashMap的底层数据结构是数组+链表,在jdk1.8以后底层数据结构变为了数组+链表+红黑树,改成数组+链表+红黑树主要是为了提升链表过长时的查找性能。
对于插入在同一个索引位置的节点在新增后达到9个(阈值为8)时,如果此时的数组长度也大于等于64的时候,则会触发链表节点转为红黑树结点,如果数组长度小于64则不会触发链表转为红黑树,会进行扩容
而对于移除,当同一个索引位置的节点在移除后达到 6 个,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点。
在首次调用put方法的时候初始化数组table。
当HashMap中的元素个数超过数组大小(也就是数组长度)× loadFactor(负载因子)的时候,会进行扩容,loadFactor的默认值DEFAULT_LOAD_FACTOR是0.75,默认情况下,数组大小为16,那么当HashMap中的元素个数超过16×0.75=12,就把数组的大小扩展为2×16=32,也就是说,在元素个数大于数组长度的0.75时默认是会进行扩容的,会扩大到当前大小的两倍,然后重新计算每个元素在数组中的位置。
另外Hashtable的底层是哈希表,同样每个元素是一个key-value对,它的内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。Hashtable也是在JDK1.0引入的类,但它是线程安全的,能用于多线程环境中。Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆。但是现在由于他的父类Dictionary类是一个已经被废弃的类,所以已经不再被使用。HashTable键值对都不能为空,否则会报空指针异常。
HashMap 扩容会扩容到原来的两倍,而且扩容结果一定是2的幂次倍,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入,但是Hashtable扩容时会扩容为原容量的2倍加1。
报空指针异常。
HashMap 扩容会扩容到原来的两倍,而且扩容结果一定是2的幂次倍,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入,但是Hashtable扩容时会扩容为原容量的2倍加1。