1、ArrayList 非线程安全,当定义成员变量时需考虑高并发时的线程安全问题。
数据结构:Object数组,存储在连续的内存空间上。
性能问题:
查询:由于数组在内存上的存储空间是连续的,因此查询性能高,可以通过角标索引直接定位到元素,时间复杂度O(1);
但是新增和删除性能不高,为什么呢?
删除:假设删除了元素3,那么删除后后边的元素都需要左移,时间复杂度O(n);
1 | 2 | 3 | 4 | 5 | 6 | 7 |
添加:如果数组内空间不足,插入元素时会触发ArrayList的扩容机制,扩容过程如下:
假如数组所在的内存后边没有连续的空间,则会申请新的可用的连续空间,将原数组复制到新的内存空间,插入新的元素,将原数组的内存空间置为null,交给GC回收;
1 | 2 | 3 | 已占用 | 已占用 | 已占用 | 已占用 | ||||||||
已占用 | 1 | 2 | 3 | 4 |
因此,ArrayList的查询性能高,但增、删性能不高。
2、LinkedList 非线程安全,当定义成员变量时也需考虑高并发时的线程安全问题。
数据结构:双向链表结构,存储在非连续的内存空间上。
性能问题:
查询:由于数组在内存上的存储空间是不连续的,不能通过索引定位,需遍历数组内所有元素,因此查询性不能高,时间复杂度O(n);
但是新增和删除性能高,为什么呢?
这就很明显了,因为数组在内存上是不连续的,删除元素后不需要移动其他元素,新增元素时随便在内存上找个可用空间存储就可以,不用考虑连续空间不足,申请新的空间,复制数组,删除旧数组等问题,因此性能比ArrayList高;
因此,LinkedList的查询性能不高,但增、删性能高。
3、CopyOnWriteArrayList 线程安全,可以应对高并发时的线程安全问题。
数据结构:Object数组,存储在连续的内存空间上。
性能问题:
查询:查询时不加锁,性能和ArrayList的查询性能差不多。
增、删:拥有和ArrayList一样的扩容机制,但是在增删操作时会添加锁机制,先将原数组复制一份加上锁,对复制的数据进行操作,操作后再赋值给原来的数组,因此是线程安全的。
问题:由于读写分离,可能会造成数据不一致(即可能读取不到最新的数据)
应用场景:在高并发的情况下,成员变量的定义需考虑使用CopyOnWriteArrayList;
若不需要考虑高并发,
读多写少:使用ArrayList
读少写多:使用linkedList