前言
ArrayList
是 List
接口的一个实现类,那么 ArrayList
的底层是如何实现的呢?让我们来一探究竟。
源码分析
属性
先来看看 ArrayList
中比较重要的两个属性:
1 | transient Object[] elementData; |
elementData
用来存储ArrayList
中的元素,其实ArrayList
的底层是用Object[]
数组来实现的。size
指的是的逻辑长度,就好像一个水杯,容量是 600 毫升,但杯中只有 200 毫升的水,ArrayList
容器就是水杯, 这个size
属性表示的就是水杯中水的容积。
构造方法
无参构造方法:
1 | public ArrayList() { |
很简单,就一行语句,elementData
在上面我们已经知道是什么了,那 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
又是什么呢?
1 | private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; |
原来只是一个空的 Object[]
。
指定容量的构造方法:
1 | public ArrayList(int initialCapacity) { |
也很简单,就是为 ArrayList
指定初始容量。这里主要对传入的容量做了有效性验证,当传入的参数小于 0 时,抛出异常。当参数等于 0 时,则用一个空的数组常量 EMPTY_ELEMENTDATA
来初始化。
用一个 Collection 对象进行构造的的构造方法:
1 | public ArrayList(Collection<? extends E> c) { |
首先将传入的参数转为对象数组赋值给 elementData
,如果传入的 Collection
参数的长度为 0,则就将空的常量数组对象 EMPTY_ELEMENTDATA
赋给了 elementData
。
常用方法
add
1 | public boolean add(E e) { |
调用了 ensureCapacityInternal
函数:
1 | private void ensureCapacityInternal(int minCapacity) { |
这里判断 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
表示如果如果为没有初始化容量时,用默认容量 DEFAULT_CAPACITY
也就是 10,来开启空间,也就是上面我们说的水杯的容量。
接下来看 ensureExplicitCapacity
方法:
1 | private void ensureExplicitCapacity(int minCapacity) { |
继续看 grow
方法:
1 | private void grow(int minCapacity) { |
其实这段代码也很简单,就是动态扩容 ArrayList 的大小,每次扩容都为当前容量的 1.5 倍,当然也进行了扩容过大或过小的限制。
其扩容原理就是创建一个容量为当前容量 1.5 倍的新数组,将旧数组的内容拷贝给新数组。
add(int index, E element)
1 | public void add(int index, E element) { |
看过了 add(E e)
的源码,这里也就很好理解了,先进行插入位置有效性判断,然后判断容量是否需要扩容,然后先将插入位置开始的所有元素向后移动一位,最后将需要插入的元素插入指定位置。
get
1 | public E get(int index) { |
ArrayList 的底层是用数组实现的,那么获取元素也就很方便了,判断获取位置合法后,直接用下标获取即可。
set
1 | public E set(int index, E element) { |
这个方法其实就是修改指定位置的元素,先拿到要覆盖的元素,然后将新元素赋值上去,最后返回覆盖的元素即可。
remove
1 | public E remove(int index) { |
先进行有效性判断,然后从要删除的元素开始的所有元素都向前拷贝一位,并将最后一位设值为 null
。
indexOf
1 | public int indexOf(Object o) { |
如果要找的内容 null
,则依次用 == 判断是否为要找的元素,如果不为 null
,则用 equals 来依次判断。
总结
根据上方的源码分析,我们可以得出 ArrayList
的一些特性:
ArrayList
底层数据结构是对象数组,如不指定长度,则初始容量为 10。- 底层的对象数组是在第一次添加元素的时候才进行初始化的。
- 每次扩容为原来容量的 1.5 倍,扩容原理是将原来的数据拷贝到新数组中。
所以我们在使用时要注意:
- 如知道大概要存多少个数据,最好指定初始化容量,这样可以提高程序性能。
ArrayList
查找元素很快,但删除元素和添加元素的效率相对较差。