ArrayList源码解析整体分析
ArrayList与LinkedList对比
文章目录
1)扩容代码
以上两篇文章,详情直接点击即可,本篇则主要分析一下扩容机制,ArrayList到底是如何扩容的?
/**
* Default initial capacity.
* 默认初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
private void ensureCapacityInternal(int minCapacity) {
//判断初始化的elementData是不是空的数组,也就是没有长度
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//Math.max得出最大值,DEFAULT_CAPACITY=10;
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
/**
*判断当前ArrayList是否需要进行扩容
**/
private void ensureExplicitCapacity(int minCapacity) {
//快速报错机制
modCount++;
//minCapacity如果大于了实际elementData的长度,那么就说明elementData数组的长度不够用,不够用那么就要增加elementData的length
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
2)minCapacity真正的理解
什么时候走真正的扩容?那肯定是minCapacity - elementData.length > 0,也就是说明elementData本身的长度不够用了。
minCapacity大小直接跟add调用有关系。有两种情况:
- add(E e)调用的时候,minCapacity=size + 1,第一次add的时候,也就是minCapacity=1,判定初始值为10,所以最终Math.max得到minCapacity大小也为10;
- add(int index, E element) 调用有参构造初始化‘添加元素后所需的最小数组容量’为添加元素后实际元素个数
3)grow核心扩容逻辑
//最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//增加容量以确保它至少可以容纳最小容量参数指定的元素数量。
//参数:minCapacity – 所需的最小容量
private void grow(int minCapacity) {
//赋值之前的容量大小
int oldCapacity = elementData.length;
//新的容量大小=之前容量大小1.5倍
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:
//Arrays.copyOf空间换时间的扩容策略
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;
}
调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间
并将elementData的数据复制到新的内存空间
4)为什么ArrayList的最大数组大小为Integer.MAX_VALUE-8?
参考文章:请点击这里
2^31 = 2,147,483,648 ,作为自己需要8 bytes存储大小 的数组2,147,483,648
5)代码调试,验证扩容逻辑
我们之前写过一篇关于反射的文章:
如何使用反射获取属性值
对elementData我们也是通过反射来获取。
测试demo如下:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
// 定义一个数组,训话放入21个值,看如何动态扩容?
for (int i = 0; i < 21; i++) {
list.add(i);
// 没增加一个元素,都通过反射机制获取elementData值,并打印
Class<ArrayList> arrayListClass = ArrayList.class;
try {
Field field = arrayListClass.getDeclaredField("elementData");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(list);
System.out.println("第" + i + "个元素扩容后的elementData: " + objects.length);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
输出结果:
我们可以很清晰明了的看到扩容的情况
- 当list大小<10,容量为默认值也就是10
- 当10=<list<15,容量为1.5倍,也就是10*1.5=15
- 当15=<list<21,容量又扩容15倍,也就是15*1.5=22.5,整数为22