说起ArrayList大家一定都不陌生,下面就给大家讲一讲他的使用方式和实现原理。
一、用途
ArrayList其实就是用来装载数据的数组列表。如果我们要用来装载基本类型的数据比如int,long,boolean等的时候我们只能存储他们对应的包装类。它的底层实现是数组
二、源码解读
ArrayList可以通过构造方法在初始化的时候指定底层数组的大小。这里值得注意的是,这个容量不是我们在使用时体现出来的list.size(),而是它底层数组的默认值。比如说我新建一个list,啥都不干,他的容量就是0而且size也是0。在我对这个list执行一次add操作后,他的对应的size会变成1,而容量就会变成10。在我对这个数组进行11次add操作后,他的容量会发生改变。怎么改变呢?下文会提到。
从下图中可以看出,如果你在一开始指定了其容量为10,即使不进行add操作,其容量也会是10。
下图为ArrayList的构造方法。可以直观看出,如果传入了大于0的参数,则会把底层数组长度设置为入参。
大家都知道,ArrayList可以存放任意数量的对象,长度不受限制。但是其底层实现却是一个数组。那么具体是怎么实现的呢?实际上,他是通过一种数组扩容的方式去完成的。源码如下图。
就比如我们现在有一个长度为10的数组,现在我们要新增一个元素,发现已经满了,那ArrayList会怎么做呢?
首先,他会重新定义一个长度为10 + 10>>1(15)的数组。
然后会把原数组的数据原封不动的复制到新的数组中,这个时候再把指向原数组的地址换到新的数组里。
大家一定想问,为什么ArrayList的默认数组大小为10呢?据说是因为sun的程序员对一系列广泛使用的程序代码进行了调研,结果就是10这个长度的数组是最常用的最有效率的。也有说就是随便起的一个数字,8个12个都没什么区别,只是因为10这个数组比较的圆满而已。
下面讲讲ArrayList的效率问题。大家一定都知道,如果要频繁进行元素的增加与删除,ArrayList的效率是比较差的,一般会采用LinkedList;而ArrayList的查询和访问效率是比LinkedList要好的。
从源码上可以分析:
相信大家都已经注意到了这个方法:System.Arraycopy。这说明了什么呢?实际上ArrayList的增加和删除都是采用覆盖的方法去进行的。示意图如下。
比如有下面这样一个数组我需要在index 5的位置去新增一个元素A
那从代码里面我们可以看到,他复制了一个数组,是从index 5的位置开始的,然后把它放在了index 5+1的位置
给我们要新增的元素腾出了位置,然后在index的位置放入元素A就完成了新增的操作了
至于为啥说他效率低,我想我不说你也应该知道了,我这只是在一个这么小的List里面操作,要是我去一个几百几千几万大小的List新增一个元素,那就需要后面所有的元素都复制,然后如果再涉及到扩容啥的就更慢了不是嘛。
删除操作其实是同样的道理的。继续打个比方,我们现在要删除下面这个数组中的index5这个位置
那代码他就复制一个index5+1开始到最后的数组,然后把它放到index开始的位置
index5的位置就成功被”删除“了其实就是被覆盖了,给了你被删除的感觉。
同理他的效率也低,因为数组如果很大的话,一样需要复制和移动的位置就大了。
经过这样的一段分析,相信大家都已经对ArrayList的增删有了深刻的了解。其实如果我们在队列的末尾进行操作的话,它的影响比较小,因此速度还是比较快的。
三、线程安全
ArrayList的线程是不安全的。如果把所有的方法都加上synchronized,我们可以得到Vector,这个就是线程安全的。
四、与LinkedList的比较的优势
论遍历ArrayList要比LinkedList快得多,ArrayList遍历最大的优势在于内存的连续性,CPU的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。
至于劣势之前已经提到了,ArrayList的增删效率会低一些。