源码版本JDK1.8.0_131
ArrayList内部是基于数组的动态管理来实现的,容量能自动增长,数组占据内存一块连续的存储空间,对于下标随机访问和遍历非常高效。
ArrayList类定义
1 | public class ArrayList<E> extends AbstractList<E> |
从ArrayList类的定义可以看出:
- 支持泛型
- 继承AbstractList并实现了List接口,因此具有基本的增删查改功能
- 实现了RandomAccess接口,具有随机读写的功能。该接口是个标识接口
- 实现了Cloneable接口,可以被克隆
- 实现了Serializable接口,并重写了序列化和反序列化方法
成员变量
1 | // 数组的默认容量 |
构造函数
ArrayList有三种构造函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29//1.指定容量就分配指定容量大小的数组
//若指定大小为0用EMPTY_ELEMENTDATA赋值
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//2.默认构造函数,用DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//3.传入一个集合作为数组的数据
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
主要成员方法
添加
add方法有两种重载,一个是在数组末尾添加,一个是在指定位置添加元素
add(E e)的调用链涉及5个方法, 依次如下:
1 | public boolean add(E e) { |
1 | public void add(int index, E element) { |
modCount变量是父类AbstrcatList中的属性,表示list结构化修改的次数。于遍历器的fail-fast机制相关。
由于要对数组元素整体移动,因此在指定位置插入的操作比较耗性能。
addAll方法也有两种重载,与add方法相似。将集合追加到末尾和将集合插入到指定位置。
(实现方法基本相同,我不想这篇文章太过累赘,相似的代码就不帖出来了)
ArrayList每次在增加元素时,都会ensureCapacityInternal方法来确保容量足够。在容量不够时会调用Arrays.copyOf方法将原数组拷贝到新数组中,这是一个非常耗性能的操作。因此建议在确定元素数量时才使用ArraysList,否则建议使用LindedList。
移除
remove方法也有2种重载,一个是移除指定下标的元素,一个是指定元素移除其第一次出现1
2
3
4
5
6
7
8
9
10
11
12
13
14public E remove(int index) {
rangeCheck(index); // 越界检查
modCount++; // 移除操作也是对数组的结构性改动,不允许发生在遍历过程中
E oldValue = elementData(index);
int numMoved = size - index - 1; // 需要移动的元素个数
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // size减1,并让旧数组的末尾元素GC
return oldValue;
}
1 | public boolean remove(Object o) { |
- 批量删除
1 | // 删除与指定集合c中相同的元素 |
此外,还有removeRange方法将指定范围内的元素移除。
修剪
作用是将数组对象的capacity减小到size长度,减少ArrayList对象占用的内存。
1 | /** |
包含
1 | // 利用indexOf方法判断是否包含某个元素 |
与indexOf方法相似的lastIndexOf方法,作用是从最后一个元素开始向前遍历。
克隆方法
Arraylist的克隆是浅克隆,即只产生ArrayList对象的副本,并不赋值元素本身
1 | public Object clone() { |
数组拷贝方法
Arrays.copyOf方法参数含义:(原数组,拷贝个数),返回拷贝的数组。
System.arraycopy方法,该方法是一个本地方法,通过调用系统的C/C++方法实现,实现数组之间的移动和复制。
1 | public static <T> T[] copyOf(T[] original, int newLength) { |
序列化机制
ArrayList实现了Serializable接口,本身具备序列化的功能。那为什么ArrayList中存储数据的elementData数组要用transient修饰,并且重写writeObject和readObject方法?
分析源码,ArrayList重写的序列化方法其实就是把size和elementData数组中不为null的元素逐个写到流中,反序列化时在逐个读取。这么做的原因是因为数组每个扩容时,极端情况下会产生原来数组长度一半的为null的元素。在序列化时把这部分null排除出去,有助于提高性能
1 | // wirteObject方法,for循环中遍历size大小而不是elementData.length |
迭代机制
在进行ArrayList遍历时,可以调用iterator()方法返回一个迭代器,使用迭代器可以进行遍历操作。1
2
3
4
5public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E>
iterator方法返回的Itr实例是ArrayList的内部类,实现了Iterator接口。
- 成员变量
1 | int cursor; // 指向迭代器下一个值的位置 |
ArrayList的迭代器使用fail-fast机制,在调用add和remove方法时会使modCount++,modCount记录的是ArrayList发生结构性修改的次数。在调用迭代器的next方法时会检查modCount与expectedModCount是否相等,不等则抛出ConcurrentModificationException异常。
这么做的原因是防止在遍历的过程中由于修改操作,有可能造成ArrayIndexOutOfBoundsException,这样的异常属于设计ArrayList这种动态数组的缺陷,应从设计层面避免。
举个栗子:1
2
3
4
5
6
7
8
9
10
11
12
13
14 // 用户代码
ArrayList<String> list = new ArrayList<String>();
list.add("a");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String next = iterator.next();
if("a".equals(next))
list.remove(next);
}
// 迭代器的hasNext方法
public boolean hasNext() {
return cursor != size(); // 注意这里的判断条件是游标cursor不等于size
}
list中只有一个元素,在remove时cursor为1,size已经为0。下一次调用hasNext方法会继续遍历,但数组有可能已经越界了。
这里有一个疑问,为什么hasNext方法的判断条件不写成cursor <= size()呢?暂时还没答案,等有更深入理解后再补充吧
还有一种特殊情况这里也记录一下,在遍历时用ArrayList的remove方法(注意并不是iterator提供的remove方法)移除元素,并不会报ConcurrentModificationException1
2
3
4
5
6
7
8
9
10// 用户代码
ArrayList<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String next = iterator.next();
if("a".equals(next)) //移除的是倒数第二个元素
list.remove(next);
}
当移除的是ArrayList中倒数第二个元素时,remove后curosor的值是原来的size-1,而此时size也变为跟curosor相等。所以当下次遍历调用hasNext方法会结束遍历,并不会继续调用next方法,所以不会去检查modCount,也就不会报异常。
- 成员方法
1 | public E next() { |
总结
- ArrayList非线程安全,只能应用在单线程环境下。
- 多线程情况下,JDK提供有Vector、Collections.SynchronizedList(List
list)。推荐JDK并发包的CopyOnWriteArrayList,Guava和Apache Common等提供的线程安全的List。
第一次写技术博客,想法由来已久,与其说是技术博客,不如算是对自己知识的回顾与总结。
写的过程中,发现很久知识已经生疏,整理的时候又有新的认识。
过程中也查了很多资料,借鉴了下面两篇文章,在这里向作者表达感谢。