书接上文:掌握这些,ArrayList就不用再学了(上)
minCapacity 是个啥(重要)
说这个之前,你先得搞清楚这个 minCapacity 是啥,它现在其实就是底层数组将要添加的第几个元素,看看上一步
ensureCapacityInternal(size + 1);
这里 size+1 了,所以现在 minCapacity 相当于是 1,也就是说将要向底层数组添加第一个元素,这一点的理解很重要,所以从 minCapacity 的字面意思理解也就是“最小容量”,我现在将要添加第一个元素,那你至少给我保证底层数组有一个空位置,不然怎么放数据嘞。
重点来了,因为第一次添加,底层数组没有一个位置,所以需要先确定下来一共有多少个位置,就是献给数组一个默认的长度
于是这里给重新赋值了(只有第一次添加数据才会执行这步,这一步就是为了指定默认数组长度的,指定一次就 ok 了)
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
这怎么赋值的应该知道嘛,哪个大取哪个,那我们要看看 DEFAULT_CAPACITY 是多少了
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
ok,明白了,这就是 ArrayList 的底层数组 elementData 初始化容量啊,是 10,记住了哦,那么现在 minCapacity 就是 10 了,我们再接着看下面的代码,也即是:
ensureExplicitCapacity(minCapacity);
进去看看吧:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
也比较简单,现在底层数组长度肯定还不到 10 啊,所以我们继续看 grow 方法
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);
}
咋一看,判断不少啊,干啥的都是,突然看到了 Arrays.copyOf,知道这是啥吧,上面可是特意讲过的,原来这是要进行数组拷贝啊,那这个 elementData 就是原来的数组,newCapacity 就是新数组的容量
我们一步步来看代码,首先是
int oldCapacity = elementData.length;
得到原来数组的容量,接着下一步:
int newCapacity = oldCapacity + (oldCapacity >> 1);
这是得到新容量的啊,不过后面的这个 oldCapacity >> 1 有点看不懂啊,其实这 oldCapacity >> 1 就相当于 oldCapacity /2,这是移位运算,感兴趣的自行搜索学习。
知道了,也就是扩容为原来的 1.5 倍,接下来这一步:
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
因为目前数组长度为 0,所以这个新的容量也是 0,而 minCapacity 则是 10,所以会执行方法体内的赋值操作,也就是现在的新容量成了 10。
接着这句代码就知道怎么回事了
elementData = Arrays.copyOf(elementData, newCapacity);
不知道你发现没,这里饶了一大圈,就是为了创建一个默认长度为 10 的底层数组。
底层数组长度要看 ensureCapacityInternal
ensureCapacityInternal 这个方法就像个守卫,时刻监视着数组容量,然后过来一个数值,也就是说要向数组添加第几个数据,那 ensureCapacityInternal 需要思考思考了,思考啥呢?当然是看底层数组有没有这么大容量啊,比如你要添加第 11 个元素了,那底层数组长度最少也得是 11 啊,不然添加不了啊,看它是怎么把关的
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
记住了这段代码
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
它的存在就是为了一开始创建默认长度为 10 的数组的,当添加了一个数据之后就不会再执行这个方法,所以重难点是这个方法:
ensureExplicitCapacity(minCapacity);
也就是真正的把关在这里,看它的实现:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
怎么样,看明白了吧,比如你要添加第 11 个元素,可是我的底层数组长度只有 10,不够啊,然后执行 grow 方法,干嘛执行这个方法,它其实就是用来扩容的,不信你再看看它的实现,上面已经分析过了,这里就不说了。
假如你要添加第二个元素,这里底层数组长度为 10,就不需要执行 grow 方法,因为根本不需要扩容啊,所以这一步实际啥也没做(有个计数操作):然后就直接在相应位置赋值了。
小结
所以这里很重要的一点就是理解这一步传入的值的意义:
ensureCapacityInternal(size + 1);
简单点就是要向底层数组中添加第几个元素了,然后开始进行一系列的判断,容量够的话直接返回,直接赋值,不够的话就执行 grow 方法开始扩容。
主要判断就在这里:
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);
}
这里需要注意这段代码
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
这段代码只有在第一次添加数据的时候才会执行,也是为创建默认长度为 10 的数组做准备的,因为这个时候原本数组长度为 0,扩容后也是 0,而 minCapacity 为默认值 10,所以会执行这段代码。
但是一旦添加数据之后,底层数组默认就是 10 了,再加上之前的判断,这里的 newCapacity 一定会比 minCapacity 大,这个点需要了解。
看看 ArrayList 的有参构造函数
我们上面着重分析了下 ArrayList 的无参构造函数,下面再来看看它的有参构造函数:
ArrayList arrayList1 = new ArrayList(100);
看看这个构造函数张啥样?
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
我去,这不就是直接创建嘛,然后还有这个:
else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
}
我们看看这个 EMPTY_ELEMENTDATA
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
ok,现在你可以回答我们开篇提的那个问题了吧。
我们以上对 ArrayList 的源码有了一定的认识之后,我们再来看看 ArrayList 的读取,替换和删除操作时怎样的?
ArrayList 的其他操作
经过上面的分析,我相信你对 ArrayList 的其他诸如读取删除等操作也没啥问题,一起来看下。
读取操作
看源码
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
代码很简单,rangeCheck 就是用来判断数组是否越界的,然后直接返回下标对应的值。
删除操作
看源码
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 E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
其实就是把原来的值覆盖,没啥问题吧 😄
和 vector 很像
这个想必大家都知道,ArrayList 和 vector 是很像的,前者是线程不安全,后者是线程安全,我们看一下 vector 一段源码就明白了
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
没错,区别就是这么明显!
总结
到这里,我们基本上把 ArrayList 的相关重点都过了一遍,对于 ArrayList 来说,重点就是分析它的无参构造函数的执行,经过分析,我们知道了它有个数组拷贝的操作,这块是会影响到它的一些操作的时间复杂度的,关于这点,就留给大家取思考吧!
好了,今天就到这里,大家如果有什么问题,欢迎留言,一起交流学习!
感谢阅读
大家好,我是 ithuangqing,一路走来积累了不少的学习经验和方法,而且收集了大量的精品学习资源,现在维护了一个公众号【编码之外】,寓意就是在编码之外也要不停的学习,主要分享 java 技术相关的原创文章,现在主要在写数据结构与算法,计算机基础,线程和并发以及虚拟机这块的原创,另外针对小白还在连载一套《小白的 java 自学课》,力求通俗易懂,由浅入深。同时我也是个工具控,经常分享一些高效率的黑科技工具及网站。
对了,公众号还分享了很多我的学习心得,可以一起探讨探讨!
关注公众号,后台回复“庆哥”,2019 最新 java 自学资源立马送上!更多原创精彩尽在【编码之外】
本文分享自微信公众号 - 编码之外(ithuangqing)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/4315339/blog/4433473