ArrayList 简介
ArrayList 实现了List接口,是一个顺序(插入顺序)容器,允许存放null元素,底层通过数组实现的,可将其理解一个动态数组(容量自动增长)。ArrayList擅长随机访问,但在列表中间位置进行增、删元素操作效率较低。 ArrayList 不是线程安全的,多线程环境下可以考虑用Collections.synchronizedList(List list)函数返回一个线程安全的ArrayList类,也可以使用并发包下的CopyOnWriteArrayList类。
一、实现原理
ArrayList是完全基于Object类型数组实现的,内部提供了一个自动扩容机制来动态扩增数组容量。当我们进行对象元素添加的时,判断当前元素个数是否达到数组容量,达到了则进行数组扩容,扩容大小为原来的1.5倍或实际需要容量大小,然后将原数组中的元素复制到扩容后的新数组中,并且新数组替换元数组,最后进行新元素的添加。这样就完成了动态数组的实现。
二、源码分析
2.1 继承与实现关系
- 实现了RandomAccess接口,标记着其支持随机快速访问;
- 实现了Cloneable接口,标记着其可以被克隆复制;
- 实现了Serializable接口,意味着它支持序列化;
- 继承了AbstractList类,意味着它是List接口的具体实现,遵循着List的操作规范,共享一些通用数据操作。
2.2 重要成员信息
- DEFAULT_CAPACITY:定义了ArrayList的默认初始化大小,当ArrayList的构造方法没有显示指定数组长度时,类内部使用该默认缺省值作为数组的容量大小;
- EMPTY_ELEMENTDATA:共享的空Object数组实例,当ArrayList构造方法中显示指定数组长度为0时,类内部使用该值赋值给elemetData数组;
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA:共享的空Object数组实例,当ArrayList构造方法中没有显示指定数组长度时,类内部使用该值赋值给elemetData数组;注意:将其与EMPTY_ELEMENTDATA区分开来,以知晓添加第一个元素时扩容多少。
- elemetData:ArrayList 的元素存储在其中的数组缓冲区,是ArrayList的底层数据结构,用于存放实际元素,被标记为transient,序列化的时候此字段不会被序列化;
- size:ArrayList中实际存放的元素个数。
- MAX_ARRAY_SIZE:ArrayList可分配的最大大小。
2.3 扩容机制
ArrayList在添加元素的时候,会触发扩容检测,当检测到当前的elementData容量已满,则进行扩容处理,然后再将新元素添加到elementData尾部。
- 扩容源码:
- 扩容原理:
- 首先,它判断了元素个数是否等于当前数组的容量,也就是判断当前数组是不是满的,如果当前空间是满的,就需要扩容了,grow函数就是扩容函数了,扩容后再将被加元素加到数组中;
- 计算当前ArrayList的原容量,然后进行扩大到1.5倍并记作新容量;
- 若新容量等于或小于所需要的最小容量,空容量集合则返回默认容量与最小所需容量中最大值,否则返回最小所需容量;
- 若新容量等于或超过ArrayList的最大容量,则返回整数最大值或ArrayList的最大容量; 否则返回新容量。
注意:
- elementData被赋予的是EMPTY_ELEMENTDATA,此时数组容量为0,添加元素时,会进入此扩容情况,容量从0扩展到1;
- elementData被赋予的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此时数组容量为0,添加元素时,会进入此扩容情况,容量从0扩展到10;
- 当ArrayList的容量大于0,并且ArrayList是满的时,此时添加元素的话,进行正常扩容,每次扩容到原来的1.5倍。
2.4 快速失败(fast-fail)机制
快速失败(fast-fail)是Java集合中的一种错误机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生
- 原理:迭代器在遍历时,直接访问集合中的内容,并且在遍历过程中使用一个modCount变量。集合在被遍历期间,如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
- 事件产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过iterator去遍历集合时,该集合的内容被其他线程所改变,则会抛出ConcurrentModificationException异常。
- 解决办法:通过java.util.concurrent集合包下的相应类去处理,则不会产生fast-fail事件。
2.5 安全失败(fail-safe)
- 原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
- 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
- 基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
- java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
2.5 重要方法
2.5.1 添加
ArrayList在add(E e)方法中主要完成三件事:
- 确保能够将元素添加到集合中,即确保ArrayList的容量(判断是否需要扩容);
- 将元素扩容到elementData数组的指定位置;
- ArrayList的实际元素个数加1。
ArrayList添加元素的方法还有:
- add(int index, E element):在指定位置插入元素;
- addAll(Collection<? extends E> c):添加整个集合元素;
- addAll(int index, Collection<? extends E> c):在指定位置处插入集合元素;
2.5.2 删除
remove(Object o)方法是移除指定元素。该方法主要是先找到待移除元素索引位置索引index,然后进行索引位置元素剔除。 ArrayList移除元素的方法还有:
- remove(int index)方法的作用是删除指定下标的元素,并返回该索引处的元素;
- removeAll(Collection<?> c):删除包含在指定容器c中的所有元素;
- removeIf(Predicate<? super E> filter):按照一定规则过滤(删除)集合中的元素;
2.5.3 更新
set(int index, E element)方法是覆盖指定下标元素,也可以理解为是更新指定索引index处的元素。
2.5.4 查询
get(int index)方法返回指定索引的元素。
2.5.5 其他常用方法
- retainAll(Collection<?> c):保留c中存在的元素,移除c中不存在的元素;
- indexOf(Object o):正序查找首个为指定元素o的位置;
- lastIndexOf(Object o):倒序查找首个为指定元素o的位置;
- clear():清空集合;
- subList(int fromIndex, int toIndex):创建只读子集合;
- trimToSize():对ArrayList的容量进行调整为当前大小,减少数据空间;
三、对应线程安全实现
3.1 Collections 同步方法
synchronizedList(List list):返回一个SynchronizedList类型的线程安全的集合类。其对部分操作加上了synchronized关键字以保证线程安全。但其iterator()操作还不是线程安全的。其读写操作都加了同步关键字,读写操作都比较均匀;
3.2 CopyOnWriteArrayList
CopyOnWriteArrayList是java.util.concurrent包下面的一个实现线程安全的List。 其发生修改时候做copy操作,读的时候没有加任何同步关键字,做到了新老版本分离,保证读的高性能,适用于以读为主,读操作远远大于写操作的场景中使用,比如缓存。
|