前言
ArrayList我们总是常用,但是其底层具体怎么实现呢?我们今天就来看看
一.例子
我们以一段代码来展开分析
1 | /** |
在分析之前我们先来看看ArrayList的构造方法
二.构造方法分析
源码1 | /** |
这里有两个构造,一个是有参构造一个是无参构造
2.1我们先来看看有参构造
1 | public ArrayList(int initialCapacity) { |
当我们通过有参构造来new一个ArrayList时,传入参数initialCapacity,这就是一个初始化的参数,解毒代码我们发现,当我们传入的参数>0时,就执行这么一段代码
1 | this.elementData = new Object[initialCapacity]; |
那这个elementData是个什么呢?我们来看看
1 | transient Object[] elementData; |
这就是那个elementData,它是一个Object类型的数组,也就是说,我们的 ArrayList 是将数据保存在了这个 Object数组 中的,底层是使用这个Object数组来实现
也就是说我们传入的数值initialCapacity如果 >0 那么ArrayList底层就会帮我们初始化一个initialCapacity大小的Object数组。如果传入的数值为0,那么就会走下面的代码
1 | this.elementData = EMPTY_ELEMENTDATA; |
我们来看看这个 EMPTY_ELEMENTDATA 是啥?
1 | private static final Object[] EMPTY_ELEMENTDATA = {}; |
这就是一个空的Object数组,也就说如果我们传入的参数值为0,那么ArrayList底层就会帮我们实现一个空的Object数组。
2.2无参构造
1 | public ArrayList() { |
这个比较简单,我们来看看 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 这个就ok了.
1 | private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; |
这个DEFAULTCAPACITY_EMPTY_ELEMENTDATA跟我们前面分析的EMPTY_ELEMENTDATA这个是一样的,都是一个空的数组,至于为什么人家用两个我就不知道了,我们继续下面的分析。
三.add方法分析
我们在第一个add方法处加上断点,然后一步一步分析
开始debug
跟进add方法内部
跟进add方法
源码1 | public boolean add(E e) { |
我们发现其中还有一个 ensureCapacityInternal(size + 1); 方法,此时它有一个参数size+1,这个size是什么呢?我们来看看
1 | private int size; |
就是这样一个变量,这里告诉你,这个size就是当前 ArrayList 的size大小(注意:这个size跟我们之前看到的那个elementData数组的长度可不一样,这个size就是我们能在程序外部看到的ArrayList集合的大小),,因为此时是我们第一次添加元素,所以size为0。
所以这时 ensureCapacityInternal(size + 1) 的参数 size+1 = 1
这里解释一下为什么是size+1,因为咱们添加一个元素,是不是ArrayList的大小要加1,所以是size+1
跟进ensureCapacityInternal(size + 1)方法内部
源码1 | private void ensureCapacityInternal(int minCapacity) { |
分析:
此时,在这个函数中,它先判断 elementData跟DEFAULTCAPACITY_EMPTY_ELEMENTDATA是不是同一个引用(==是判断地址的在对象中和),而之前在分析无参构造时有这么一句代码
1 | this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; |
所以显然,这个判断成立,执行如下代码
1 | minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); |
此时这个代码就是选出 DEFAULT_CAPACITY 和 minCapacity的最大值,而这个 minCapacity 就是我们之前传的 size+1 = 1,所以这个minCapacity是1
这个minCapacity从英文角度去理解,就是当前容器(ArrayList)的最小容量,就是我这个容器不能比这个容量再小,再小就装不下了,所以在这里选最大值
我们来看看 DEFAULT_CAPACITY 这个是个啥?
1 | private static final int DEFAULT_CAPACITY = 10; |
在这里说一下,我们的ArrayList底层是使用那个Object数组来存放数据的,所以ArrayList给这个数组得初始化一个大小,之前在构造中只是给它引用地址并未初始化大小。
注意:这里这个Object数组的长度,并不是我们ArrayList的长度,我们的ArrayList的长度,是那个size字段并不是它,这一点千万别搞混
所以这里执行完后,minCapacity就会变为10,因为是找最大值啊,10比1(size+1)大,接下来执行下面这段代码
1 | ensureExplicitCapacity(minCapacity); |
### 跟进ensureExplicitCapacity(minCapacity)方法内部
源码
1 | private void ensureExplicitCapacity(int minCapacity) { |
这里这个modCount++;就不用管了,这个就是用来计数的,当你每次add的时候,我们来看下面的代码
1 | // overflow-conscious code |
如果 minCapacity 减去elementData.length(就是那个Object数组的长度)>0,那么执行grow(minCapacity);函数。此时minCapacity为10(经过前面的选最大值后变为了10)
所以此时会进入grow方法
跟进grow(minCapacity);方法内部
源码1 | private void grow(int minCapacity) { |
此时代码中有 oldCapacity ,这个代表的是原有的Object数组的长度,现在这个长度为0
newCapacity 代表的是新的Object数组的长度,就是扩容后的长度,通过 oldCapacity + (oldCapacity >> 1); 这句代码给它扩容,其实就是旧的长度加上旧的长度除以2之后的长度作为新的长度此时 elementData.length; 为0,所以 oldCapacity为0,所以算出的newCapacity也为0
所以会走下面这句代码
1 | if (newCapacity - minCapacity < 0) |
就是说扩容后的长度还没有我默认的长度10大呢,所以就将默认长度10赋值给新的长度newCapacity。
接下来走了
1 | elementData = Arrays.copyOf(elementData, newCapacity); |
这段代码,这段代码我就不进去看了,就是构造一个newCapacity大小的数组,然后将Object数组中原有的内容复制到新的数组中然后返回给elementData,此时elementData已经跟那个DEFAULTCAPACITY_EMPTY_ELEMENTDATA已经不一样了,已经不是指向同一个地址了,因为这个elementData指向了新的地址,因为Arrays.copyOf(elementData, newCapacity);给它返回了一个新的数组
最后分析
1 | public boolean add(E e) { |
最终跳回了这里,不过ensureCapacityInternal(size + 1);这个方法我们已经分析完毕,就是去判断我们添加的元素有没有超过内置Object的数组长度,如果超过,我们就去给它扩容,然后将原有的Object数组复制到新的扩容后的数组中。
所以此时elementData已经指向了那个扩容后的数组的地址了,然后执行elementData[size++] = e;将传入的参数添加到数组中
四.之后分析
在我们之后的九次add中,既不会再去扩容了,也就是说不会再走grow(minCapacity);方法了