List
首先说一下list的一些特点:list中的元素是有序并且有重复的!
以下内容请结合源码一起食用效果更佳!
ArrayList
底层是什么?
ArrayList的底层是
数组
。
初始化参数有哪些?
- 默认容量是
10
; - 当调用
new ArrayList()
,即调用空的构造函数
的时候是默认初始化一个空数组。然后往数组添加元素也即调用add(E)或add(int, E)方法的时候才初始化数组容量为默认值10; - 当调用
new ArrayList(int initialCapacity)
,即调用指定数组容量的构造函数
的时候是默认初始化一个指定大小的数组;
扩容机制是怎样的?
- 当添加新的元素的时候,如果
size+1
大于当前数组的容量,默认扩容为原数组容量的1.5倍,这个1.5倍的说法不是很严谨,源码中是int newCapacity = oldCapacity + (oldCapacity >> 1);
,也即原容量+原容量右移一位
,可以理解为原容量+原容量/2
(即对2取整的结果)。
是否线程安全?
ArrayList是非线程安全的!为什么?
- 当不会触发扩容的时候,新元素添加到数组中的操作是
elementData[size++] = e;
,这里size++操作就不是线程安全的,因为它不是一个原子操作,多线程情况下很容易出现并发的问题! - 当触发扩容的时候,可能会出现数组元素丢失的情况,具体是怎么个丢失法呢?假设数组容量是10,现在有两个线程同时执行到
grow()
方法,在最后一步执行数组拷贝以及重复赋值时,两个线程都执行了数组的拷贝,然后A线程先赋值elementData = Arrays.copyOf(elementData, newCapacity);
,接着完成了add方法,此时数组elementData[10]
这个位置就添加量新的元素,然后B线程再进行数组的赋值,此时就会将A线程添加的元素值给覆盖掉的情况!这个过程好好想想还是可以理解的!
拓展:
如果想要使用线程安全的ArrayList有没有呢?
可选方案有:Vector、Collections.synchronizedList()、CopyOnWriteArrayList
小妙招:
如果能确定ArrayList的初始容量,最好是使用new ArrayList(int initialCapacity)
这个构造函数,避免数组扩容带来额外的影响。
使用场景有哪些?
ArrayList的一个特性就是它的底层是基于数组的,数组是一种线性数据结构,在内存中的存储空间是连续的。
- 查询的效率高,因为空间连续,支持通过下标的随机访问;
- 增删改的效率可能不是很好,因为要重新拷贝数组,当数组的元素特别多的时候,这个效率可想而知!
所以说,对于只是随机访问的,使用ArrayList是个不错的选择,效率高还安全!如果要执行一些增删改的操作,可以考虑使用LinkedList!
下一篇就说一下LinkedList!
彩蛋
多次执行下面的代码,看看有什么发现?
public class ArrayListUnsafeTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>(10);
for (int i = 0; i < 5; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}