Java List 讲解
List 讲解
List是Java里边的一个接口,常用的实现类有ArrayList和LinkedList,在开发中用的最多的是ArrayList。
ArrayList
ArrayList的底层数据结构是数组。
Java本身就有数组了,为什么还要用ArrayList呢?
原生数组有一个特点:使用它的时候必须要为它创建大小,而ArrayList不用,ArrayList实现了动态扩容。
在日常开发中,往往我们是不知道要给数组分配多大数量的。
ArrayList的动态扩容
当我们new ArrayList()的时候,默认会有一个空Object数组,大小为0;
当我们第一次add添加数据的时候,会给这个数组初始化一个大小,默认是10;
使用ArrayList在每一次add的时候,它都会先去计算这个数组够不够空间;
如果空间是够的,直接尾部追加。不够,则需扩容;
源码里有个grow方法,每一次扩原来的1.5倍。空间扩容完成后,会调用arraycopy来对数组进行拷贝。
日常开发过程中为什么用ArrayList比较多?
是由底层的数据结构决定的,在日常开发中,遍历的需求比增删要多,即便是增删也是往往在List的尾部添加就OK了。像在尾部添加元素,ArrayList的时间复杂度也就O(1)。另外,ArrayList的增删底层调用的copyOf()被优化过,现代CPU对内存可以块操作,ArrayList的增删一点不会比LinkedList慢。
Vector
底层数据结构是数组,一般现在很少用了。
相对于ArrayList它是线程安全的,扩容时直接扩容两倍。
CopyOnWriteArrayList
CopyOnWriteArrayList是一个线程安全的List,底层是通过复制数组的方式来实现的。
add方法
首先会加lock锁,然后会复制出一个新的数组,往新的数组里边add真正的元素,最后把array的指向改变为新的数组。
get方法和size方法
只是获取array所指向的数组的元素或者大小。
CopyOnWriteArrayList的缺点
-
耗费内存,每次add都会复制一个数组出来。
-
只能保证数据的最终一致性,不能保证数据实时一致性。
假设两个线程,线程A去读取CopyOnWriteArrayList的数据,还没有读完,线程B把这个List给清空了,线程A此时还是可以把剩余的数据给读出来。