Java源码分析之ArrayList

Java源码分析之ArrayList

之前整理了HashMap的源码分析
接下来,这里整理的是ArrayList的源码分析

对于ArrayList,我们需要知道以下几点:

1
2
3
4
5
6
7
ArrayList实现了List接口,是顺序容器,元素存取的顺序相同。
允许放入null元素,底层通过数组实现。
除该类未实现同步外,其余跟Vector大致相同。
每个ArrayList都有一个容量(capacity),表示底层数组的实际大小。
容器内存储元素的个数不能多于当前容量。
当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小。
所以这里的数组是一个Object数组,以便能够容纳任何类型的对象。
1
2
3
4
5
ArrayList初始化时候是一个空的Object数组
private transient Object[] elementData;

当添加第一个元素的时候,数组的大小变成10(也就是数组的默认大小)
private static final int DEFAULT_CAPACITY = 10;
Add方法
1
2
3
4
5
6
7
8
每次add的时候都会进行剩余空间检查。
minCapacity = size + 1;
如果是第一次,则空间大小(minCapacity)直接变成10,再执行如下步骤:
1.判断minCapacity是否大于elementData.length
即:if (minCapacity - elementData.length > 0)
2.条件成立:grow(minCapacity);
3.grow内部调用Arrays.copyOf进行数组复制
4.Arrays.copyOf调用System.arrayCopy进行数组复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void grow(int minCapacity) {
// 新数组增长为之前的1.5倍
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果还是小与minCapacity,那么变成minCapacity
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);
}
//最大容量变成Integer.MAX_VALUE,或者超出MAX_VALUE范围,跑出OutOfMenory异常
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
1
2
System.arraycopy(源数组, 源数组起始位置, 目标数组, 目标起始位置, 复制长度);
System.arraycopy(elementData, index+1, elementData, index, numMoved);
检查是否越界
1
2
3
4
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
Set方法
1
2
3
4
5
6
7
8
既然底层是一个数组ArrayList的set()方法也就变得非常简单,直接对数组的指定位置赋值即可。

public E set(int index, E element) {
rangeCheck(index); // 下标越界检查
E oldValue = elementData(index);
elementData[index] = element; // 赋值到指定位置,复制的仅仅是引用
return oldValue; // 返回之前的元素
}
Get方法
1
2
3
4
5
6
get()方法同样很简单,唯一要注意的是由于底层数组是Object[],得到元素后需要进行类型转换。

public E get(int index) {
rangeCheck(index);
return (E) elementData[index];//注意类型转换
}
Remove()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
remove()方法也有两个版本,一个是remove(int index)删除指定位置的元素,
另一个是remove(Object o)删除第一个满足o.equals(elementData[index])的元素。
删除操作是add()操作的逆过程,需要将删除点之后的元素向前移动一个位置。
需要注意的是为了让GC起作用,必须显式的为最后一个位置赋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;
}


public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

// 此方法跟remove基本一样,只不过不需要返回值,也不用检查是否越界
private void fastRemove(int index) {
modCount++;
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
}
AddAll方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//把指定的集合元素添加到list后面,先进行容量检查,然后复制
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}

//在指定位置插入指定的集合元素
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
// 原数组:头部 + 尾部
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
// 新数组:头部 + 空 + 尾部
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 新数组:头部 + 新增 + 尾部
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
作者

Trainoo

发布于

2018-06-25

更新于

2020-06-02

许可协议