浅谈ArrayList的扩容机制
源码
以下为jdk21中ArrayList部分源码
private Object[] grow() {return grow(size + 1);
}private Object[] grow(int minCapacity) {int oldCapacity = elementData.length;if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, /* minimum growth */oldCapacity >> 1 /* preferred growth */);return elementData = Arrays.copyOf(elementData, newCapacity);} else {return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];}
}
ArrayList中有两个grow
方法,实现逻辑均在grow(int minCapacity)
方法中,grow()
方法是对grow(int minCapacity)
的调用封装。
实现解析
private Object[] grow(int minCapacity) {// 1.获取当前数组的长度int oldCapacity = elementData.length;if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {// 2.若数据长度>0或数组不等于默认的空数组,则计算扩容后的数数组长度。// 2.1扩容后长度为:minCapacity或员长度的1.5倍int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, /* minimum growth */oldCapacity >> 1 /* preferred growth */);// 3.使用Arrays.copyOf将数组元素拷贝到新数组return elementData = Arrays.copyOf(elementData, newCapacity);} else {// 2.若数组长度小于等于0或数组等于默认的空数组,则初始化数组,数组容量取DEFAULT_CAPACITY与minCapacity中的最大值。return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];}
}
实现逻辑已注释在源码上,可自行查看,下面着重分析实现细节。
DEFAULTCAPACITY_EMPTY_ELEMENTDATA的作用
ArrayList源码中分别定义了两个成员变量EMPTY_ELEMENTDATA
和DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,初始值都为空数组,两者有何区别,为什么要分别定义。
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
可以分别查看两个变量在何处使用来分析其作用:
public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}
}public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
结合扩容方法可得出以下结论:
EMPTY_ELEMENTDATA
和DEFAULTCAPACITY_EMPTY_ELEMENTDATA
均在ArrayList构造函数中引用,表示创建了一个元素为空的数组。- 差异在于,
new ArrayList<Object>(0)
和new ArrayList<Object>()
创建list对象后,第一次往list中添加元素,扩容的逻辑不一样,举例,如使用add()
方法添加元素,new ArrayList<Object>()
扩容后的长度为10,而new ArrayList<Object>(0)
为1(具体可分析以上扩容代码)。
为什么要分别定义(以下内容来自AI):
JDK 之所以这么做,并不是说“技术上必须要两个空数组”,而是为了:
1.区分用户显式容量 = 0 和隐式默认容量的语义;
2.节省内存(懒初始化);
3.保持 API 的行为一致性(默认容量仍然是 10);
4.成本极低,用两个常量数组就能实现。
ArraysSupport.newLength的方法实现
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {// preconditions not checked because of inlining// assert oldLength >= 0// assert minGrowth > 0// 1.计算所需长度:当前长度 + Math.max(扩容所需最小长度, oldLength >> 1)int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflowif (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {// 2.若长度>0且小于等于Integer.MAX_VALUE - 8,直接返回return prefLength;} else {// put code cold in a separate methodreturn hugeLength(oldLength, minGrowth);}
}private static int hugeLength(int oldLength, int minGrowth) {// 1.获取所需最小容量int minLength = oldLength + minGrowth;if (minLength < 0) { // overflow// 2.溢出抛异常throw new OutOfMemoryError("Required array length " + oldLength + " + " + minGrowth + " is too large");} else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {// 2.小于等于最大安全长度,统一返回最大安全长度return SOFT_MAX_ARRAY_LENGTH;} else {// 2.直接返回minLengthreturn minLength;}
}
为什么使用Integer.MAX_VALUE - 8作为阈值(以下来自AI):
这是为了 留出数组对象头和实现开销的空间,避免在某些 JVM 上出现
OutOfMemoryError
或InternalError
。
- 在 HotSpot JVM 里,Java 数组在底层是一个对象,包含:
- 对象头 (Object Header):存放哈希码、GC 信息、锁信息等。
- 数组长度字段(数组对象特有)。
- 数组元素数据区。
- 不同 JVM、不同平台(32 位/64 位、开启压缩指针与否),对象头大小不一样(通常 12 或 16 字节),加上对齐填充,也需要额外空间。
👉 如果直接允许数组长度达到
Integer.MAX_VALUE
,在某些实现下可能因为额外的头部/对齐开销导致实际所需内存超过2^31 - 1
,从而报错。为什么是8
-8
是 JDK 里一个 安全余量 (safety margin)。它不是严格的对象头大小(因为对象头可能是 12、16、24 字节),而是一个经验性的保守值。
这样即使在不同 JVM 实现或未来调整下,
new Object[Integer.MAX_VALUE - 8]
依然大概率能成功,而不会立刻抛错。