ArrayList是大家用的再熟悉不过的集合了, 而此集合设计之初也是为了高效率, 并未考虑多线程场景下, 所以也就有了多线程下的CopyOnWriteArray List这一集合
回一下ArrayList
f ail- f ast 快速失败机制, 一个线程A在用迭代器遍历集合时, 另个线程B
这 时 对 集 合 修 改 会 导 致 A 快 速 失 败 , 抛 出 Co nc urre nt Mo d i f i c a t i o n E x c e p t i o n 异常。在java. util中的集合类都是快速失败的
f ail- safe 安全失败机制, 遍历时不在原集合上, 而是先复制一个集合, 在拷贝的集合上进行遍历。在java.util.concurrent包下的容器类是安全失败的, 建议在并发环境下使用这个包下的集合类
ArrayList定义:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
}
ArrayList简介:
ArrayList是实现List接口的可变数组,并允许null在内的重复元素
底层数组实现,扩容时将老数组元素拷贝到新数组中,每次扩容是其容量的1.5 倍,操作代价高
采用了Fail-Fast机制,面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险
ArrayList是线程不安全的,所以在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList
重点关注问题:
Ar r ay Lis t的默认初始化大小是 10 ( 在新建的时候还是空, 只有当放入第一个元素的时候才会变成10 ) , 若知道Ar r ay Lis t的大致容量, 可以在初始化的时候指定大小, 可以在适当程度减少扩容的性能消耗( 看下一个问题解析) 。
至于为何是10
据说是因为s un的程序员对一系列广泛使用的程序代码进行了调研, 结果就是10 这个长度的数组是最常用的最有效率的。也有说就是随便起的一个数字, 8 个12 个都没什么区别, 只是因为10 这个数组比较的圆满而已。
ArrayList的扩容机制
当添加元素的时候数组是空的, 则直接给一个10 长度的数组。当需要长度的数组大于现在长度的数组的时候, 通过新= 旧+ 旧> > 1 ( 即新 = 1 . 5倍的旧) 来扩容, 当扩容的大小还是不够需要的长度的时候, 则将数组大小直接置为需要的长度( 这一点切记! ) 。
ArrayList 特点访问速度块, 为什么? 插入删除一定慢吗? 适合做队列吗?
Ar r ay Lis t从结构上来看属于数组, 也就是内存中的一块连续空间, 当我们get( index) 时, 可以直接根据数组的首地址和偏移量计算出我们想要元素的位置, 我们可以直接访问该地址的元素, 所以查询速度是O ( 1 ) 级别的。
我们平时会说Ar r ayList 插入删除这种操作慢, 查询速度快, 其实也不是绝对的。
当数组很大时, 插入删除的位置决定速度的快慢, 假设数组当前大小是一千万, 我们在数组的index 为0 的位置插入或者删除一个元素, 需要移动后面所有的元素, 消耗是很大的。但是如果在数组末端index操作, 这样只会移动少量元素, 速度还是挺快的( 插入时如果在加上数组扩容, 会更消耗内
存) 。
个人觉得不太适合做队列, 基于上面的分析, 队列会涉及到大量的增加和删除( 也就是移位操作) , 在Ar r ay Lis t中效率还是不高。
ArrayList 底层实现就是数组, 访问速度本身就很快, 为何还要实现 RandomAccess ?
R andom Ac c es s 是一个空的接口, 空接口一般只是作为一个 标识, 如S er ializ able接口.。
JD K 文档说明R andom Ac c es s 是一个标记接口( Mar ker inter fac e) , 被用于Lis t接口的实现类, 表明这个实现类支持快速随机访问功能( 如Ar r ay Lis t ) . 当程序在遍历这中Lis t的实现类时, 可以根据这个标识来选择更高效的遍历方式。
优缺点
上面说的查询速度快自然就是其中的优点, 除此之外, 还可以存储相同的元素
底层数据结构属于数组, 和数组的优缺点大同小异, 数组属于线性表, 更适合于那种在末尾经常添加数据的场景, 而对于在整个l i s t 中各个位置随机添加元素比较多的情况则不太合适
因为可能会涉及到很多元素位置的移动
A r r ay L i s t 还有一个比较大的缺点就是不适应于多线程环境