Java容器之ArrayList
一. ArrayList的类关系图
二. 关于ArrayList,我们从以下几个方面进行分析
- 有无顺序
- 长度是否可变
- 元素是否可重复
- 底层实现
- 线程安全性
- 优点,特性
三. 分析过程
- 创建ArrayList,赋值,然后断点查看执行过程
从这张图片中我们可以看到刚刚创建的ArrayList是Object类型的数组.并且长度为0
添加数据之后,数组的长度变了,类型没有变还是Object类型,但是长度为10,而且我们要注意这个数组的标识也由@526变成了@528
当我们一直添加数据的时候,在超出数组原有长度之后,数组的长度和标识在一次发生了改变.
再此处我们可以得出的结论是:
ArrayList在底层是使用数组进行存储的,第一次声明ArrayList的时候,数组是没有长度的,当第一次赋值的时候,会重新创建一个数组,当数组的长度不不够时,会再次重新创建一个数组.
- 接下来我们看源码中的add()方法
在add()方法中出现了一个ensureCapacityInternal()方法,传入的参数是size + 1;
还出现了一个elementData[size++] = e; 将当前add()方法传入的值赋值给一个名为elementData的数组.
这个数组就是我们在断点中看见的存储数据的数组,size 是 ArrayList的大小(它包含的元素数)
下面我们看下ensureCapacityInternal()方法又干了什么.
在add()方法中我们可以看到传给ensureCapacityInternal()的方法是size+1也就是当前数组中的元素数加上1,就是如果添加完之后的数组至少应该有这么多位置,不然的话一定会报越界异常.
在看calculateCapacity(elementData, minCapacity)方法.
这个方法先判断当前的数组是不是刚初始化的数组,如果是行初识话的数组就返回个10,如果不是行初始化的数组就将这个传入的参数返回,也就是添加元素之后的所有元素总数.到现在我们终于找到一个返回的了,我们可以接着看ensureCapacityInternal()方法中调用的ensureExplicitCapacity()方法,这个方法传入的要么是10,要么就是当前添加成功之后的元素总数.
这里的逻辑也非常简单,就是判断一下添加新元素之后的元素总数是否大于数组的总长度.如果大于的话会继续调用grow()方法,传入添加之后的元素总长度,接着往下看.
前面我们说如果添加新元素之后的元素总数大于当前数组的总长度.就会调用grow()方法,传入添加之后的元素总长度.也就是说当前数组还能不能进行继续添加的操作,不能的话就调用grow()方法,这个方法实际上就是ArrayList动态扩容的关键方法.它会将当前数组长度的1.5倍做为新的数组的长度.这个方法中的两个if是判断新的数组长度是否合法,如果不合法的话就会重新复制,最后一段代码就是将原来数据copy到新的数组中.
到此为止add()的方法我们就看完了.这里能总结出来什么呢,就是ArrayList的动态扩容虽然给我们增加了很大的方便,但是在扩容的过程中需要判断的地方也很多,比较耗费资源.
- 源码就先看这些吧,我们接着说下ArrayList的删除.实际上他在删除的时候更复杂,需要将你删除元素之后的所有数据进行向前移动,这个有兴趣的可以自己跟着源码走一遍.
- 下面我们看下是否线程安全,发现了一篇写的不错的文章,这里我就不继续啰嗦了,请大家参考:
Java中不同容器类是否线程安全
总结
- ArrayList是有序的,上边虽然没有演示,但是我相信大家都用过 list.get(0) , 其中这个 0 实际上就是元素在数组中的位置,就是数组中的下标
- ArrayList长度可变,这个上边我们一起看了源码,除非特出情况,不然是增加原有长度的1.5倍,而且刚声明出来如果不指定长度的话是没有长度的,只有在赋值的时候才会创建一个默认长度为10的数组.
- 元素当然是可重复的了,这个没什么好说的吧,我加的都是香蕉.
- 底层实现实际上就是一个数组.ArrayList就是对数组进行了一系列的包装,
- 线程不安全,关于不安全的原因 Java中不同容器类是否线程安全 这篇文章说的已经很清楚了,如果还不清楚欢迎留言.
- 优点,特性,简单的说就是增删速度比较慢,但是查找速度快.但是如果不动态扩容的话,只是在末尾增减一个元素,倒是还能接受,但是如果向指定位置插入元素的话,这个就确实有点复杂了.
- 具体的应用场景,这个我感觉还是根据具体业务吧,关于ArrayList的特性和实现原理都清楚了,应用的话应该不是问题吧!