参考文章:【https://www.cnblogs.com/xrq730/p/4989451.html】
前言:
在Java中从我们一开始接触Java开始就一直在和List、Map、set等集合打交道,在这一块个人认为是重中之重的一块知识点,正好有时间来学习一下,弥补一下知己匮乏的只是头脑,在开始前先来画一张草图,来明确一下学习的步骤:
一、ArrayList数据存储特点:
ArrayList是在我们日常开发中最常用的集合之一,在JDK文档中很清楚的介绍了ArrayList集合的特点总结下来有以下几个方面:
1、ArratList是有序的集合
2、ArratList允许重复的值存入
3、ArrayList集合允许存入的值为空
二、ArrayList底层实现原理:
依旧查看JDK文档,我们可以看出ArrayList集合底层的实现原理是基于数组,通过动态的改变数组容量的大小来实现的,接着看一下底层源码一些方法的实现
1、添加方法(add)
我们看一下添加的操作。ArrayList支持四种添加方式,这里只讨论前两种:
1.1、按照顺序添加:
1.2、按照下标添加:
1.3、添加一个Collection集合到ArrayList中:(注:这里不做讨论)
1.4、按照下标,添加一个Collection集合到ArrayList中:(注:这里不做讨论)
按照顺序添加:
我们先来看一下在我们初始化一个集合时做了哪些事情:解释一下这段源码截图在我们new一个ArrayList集合时就如下(第三张图),elementData是ArrayList定义的一个数组变量(第一张图),而DEFAULTCAPACITY_EMPTY_ELEMENTDATA则是ArrayList类中的一个数组常量如(第二张图),实例化时会将定义好的数组数组赋值给elementData,这样就完成两个一个空的数组ArrayList初始化:
在这里先来看一下add方法,当我们执行一个向ArrayList集合添加元素时,首先会进入到线面代码,在下面带面中size是一个随着我们添加元素而递增的全局变量,目的是为了用来判断添加一个元素时是否需要执行扩容操作,而elementData这个数组则是在new的时候就被初始化好了
ensureCapacityInternal 方法DEFAULT_CAPACITY是定义的一个大小是10的int常量,第一行判断两个数组内存地址是否相同,如果相同则取DEFAULT_CAPACITY和变化后的size作比较,获取两者之间的最大值后进入ensureExplicitCapacity方法
ensureExplicitCapacity中很简单只是判断了一下,上一步获取的minCapacity是否大于elementData数组的长度,如果超过则需要进行扩容,grow方法里面则是扩容的具体代码
growf方法内:
首先获取了原数组的长度,接着根据原数组长度算出了需要扩容的大小,看到扩容的时候把原数组的大小加上二分之一作为扩容后新数组的容量。可能有些人要问为什么?我们可以想:
1.1、如果一次性扩容扩得太大,必然造成内存空间的浪费
1.2、如果一次性扩容扩得不够,那么下一次扩容的操作必然比较快地会到来,这会降低程序运行效率,要知道扩容还是比较耗费性能的一个操作
所以扩容扩多少,是JDK开发人员在时间、空间上做的一个权衡,提供出来的一个比较合理的数值。最后调用到的是Arrays的copyOf方法,将元素组里面的内容复制到新的数组里面去,
copyOf方法中的实现,在这里就不赘述Arrays的copyOf方法了,有兴趣可以私下看一看。
按照下标添加:
第一步:rangeCheckForAdd方法做了一下下标的效验,效验不通过会直接跑出空指针异常,ensureCapacityInternal方法则是和顺序添加的ensureCapacityInternal一样,判断是否需要扩若等:
第二步:把要添加的元素该下标之后的元素,利用System.arraycopy方法整体向后移动一个位置:
第三步:将该下标的的元素赋值为我们需要添加的元素与角标对应起来:
2、删除元素(remove)
接着我们看一下删除的操作。ArrayList支持三种删除方式,这里只讨论前两种:
2.1、按照下标删除:
2.2、按照元素删除,这会删除ArrayList中与指定要删除的元素匹配的第一个元素:
2.3、按照一个Collection集合移除的方式(注:这里不讨论这种方式)
按照下标移除的方式(注:返回值是我们移除的对象):
第一步:按照下标删除,在执行remove方法是第一步rangeCheck方法验证了移除元素的角标是否合法,不合法会抛出控制值异常:
第二部:modCount是一个全局变量代表的是对集合的操作次数,不管是添加还是删除每次操作都会累加:
第三步:则是获取该角标对应的元素则是获取该下标对应的元素:
第四步:计算出要移除元素的下一个元素位置,如果大于零,把指定元素后面位置的所有元素,利用System.arraycopy方法整体向前移动一个位置,最后一个位置的元素指定为null,如果不大于零直接赋值为null,让GC回收
按照元素删除的方式(注:返回值是一个boolean类型):
第一步:判断移除的元素是否是null,由于ArryList集合是允许空值存在的,所以分为了是null和不是null两种情况,之后后遍历当前集合并且判断集合中是否存在要移除的元素:
第二部:如果移除的元素存在,执行fastRemove方法,fastRemove和上面的removes所做的事情几乎一模一样,这里就不多说了。
三、ArrayList的优缺点:
优点:
通过上面的源码分析,我们可以大致看得出来ArrayList的优点如下:
1、ArrayList底层以数组实现,它实现了RandomAccess接口,RandomAccess接口提供了随机访问的特点,因此查找数据非常快:
2、ArrayList在顺序添加是,源码中可以看到直接是向数组中添加一个元素,不存在数组的复制操作,非常方便:
缺点:
1、删除元素时,涉及到数组的复制,如果要复制的元素很多,数组的复制将会消耗大量的资源:
2、插入元素的时候,涉及到数组的复制,如果要复制的元素很多,数组的复制将会消耗大量的资源:
总结:
ArrayList比较适合顺序添加、随机访问的场景:
四、ArrayList是否线程安全:
答案是否定的,Java JDK中很明确的写明了,ArrayList是一个非线程安全的集合,如果我们想创建一个线程安全的集合,这里提供两种方式:
1、使用Vector集合,Vector集合底层也是采用动态数组实现的,而它的方法都加了synchronized关键字,所以是线程安全的,如下图:
2、利用Collections类中的synchronizedList方法可以让ArryList变成一个线程安全的数组: