ArrayList 的扩容机制
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 中添加元素时,它会首先检查当前数组的容量是否足够。具体来说,在执行 add 方法时会判断当前的元素个数(size)是否等于数组容量(elementData.length)。若两者相等,则表示数组已满,需要触发扩容操作。
扩容操作的核心逻辑位于 grow(int minCapacity) 方法中,其参数 minCapacity代表满足当前添加操作所需的最小容量,即 size + 1。
方法的执行流程分为两种情况:
-
正常扩容(数组已使用过):
如果原数组容量大于0,或者当前数组不是默认的空数组(意味着已经分配过内存),则会计算新容量。计算规则是:- 最小增长量:
minCapacity - oldCapacity(即至少需要增加多少才能满足需求) - 首选增长量:
oldCapacity >> 1(即原容量的一半,相当于扩容至原容量的1.5倍) ArraysSupport.newLength方法会从这两个增长量中选取一个较大值,来确定最终的新容量。- 确定容量后,通过
Arrays.copyOf将原数组元素拷贝到新扩容的数组中。
- 最小增长量:
-
首次扩容(默认空数组的第一次添加):
如果数组是默认的空数组(即通过new ArrayList<>()创建且从未添加过元素),则直接创建一个新数组。其容量取DEFAULT_CAPACITY(默认值为10)和minCapacity中的较大值。
简而言之,ArrayList 的扩容策略是:优先尝试按1.5倍增长,如果1.5倍不够满足最小需求,则按最小需求增长;如果是首次添加,则直接初始化为默认容量10。
最大数组长度
在阅读源码时,你会发现这么几行代码
/**
* A soft maximum array length imposed by array growth computations.
* Some JVMs (such as HotSpot) have an implementation limit that will cause
*
* OutOfMemoryError("Requested array size exceeds VM limit")
*
* to be thrown if a request is made to allocate an array of some length near
* Integer.MAX_VALUE, even if there is sufficient heap available. The actual
* limit might depend on some JVM implementation-specific characteristics such
* as the object header size. The soft maximum value is chosen conservatively so
* as to be smaller than any implementation limit that is likely to be encountered.
*/
public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// preconditions not checked because of inlining
// assert oldLength >= 0
// assert minGrowth > 0
int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
return prefLength;
} else {
// put code cold in a separate method
return hugeLength(oldLength, minGrowth);
}
}
为什么这个扩容的最大数据为2147483647 - 8呢?而不是减 16?为什么不是减 4?为什么不是直接用 Integer.MAX_VALUE?
jvm本身的内存对齐

JVM 在分配内存时,要求对象的起始地址必须是 8 字节的整数倍。这叫做 8 字节对齐。
假如你要分配为N的对象数组
总内存占用 = 对象头 + N * 每个引用的字节数
实际占用 = 对齐到8的倍数(对象头 + N* 每个引用的字节数)
如果我设置容量为2147483639,即限制的最大数,此时内存占用为(假如是四字节引用)
内存占用: 16 + 2147483639 * 4 = 8 589 934 572
8 589 934 572 mod 8 = 4,所以需要补充四个字节
实际占用为8 589 934 576
那如果是2147483640,岂不是也可以呢?
16 + 2147483640 * 4 mod 8 = 0
虽然2147483640在数学上可行,但是为了保守,JVM做了安全保留。

2万+

被折叠的 条评论
为什么被折叠?



