基于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 总结
new ArrayList<>(positive)
底层数组初始化为new Object[positive]
new ArrayList<>(0)
底层数组初始化为EMPTY_ELEMENTDATA
new ArrayList<>()
底层数组初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
new ArrayList<>()
采用了懒初始化的思想,底层虽然是空数组,但逻辑上依然认为其长度是默认长度10- 扩容时,新的长度 = 1.5倍旧长度;如果1.5倍旧长度还不满足要求,则直接扩容到用户指定的长度
- 如果新长度超过了
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 总结
new HashMap<>(initialCapacity, loadFactor)
会给HashMap
的threshold
和loadFactor
变量赋值new HashMap<>(initialCapacity)
会给HashMap
的threshold
和loadFactor
变量赋值,其中loadFactor
为默认值new HashMap<>()
会给loadFactor
变量赋默认值HashMap
的默认容量为16,扩容时容量变为2倍- 如果容量已经达到最大容量限制,则不再进行扩容