ArrayList源码分析
不分析也不行啊...分析了都找不着工作...
属性#
/**
* 默认容量,当前大小以及当前数组元素,没啥好说的
*/
private static final int DEFAULT_CAPACITY = 10;
private int size;
transient Object[] elementData;
/**
* 一眼看过去就是享元设计:
* 假如你调用了new ArrayList(0),这时ArrayList需要为你准备一个底层空数组,为了不让系统中每个刚创建的空ArrayList都创建自己的空数组,所以这里用静态常量做享元设计,类似Boolean.FALSE、Redis中的小数字字符串
* 加入你调用了new ArrayList(),也就是使用默认容量`10`,那这时使用`DEFAULTCAPACITY_EMPTY_ELEMENTDATA`
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
享元设计#
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 传入了>0的初始容量,那就创建自己的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 如果传入0,就使用EMPTY_ELEMENTDATA,所有这样的ArrayList都共享这个实例
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
// 如果没传入初始容量,就使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
add#
ArrayList中的size
代表当前你放置在其中的元素数,但它并不是底层数组的实际容量,所以在添加之前,首先调用ensureCapacityInternal
来确保目前底层数组具有足够的容量(size+1
)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这里注意参数的命名,minCapacity
,代表当前需要底层数组的最小容量。然后又调用了ensureExplicitCapacity
,并使用calculateCapacity
计算参数。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
calculateCapacity
很简单,就是为了保证如果当前是使用默认容量10的话,取10和当前需要的容量中最大的一个返回,这种代码我们在源码分析里可以忽略,屁用没有。
然后,ensureExplicitCapacity
就去判断,需要的底层数组容量如果大于底层数组的真实容量,就grow
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
其实到了现在都很简单,md真的不知道我要看这个源码干啥,为啥java现在这么难混...每天早上三点钟焦虑醒,真的要受不了了
总结#
总结一下,add
的时候就判断底层数组容量是否足够,不够就grow
grow#
private void grow(int minCapacity) {
// overflow-conscious code (考虑了溢出情况)
int oldCapacity = elementData.length;
// 新容量等于旧容量的1.5倍 相当于newCapacity = oldCapacity + oldCapacity / 2
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果增长完容量反而比需要还少,这里可能是发生了溢出,或者更加可能的是上次一次性添加了很多,超出1.5倍了
// 这时,取minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量比最大数组容量还大,调用这个`hugeCapacity`得到新数组容量,注意,传入的是需要的最小容量而不是计算出的新容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 使用经过一系列计算得到的newCapacity来扩容数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
MAX_ARRAY_SIZE
和hugeCapacity
#
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
MAX_ARRAY_SIZE
为啥是Int最大值减8?首先,Java中的数组使用整形做下标,所以理应,数组的最大长度应该是Integer.MAX_VALUE
,减8是因为某些JVM可能在数组上保留一些头部字,如果你真的创建了Integer.MAX_VALUE
这么大的数组,可能就会发生OOM,所以这里预留了8个。
我们要注意,传入hugeCapacity
的是当前所需要的最小容量,而不是简单一拍脑门计算出来的1.5倍的新容量。如果所需最小容量minCapacity < 0
,明显就是整数发生了溢出变成了负的,证明我们不能创建再大的数组了,直接OOM,否则,如果需要的容量比最大数组长度还大(就是Integer最大值减8),那就尝试使用Integer.MAX_VALUE
分配,否则还是用最大数组值来分配。
hugeCapacity
存在的目的,就是为了保证在非必要的情况下,不将底层数组扩容至MAX_ARRAY_SIZE
之上,因为在一些JVM中可能会发生OOM,如果计算出的1.5倍的newCapacity
已经超过了,就尝试判断下需要的最小容量minCapacity
是否真的超过了MAX_ARRAY_SIZE
,如果没有就不将它扩展到MAX_ARRAY_SIZE
之上,而是让它正好等于MAX_ARRAY_SIZE
总结#
如果底层数组长度小于最小需要容量
- 尝试将数组扩大1.5倍
- 如果1.5倍还不够,就使用足够情况下的最小长度
- 如果决定扩容后的新数组长度过大,超过
Integer.MAX_VALUE - 8
,为了保证不在一些JVM上发生OOM,ArrayList会判断实际的需求是否真的需要这么多 - 如果不需要,则使用
Integer.MAX_VALUE - 8
- 否则,没办法了,摆烂!使用
Integer.MAX_VALUE
最后总结#
add
的时候就判断底层数组容量是否足够,不够就grow
grow
时首先尝试将数组扩大1.5倍- 如果1.5倍还不够,就使用足够情况下的最小长度
- 如果决定扩容后的新数组长度过大,超过
Integer.MAX_VALUE - 8
,为了保证不在一些JVM上发生OOM,ArrayList会判断实际的需求是否真的需要这么多 - 如果不需要,则使用
Integer.MAX_VALUE - 8
- 否则,没办法了,摆烂!使用
Integer.MAX_VALUE
作者:Yudoge
出处:https://www.cnblogs.com/lilpig/p/16740154.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
欢迎按协议规定转载,方便的话,发个站内信给我嗷~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)