掌握这些,ArrayList就不用再学了(下)

coding

书接上文:掌握这些,ArrayList就不用再学了(上)

minCapacity 是个啥(重要)

说这个之前,你先得搞清楚这个 minCapacity 是啥,它现在其实就是底层数组将要添加的第几个元素,看看上一步

 ensureCapacityInternal(size + 1);

这里 size+1 了,所以现在 minCapacity 相当于是 1,也就是说将要向底层数组添加第一个元素,这一点的理解很重要,所以从 minCapacity 的字面意思理解也就是“最小容量”,我现在将要添加第一个元素,那你至少给我保证底层数组有一个空位置,不然怎么放数据嘞。

重点来了,因为第一次添加,底层数组没有一个位置,所以需要先确定下来一共有多少个位置,就是献给数组一个默认的长度

于是这里给重新赋值了(只有第一次添加数据才会执行这步,这一步就是为了指定默认数组长度的,指定一次就 ok 了)

minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

这怎么赋值的应该知道嘛,哪个大取哪个,那我们要看看 DEFAULT_CAPACITY 是多少了

/**
* Default initial capacity.
*/

privatestaticfinalint DEFAULT_CAPACITY = 10;

ok,明白了,这就是 ArrayList 的底层数组 elementData 初始化容量啊,是 10,记住了哦,那么现在 minCapacity 就是 10 了,我们再接着看下面的代码,也即是:

ensureExplicitCapacity(minCapacity);

进去看看吧:

privatevoidensureExplicitCapacity(int minCapacity){
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

也比较简单,现在底层数组长度肯定还不到 10 啊,所以我们继续看 grow 方法

privatevoidgrow(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 啊,不然添加不了啊,看它是怎么把关的

privatevoidensureCapacityInternal(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);

也就是真正的把关在这里,看它的实现:

privatevoidensureExplicitCapacity(int minCapacity){
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

怎么样,看明白了吧,比如你要添加第 11 个元素,可是我的底层数组长度只有 10,不够啊,然后执行 grow 方法,干嘛执行这个方法,它其实就是用来扩容的,不信你再看看它的实现,上面已经分析过了,这里就不说了。

假如你要添加第二个元素,这里底层数组长度为 10,就不需要执行 grow 方法,因为根本不需要扩容啊,所以这一步实际啥也没做(有个计数操作):然后就直接在相应位置赋值了。

小结

所以这里很重要的一点就是理解这一步传入的值的意义:

ensureCapacityInternal(size + 1);

简单点就是要向底层数组中添加第几个元素了,然后开始进行一系列的判断,容量够的话直接返回,直接赋值,不够的话就执行 grow 方法开始扩容。

主要判断就在这里:

privatevoidensureExplicitCapacity(int minCapacity){
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

具体的扩容是这里

privatevoidgrow(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);

看看这个构造函数张啥样?

publicArrayList(int initialCapacity){
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} elseif (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
thrownew IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}

我去,这不就是直接创建嘛,然后还有这个:

elseif (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
}

我们看看这个 EMPTY_ELEMENTDATA

privatestaticfinal Object[] EMPTY_ELEMENTDATA = {};
privatestaticfinal 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 一段源码就明白了

publicsynchronizedbooleanadd(E e){
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
returntrue;
}

没错,区别就是这么明显!

总结

到这里,我们基本上把 ArrayList 的相关重点都过了一遍,对于 ArrayList 来说,重点就是分析它的无参构造函数的执行,经过分析,我们知道了它有个数组拷贝的操作,这块是会影响到它的一些操作的时间复杂度的,关于这点,就留给大家取思考吧!

好了,今天就到这里,大家如果有什么问题,欢迎留言,一起交流学习!

感谢阅读

大家好,我是 ithuangqing,一路走来积累了不少的学习经验和方法,而且收集了大量的精品学习资源,现在维护了一个公众号【编码之外】,寓意就是在编码之外也要不停的学习,主要分享 java 技术相关的原创文章,现在主要在写数据结构与算法,计算机基础,线程和并发以及虚拟机这块的原创,另外针对小白还在连载一套《小白的 java 自学课》,力求通俗易懂,由浅入深。同时我也是个工具控,经常分享一些高效率的黑科技工具及网站。

对了,公众号还分享了很多我的学习心得,可以一起探讨探讨!

关注公众号,后台回复“庆哥”,2019 最新 java 自学资源立马送上!更多原创精彩尽在【编码之外】


好文章,我在看❤️

本文分享自微信公众号 - 编码之外(ithuangqing)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

以上是 掌握这些,ArrayList就不用再学了(下) 的全部内容, 来源链接: utcz.com/z/510153.html

回到顶部