学而不思则罔,思而不学则殆
【Java】ArrayList源码解析
引言
ArrayList是一个很常见的列表工具类,虽然是列表,但它的底层原理却是数组。
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
The array buffer into which the elements of the ArrayList are stored. The capacity of the ArrayList is the length of this array buffer. Any empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
存储ArrayList元素的数组缓冲区。ArrayList的容量是这个数组缓冲区的长度。任何带有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList将在添加第一个元素时扩展为DEFAULT_CAPACITY。
测试添加元素
测试代码
通过反射拿到elementData的数组
private static void show() {
System.out.println("\n--------------------------");
Class<ArrayList> aClass = ArrayList.class;
try {
Field field = aClass.getDeclaredField("elementData");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(arrayList);
System.out.println("size:" + arrayList.size());
System.out.println("length:" + objects.length + " elementData:" + Arrays.toString(objects));
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println("--------------------------\n");
}
测试空列表
初始化一个列表
private static ArrayList<Integer> arrayList = new ArrayList<>();
打印信息如下:
size:0
length:0 elementData:[]
通过反射拿到DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的对象看一下:
try {
Field field = aClass.getDeclaredField("elementData");
Field empty = aClass.getDeclaredField("DEFAULTCAPACITY_EMPTY_ELEMENTDATA");
field.setAccessible(true);
empty.setAccessible(true);
Object[] objects = (Object[]) field.get(arrayList);
Object[] default_empty = (Object[]) empty.get(null); //静态变量通过null获取
System.out.println("default_empty:" + default_empty);
System.out.println("objects:" + objects);
System.out.println("size:" + arrayList.size());
System.out.println("length:" + objects.length + " elementData:" + Arrays.toString(objects));
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
打印结果:
default_empty:[Ljava.lang.Object;@7ea987ac
objects:[Ljava.lang.Object;@7ea987ac
size:0
length:0 elementData:[]
两个是同一个数组对象,说明空数组的时候,采用了:DEFAULTCAPACITY_EMPTY_ELEMENTDATA
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
添加一个元素
arrayList.add(0);
show();
打印数组信息:
size:1
length:10 elementData:[0, null, null, null, null, null, null, null, null, null]
list的size=1
底层数组的长度为10
其他九个位置为null,数据为空
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//第一次添加元素的是否返回DEFAULT_CAPACITY = 10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
数组长度:0 ->10
测试数组第二次扩容
此时数组长度为10,我们把数组填满。
for (int i = 1; i < 10; i++) {
arrayList.add(i);
}
show();
打印一下此时的数组情况:
size:10
length:10 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
size为10,数组长度也为10,再添加一个元素数组就放不下了?那要怎么处理呢?我们都知道ArrayList是一个可变的数组,怎么可变的呢?
我们先看看扩容长度怎么变化:
测试:在上面的基础上再添加一个元素,在打印数组情况
arrayList.add(11);
show();
情况如下:
size:11
length:15 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, null, null, null, null]
此时size=11,数组长度变为15了,刚刚加入了11,后面四个是null,空位置
再看源码,基于之前的:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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:
elementData = Arrays.copyOf(elementData, newCapacity);
}
- 在调用ensureCapacityInternal方法的时候,size=10 所以入参 = 11
- calculateCapacity此时返回的是11
- ensureExplicitCapacity方法入参是11,此时数组长度10,所以会走grow方法,入参是11
- 再来看看关键方法grow,入参11
- 其中oldCapacity = 10,老的数组长度 新的数组长度= oldCapacity +oldCapacity/2 = 15 (跟前文的15对上了)
- 然后是一些判断逻辑,先不用关心,主要最后一行:数组复制,浅拷贝。
Arrays.copyOf 简单测试:
int[] ints = new int[]{1, 2, 3, 4};
int[] ints1 = Arrays.copyOf(ints, 8);
int[] ints2 = Arrays.copyOf(ints, 3);
System.out.println(Arrays.toString(ints));//[1, 2, 3, 4]
System.out.println(Arrays.toString(ints1));//[1, 2, 3, 4, 0, 0, 0, 0]
System.out.println(Arrays.toString(ints2));//[1, 2, 3]
想了解更多Arrays相关的方法,访问:【Java】Arrays
结束了就修改了elementData 数组长度,做了一次数组拷贝。
理论上会有数组的操作,操作时间复杂度为:
O
(
n
)
O(n)
O(n)
ArrayList扩容长度总结
这里只是默认不设置长度的情况。
L
e
n
g
t
h
[
i
]
=
{
10
i=0
L
e
n
g
t
h
[
i
]
=
L
e
n
g
t
h
[
i
−
1
]
+
L
e
n
g
t
h
[
i
−
1
]
/
2
i>0
Length[i]= \begin{cases} 10 & \text{i=0}\\ Length[i] = Length[i-1]+Length[i-1]/2 & \text{i>0}\\ \end{cases}
Length[i]={10Length[i]=Length[i−1]+Length[i−1]/2i=0i>0
举例:
0
10
15 = 10 + 10/2 = 10 + 5
22 = 15 + 15/2 = 15 + 7
33 = 22 + 22/2 = 22 + 11
49 = 33 + 33/2 = 33 + 16
长度扩容验证
前面我们长度是15了
15 --》22
//填充
arrayList.add(12);
arrayList.add(13);
arrayList.add(14);
arrayList.add(15);
show();
//再加一个元素,数组长度扩容
arrayList.add(15);
show();
--------------------------
size:15
length:15 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15]
--------------------------
--------------------------
size:16
length:22 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 15, null, null, null, null, null, null]
--------------------------
22 --》33
//填充
for (int i = 16; i < 22; i++) {
arrayList.add(i);
}
show();
//扩容
arrayList.add(22);
//扩容
show();
--------------------------
size:22`在这里插入代码片`
length:22 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 15, 16, 17, 18, 19, 20, 21]
--------------------------
--------------------------
size:23
length:33 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 15, 16, 17, 18, 19, 20, 21, 22, null, null, null, null, null, null, null, null, null, null]
--------------------------
测试删除元素
先填充元素,size = 15
for (int i = 0; i < 15; i++) {
arrayList.add(i);
}
show();
--------------------------
size:15
length:15 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
--------------------------
此时size和length都是15
删除0下标位置的元素
arrayList.remove(0);
show();
size:14
length:15 elementData:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, null]
看看源码:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
其中最重要的就是:
System.arraycopy(elementData, index+1, elementData, index, numMoved);
表示把[index+1,size)的所有元素都向前移位,并把size-1的位置元素置为null.
时间复杂度为:
O
(
n
u
m
M
o
v
e
d
)
O(numMoved)
O(numMoved)
更多arraycopy用法详见:【Java】System.arraycopy