要回答上面的问题,我们就需要对ArrayList的源码进行一番分析,深入了解其实现原理的话,我们就自然能够解答上述问题。
需要说明的是,本文所分析的源码引用自JDK 8版本
ArrayList使用的存储的数据结构
从源码中我们可以发现,ArrayList使用的存储的数据结构是Object的对象数组。
其实这也不能想象,我们知道ArrayList是支持随机存取的类似于数组,所以自然不可能是链表结构。
1 | / |
我想大家一定对这里出现的transient关键字很疑惑,我们都知道ArrayList对象是可序列化的,但这里为什么要用transient关键字修饰它呢?查看源码,我们发现ArrayList实现了自己的readObject和writeObject方法,所以这保证了ArrayList的可序列化。具体序列化的知识我们在此不过多赘述。有兴趣的读者可以参考笔者关于序列化的文章。
ArrayList的初始化
ArrayList提供了三个构造函数。下面我们依次来分析
- public ArrayList(int initialCapacity) 当我们初始化的时候,给ArrayList指定一个初始化大小的时候,就会调用这个构造方法。
1
List<String> myList = new ArrayList<String>(7);
源码中这个方法的实现如下
1 | / |
这里的EMPTY_ELEMENTDATA 实际上就是一个共享的空的Object数组对象。
1 | / |
上述代码很容易理解,如果用户指定的初始化容量大于0,就new一个相应大小的数组,如果指定的大小为0,就复制为共享的那个空的Object数组对象。如果小于0,就直接抛出异常。
- public ArrayList() 默认的空的构造函数。
我们一般会这么使用1
myList = new ArrayList();
源码中的实现是
1 | / |
其中DEFAULTCAPACITY_EMPTY_ELEMENTDATA 定义为
1 | / |
注释中解释的很清楚,就是说刚初始化的时候,会是一个共享的类变量,也就是一个Object空数组,当第一次add的时候,这个数组就会被初始化一个大小为10的数组。
- public ArrayList(Collection<? extends E> c) 如果我们想要初始化一个list,这个list包含另外一个特定的collection的元素,那么我们就可以调用这个构造函数。
我们通常会这么使用1
2
3
4
5
6Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
set.add(4);
ArrayList<Integer> list = new ArrayList<>(set);
源码中是这么实现的
1 | / |
首先调用给定的collection的toArray方法将其转换成一个Array。
然后根据这个array的大小进行判断,如果不为0,就调用Arrays的copyOf的方法,复制到Object数组中,完成初始化,如果为0,就直接初始化为空的Object数组。
ArrayList是如何动态增长
当我们像一个ArrayList中添加数组的时候,首先会先检查数组中是不是有足够的空间来存储这个新添加的元素。如果有的话,那就什么都不用做,直接添加。如果空间不够用了,那么就根据原始的容量增加原始容量的一半。
源码中是如此实现的:
1 | / |
ensureCapacityInternal的实现如下:
1 | private void ensureCapacityInternal(int minCapacity) { |
DEFAULT_CAPACITY为:
1 | private static final int DEFAULT_CAPACITY = 10; |
这也就实现了当我们不指定初始化大小的时候,添加第一个元素的时候,数组会扩容为10.
1 | private void ensureExplicitCapacity(int minCapacity) { |
这个函数判断是否需要扩容,如果需要就调用grow方法扩容
1 | / |
我们可以看到grow方法将数组扩容为原数组的1.5倍,调用的是Arrays.copy
方法。
在jdk6及之前的版本中,采用的还不是右移的方法
1 | int newCapacity = (oldCapacity 3)/2 + 1; |
现在已经优化成右移了。
ArrayList如何实现元素的移除
我们移除元素的时候,有两种方法,一是指定下标,二是指定对象
1 | list.remove(3);//index |
下面先来分析第一种,也就是
- public E remove(int index)
源码中是如此实现的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
@param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
/
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;
}
对于数组的元素删除算法我们应该很熟悉,删除一个数组元素,我们需要将这个元素后面的元素全部向前移动,并将size减1.
我们看到源码中,首先检查下标是否在可用范围内。然后调用System.arrayCopy方法将右边的数组向左移动,并且将size减一,并置为null。
- public boolean remove(Object o)
源码中实现如下:
1 | / |
我们可以看到,这个remove方法会移除数组中第一个符合的给定对象,如果不存在就什么也不做,如果存在多个只移除第一个。
fastRemove方法如下
1 | /* |
可以理解为简化版的remove(index)方法。
ArrayList小结
ArrayList是List接口的一个可变大小的数组的实现
ArrayList的内部是使用一个Object对象数组来存储元素的
初始化ArrayList的时候,可以指定初始化容量的大小,如果不指定,就会使用默认大小,为10
当添加一个新元素的时候,首先会检查容量是否足够添加这个元素,如果够就直接添加,如果不够就进行扩容,扩容为原数组容量的1.5倍
当删除一个元素的时候,会将数组右边的元素全部左移