博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
掌握这些,ArrayList就不用再学了(下)
阅读量:4131 次
发布时间:2019-05-25

本文共 6338 字,大约阅读时间需要 21 分钟。

说这个之前,你先得搞清楚这个 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 来说,重点就是分析它的无参构造函数的执行,经过分析,我们知道了它有个数组拷贝的操作,这块是会影响到它的一些操作的时间复杂度的,关于这点,就留给大家取思考吧!

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

转载地址:http://tgyvi.baihongyu.com/

你可能感兴趣的文章
CSS3中的变形与动画(下)
查看>>
CSS3 布局样式
查看>>
CSS3 Media Queries 与Responsive 设计
查看>>
CSS3的flexbox布局
查看>>
前端面试题系列
查看>>
HTML优化技巧
查看>>
Javascript模块化编程
查看>>
Bootstrap(五) 导航条、分页导航
查看>>
Bootstrap(六) 其它内置组件
查看>>
HTML DOM querySelector() 方法
查看>>
js 事件冒泡和事件捕获的区别
查看>>
Web前端性能优化(一)减少Http请求
查看>>
Web前端性能优化(二)使用内容分发网络
查看>>
Web前端性能优化(四)压缩组件
查看>>
Web前端性能优化(五)网站样式和脚本
查看>>
Web前端性能优化(六)减少DNS查找、避免重定向
查看>>
Web前端性能优化(七)精简JS 移除重复脚本
查看>>
Web前端性能优化(八)配置ETag
查看>>
Web前端性能优化(九)图像和Cookie优化
查看>>
jQuery-DOM节点的创建
查看>>