前言
本文是1.8版本的ArrayList源码解析,本文包含了transient,serialVersionUID,深浅拷贝,反射元素创建,扩容逻辑,Fail-Fast机制,数组平移,迭代器等内容的解析。
简介
-
ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。
-
继承于AbstractList,提供了相关的添加、删除、修改、遍历等功能。
-
和Vector不同,ArrayList中的操作不是线程安全的所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
-
实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
-
实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
-
实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
关系如下:
java.lang.Object ↳ java.util.AbstractCollection<E> ↳ java.util.AbstractList<E> ↳ java.util.ArrayList<E> public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}
java集合框架图如下:
问题
- 为什么是有序的
- 为什么是线程不安全的
- 为什么查询较快,删除插入慢
- 扩容逻辑
- 拷贝方式
带着问题开始解析,打开源码,我们先从属性入手。
一、ArrayList属性解析
我们跟踪源码看到如下属性
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static final long serialVersionUID = 8683452581122892189L;
1.DEFAULT_CAPACITY
此属性为默认的容器大小
2.EMPTY_ELEMENTDATA
此属性为空元素数据的数组
3.DEFAULTCAPACITY_EMPTY_ELEMENTDATA
此属性为构造带容量参数时的空数组实现
4.elementData
此属性为真正存数据的地方
要注意此属性用transient关键字修饰,transient用来表示一个域不是该对象序行化的一部分,当一个对象被序行化的时候,transient修饰的变量不会被序列化。
用此修饰符其实是为了提高性能,因为elementData中有很多未使用的空间,如果这些空间也序列化,其实是无意义的,那么我们知道ArrayList其实是实现了序列化的,序列化和反序列化的实现其实writeObject(),readObject()这两个方法,我们下文有解析。
5.size
此属性为ArrayList的大小
6.MAX_ARRAY_SIZE
此属性为最大大小
7.serialVersionUID
此属性是序列化UID,Java序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。 在进行反序列化,Java虚拟机会把传过来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较, 如果相同就认为是一致的实体类,可以进行反序列化,否则Java虚拟机会拒绝对这个实体类进行反序列化并抛出异常。
我们先简单对属性做了说明,下面跟随思路通过构造函数进行解析。
二、ArrayList方法解析
1、构造方法
跟踪源码看到如下代码
//空构造
public ArrayList() {this.elementData = DEFAULTCAPACITY_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);
}
}
//传入集合的构造
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 {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
首先看到,ArrayList有三个构造,我们逐个解析
1.ArrayList()
空构造实现,给elementData赋值DEFAULTCAPACITY_EMPTY_ELEMENTDATA(空数组)。
2.ArrayList(int initialCapacity)
带初始容量的实现
- 如果传入的容量大于0,则对elementData创建出对应大小的数组
- 如果传入的容量等0,则给elementData赋值EMPTY_ELEMENTDATA
- 否则抛出异常,非法容量
这时我们发现,在空构造和传入初始容量为0的构造中,对elementData的初始化赋值用了两个不用的对象,虽然这两个对象都是空数组实现。
这个实现是1.8之中新加入的,在1.7之中如果容量是0,会调用Arrays.copyOf()方法,也就是创建出空实例,如果一个应用中有很多空实例,就会有很多空数组。在1.8之中用EMPTY_ELEMENTDATA代替Arrays.copyOf()方法【创建出空数组】,直接指向同一个空数组。
3.ArrayList(Collection<? extends E> c)
此方法可以传入一个实现了Collection的E的子类,并对elementData赋值
-
用Collection.toArray()方法得到一个对象数组,并赋值给elementData 。
-
如果通过别的集合构造出ArrayList,则需要手动对Size赋值
-
Collection.toArray()方法是有可能出错的,如果出错则通过Arrays.copyOf()方法来复制传入的集合至elementData中。
-
如果传入的集合为空,则给elementData赋值EMPTY_ELEMENTDATA
在步骤3中有一个很关键的方法是Arrays.copyOf(),有很多重载,下面源码为基本数据类型的拷贝,和复杂数据类型的拷贝。
//简单数据类型拷贝
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
//复杂数据类型拷贝
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
public static native void arraycopy(java.lang.Object src, int srcPos, java.lang.Object dest, int destPos, int length);
-
基本数据类型拷贝和复杂数据类型拷贝其实都调用了System.arraycopy()方法,此方法的实现在native层。此方法有五个参数,解析如下
-
第一个参数src:要复制的数组
-
第二个参数srcPos:要复制的数组中的起始位置
-
第三个参数dest:副本数组。预先创建好的数组
-
第四个参数destPos:副本数组中的起始位置
-
第五个参数length:要复制的数组元素的数量
这里要提一个概念,就是深拷贝和浅拷贝,浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存。使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
此方法将数组src从下标为srcPos开始拷贝,一直拷贝length个元素到dest数组中,在dest数组中从destPos开始加入刚才拷贝的数组。对于一维数组来说,这种复制属性值传递,修改副本不会影响原来的值。对于二维或者一维数组中存放的是对象时,复制结果是一维的引用变量传递给副本的一维数组,修改副本时,会影响原来的数组。
-
-
复杂数据类型会根据newType进行判断
-
如果是Object数组类型,则通过new创建对象
-
如果不是则调用 Array.newInstance()方法动态反射创建对象,此方法下文会有解析
最后总结一下Arrays.copyOf()和System.arraycopy()的区别
Arrays.copyOf()会返回一个新数组。该新数组在方法内部被创建
System.arraycopy()需要预先创建好一个目标数组,且不会返回任何值。比如当希望数据的操作在原数组中进行时,只需将原数组作为目标数组即可。
Arrays.copyOf()实际上是调用了System.arraycopy()
-
总结
本节重点其实是概念,要区分出EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA两个空数组的意义,要重点理解System.arraycopy()方法。以及深拷贝和浅拷贝的概念。最后要了解复杂数据类型的Arrays.copyOf()的元素创建方式。
关键词
EMPTY_ELEMENTDATA,DEFAULTCAPACITY_EMPTY_ELEMENTDATA,深拷贝,浅拷贝,反射元素创建
2、添加方法
添加方法有三个,其实内部关键逻辑就两步
- 确认容量是否够用,如果不够则最终调用grow()方法
- System.arraycopy()进行元素添加
下面我们围绕这两个关键点进行源码解析
//元素直接添加
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//通过下下标添加
public void add(int index, E element) {
if (index > size || index < 0)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
//添加集合
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
//添加集合
public boolean addAll(int index, Collection<? extends E> c) {
if (index > size || index < 0)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
//判断是否需要扩容
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)grow(minCapacity);
}
//扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
//大容量开辟
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
1.add(E e)
-
首先调用ensureCapacityInternal()方法,把size+1当作参数传入
1.1 进入ensureCapacityInternal()方法,如果elementData是空构造实现的(也就是等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA)则对传入的参数和默认容器大小取最大值当作最小的容器大小。
1.2 然后调用ensureExplicitCapacity()方法,把刚才算出的最小容器大小传入
1.2.1 进入ensureExplicitCapacity()方法,首先对modCount进行自增
1.2.2 如果传入的最小容器大小-elementData的大小>0则扩容(调用grow()方法)
1.2.2.1 计算新的容器大小,当前大小的1.5倍(通过位移计算)
1.2.2.2 如果是第一次调用add()方法,容器大小则为默认大小(DEFAULT_CAPACITY)
1.2.2.3 如果计算出的容器大小超过了最大限额(Integer.MAX_VALUE - 8)则做出如下处理
1.2.2.4 如果minCapacity大于最大限额,则赋值为Integer.MAX_VALUE,不是则赋值为MAX_ARRAY_SIZE,否则抛出异常
1.2.2.5 最后通过Arrays.copyOf()拷贝之前的数据至新数组并扩容
-
最总把传入的E通过size自增的方式定位下标并赋值,然后返回True
此时要注意的是modCount的自增是为了防止在迭代过程中通过List的方法(非迭代器)改变了原集合,导致出现不可预料的情况,从而提前抛出并发修改异常,也就是Fail-Fast机制。在可能出现错误的情况下提前抛出异常终止操作。而且此属性并不是为了多线程所设计,ArrayList不适用与多线程情况。。
2.add(int index, E element)
-
此方法的也会像add(E e)一样,首先调用ensureCapacityInternal()方法,执行逻辑和上文分析一样。
-
通过System.arraycopy(elementData, index, elementData, index + 1,size - index)进行数据位移,把elementData从index开始的数据向后移动一位。
-
把新的element传入至对应下标
-
size自增
要注意,Arraylist是通过System.arraycopy()实现的位移,上文分析得出该方法是只操作传入的数组,并不会重新创建数组。这里我们就要思考什么时候需要调用Arrays.copyOf(),什么时候需要调用System.arraycopy()。
3.addAll(Collection<? extends E> c)
-
用Collection.toArray()方法得到一个对象数组,并得到数组的长度
-
调用ensureCapacityInternal(size + numNew)判断是否扩容
-
依然还是通过System.arraycopy(a, 0, elementData, size, numNew)直接把对象数组添加至elementData尾部
-
size增加对应添加数组的长度
-
如果对象数组长度大于0则返回True,否则返回False。
通过阅读发现,数组的元素添加也是通过System.arraycopy()实现
4.addAll(int index, Collection<? extends E> c)
-
首先判断传入的下标是否合法,不合法抛出异常。
-
和addAll(Collection<? extends E> c)一样,得到对象数组,数组长度,并判断是否扩容。
-
通过System.arraycopy()从index位置开始位移index之后元素的数组长度位(c)。
-
还是通过System.arraycopy()把数组(c)的元素添加至index位
还是通过System.arraycopy()实现了元素位移和添加
总结
根据解析发现,数组添加首先要通过ensureCapacityInternal()方法确认容量是否够用,如果不够用则扩容自身的1.5倍大小,扩容的实现是Arrays.copyOf()。每次扩容的时候要对modCount进行自增,用于预防在迭代时如果对元素同步进行修改则提前抛出异常。然后无论是那种添加方式,元素新增和元素位移都是通过System.arraycopy()实现。最后对Size进行数值增加。
分析得出,在扩容时最终调用的方法是Arrays.copyOf(),上文分析出该方法会返回一个新数组,该新数组在方法内部被创建,也就是说每一次扩容都意味着重新创建,这也就是为什么如果新增时需要扩容效率会低。元素位移和增加调用的时System.arraycopy(),效率相对要高一些,只操作了传入数组,并没有反复重新创建。
关键词
扩容逻辑,Arrays.copyOf(),System.arraycopy(),Fail-Fast,
3、删除方法
1.remove(int index)
//下标位元素删除
public E remove(int index) {
if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
- 验证下标合法性,不合法则抛出异常
- modCount自增,Fail-Fast机制
- 通过下标获取对应元素
- 通过System.arraycopy()实现index位的元素删除
- 把Size-1位元素归null,用于GC回收
- 返回删除元素
2.remove(Object o)
//元素删除
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
-
如果删除元素为空,for循环取出第一个null元素的下标并调用fastRemove(index)方法,不为空元素同理。
-
删除成功返回Ture,没有找到元素返回False。
fastRemove方法实现如下
private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved); elementData[--size] = null; // clear to let GC do its work }
-
modCount自增,Fail-Fast机制
-
通过System.arraycopy()实现index位的元素删除
-
把Size-1位元素归null,用于GC回收
其实根本上和通过下标删除方法逻辑一致
-
3.removeAll(Collection<?> c)
//集合删除
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r];
} finally {
if (r != size) {
System.arraycopy(elementData, r,elementData, w,size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
-
验证传入的集合不为空
-
调用batchRemove(Collection<?> c, boolean complement) 方法
2.1 在for循环之中,通过r,w两个变量完成了数组的平移,如果传入的c不包含elementData[r]则w自增,并赋值。
例如列表为: [0, 1, 2, 3, 4, 5, 6] 需要删除的列表为 [2, 5], 那么遍历的时候, 数组是这样变化的:
r=0,w=0: [0, 1, 2, 3, 4, 5, 6] r++, w++
r=1,w=1: [0, 1, 2, 3, 4, 5, 6] r++, w++
r=2,w=2: [0, 1, 2, 3, 4, 5, 6] r++, w不变, 使得后面的元素都往前平移一位
r=3,w=2: [0, 1, 3, 3, 4, 5, 6] r++, w++
r=4,w=3: [0, 1, 3, 4, 4, 5, 6] r++, w++
r=5,w=4: [0, 1, 3, 4, 5, 6, 6] r++, w不变
r=6,w=4: [0, 1, 3, 4, 6, 6, 6]
2.2 我们先不看(r != size) ,先看if (w != size)分支
2.2.1 如果w==size则说明没有需要删除的元素,直接返回false
2.2.2 如果w!=size则说明有需要删除的元素,则从w下表位开始循环给elementData赋值null,用于GC回收。在把w叠加给modCount,更新size,返回true
2.3 我们再看(r != size)分支,因为c.contains(elementData[r])很有可能发生异常,就会导致还没有循环完毕就抛出。这个分支的处理是异常之后把未被循环到的元素添加至elementData之后。
总结
删除本质其实就是首先定位元素下标,然后调用了System.arraycopy()实现index位的元素删除。并且删除出发了modCount自增。
传入集合删除方法removeAll(Collection<?> c) 比较特殊,内部很巧妙的用r和w实现了列表元素位的平移,把不删除的元素向左平移,最后删除w之后的元素,并且要记得在此方法之中会有可能出错。
所有删除后的元素位都会赋值null处理,用于GC回收
关键字
modCount增加,数组平移,Null处理GC回收
4、修改,获取,清空,替换方法
1.set(int index, E element)
修改方法
public E set(int index, E element) {
if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
- 判断下标是否合法,不合法抛出异常
- 把传入的Element赋值到对应下标位元素
2.get(int index)
获取方法
public E get(int index) {
if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
- 判断下标是否合法,不合法抛出异常
- 通过下标获取元素返回
3.clear()
清空方法
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
- modCount自增,Fail-Fast机制
- 循环赋值Null
- size赋值为0
4.replaceAll(UnaryOperator operator)
替换方法
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
elementData[i] = operator.apply((E) elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
5、其他方法
1.clone()
克隆
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
-
通过Arrays.copyOf()方法对elementData进行浅拷贝,然后把创建出的Arraylist的modCount赋值为0,然后return。
要注意如果需要深拷贝,则需要实现复杂对象的clone()方法。
2.contains(Object o)
是否包含
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
- 其实和remove(Object o)方法基本一样,没有区别只不过返回了对应下标
3.ensureCapacity(int minCapacity)
预先控制初始化Arraylist大小
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)? 0: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
-
如果传入的容器大小大于默认大小(或0),则按照传入参数开辟。
其实最后还是调用了ensureExplicitCapacity()方法,由此可推断出,如果arraylist在首次获得数据量较大,则调用此方法会大幅度提高性能,因为少去了多次扩容的步骤(当前容量*1.5)。
4.lastIndexOf(Object o)
最后出现的搜索元素下标
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
- 和contains(Object o)基本一致,区别是i--,则取尾。如果没有对应元素则返回-1
5.retainAll(Collection<?> c)
两个ArrayList是否包含相同的元素
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
- 看到代码是不是非常熟悉,和removeAll(Collection<?> c)的区别就在于把complement变为了true。所以也就自然保留下相同元素。
6.trimToSize()
去除ArrayList不包含Null节点的元素
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
-
modCount自增,Fail-Fast机制
-
如果elementData的容器大小大于实质元素个数,则做出如下处理
2.1 如果元素个数为0则直接赋值EMPTY_ELEMENTDATA
2.2 否则直接通过Arrays.copyOf()保留实际元素。
7.toArray()
转成Array
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
- 直接通过Arrays.copyOf()返回数组
总结
clone(),ensureCapacity(int minCapacity),trimToSize(),toArray()其实都是通过Arrays.copyOf()做最终处理,这也就侧面证明为什么查询较快,删除插入慢。
6、迭代器
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
protected int limit = ArrayList.this.size;
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor < limit;
}
public E next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int i = cursor;
if (i >= limit)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
}
-
在获取集合的迭代器的时候,去new了一个Itr对象,而Itr实现了Iterator接口,主要重点关注Iterator接口的next方法
属性:
- limit:用来记录当前集合的大小值
- cursor:游标,默认为0
- astRet:上一次返回元素的下标
- expectedModCount :就是修改次数,对ArrayList 内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。
next():
- 在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等则是其他线程修改了Arraylist。抛出ConcurrentModificationException异常(线程不同步)
- 游标大于了最大限制则抛出NoSuchElementException异常(无法找到对应元素)
- 获取到集合中存储元素的elemenData数组,游标cursor+1,然后返回元素 ,并设置这次次返回的元素的下标赋值给lastRet
根据分析,在Arraylist中,证明了需要遍历元素,如果头铁非要多线程访问,还是多使用迭代器,是从线程安全的角度考虑。
arrayList支持的3种遍历方式,迭代器,下标(随机访问),for循环,经过测试,从100000个元素之中迭代每一个元素耗时如下
RandomAccess:3 ms Iterator:8 ms For:5 ms
得出结论,下标(随机访问)效率最高
总结
- 为什么是有序的
- 本质就是数组,只不过不光有序,还可重复。
- 为什么是线程不安全的
- 所以的增删改查其实都没有synchronized代码块,唯一做线程安全处理的其实就是modCount处理,Fail-Fast机制,如果线程不同步则抛出异常。
- 遍历元素时尽可能使用迭代器,防止线程不同步
- 为什么查询较快,删除插入慢
- 本质就是数组,通过下标定位元素,所以快
- 增加,删除,扩容,克隆等方法都是通过Arrays.copyOf()实现的,方法内部会返回一个新数组。该新数组在方法内部被创建,所以慢
- 扩容逻辑
- 自身的1.5倍
- 如果首次数据量很大,调用ensureCapacity(int minCapacity)开辟空间,可以防止多次扩容,提高性能
- 拷贝方式
- 对基本类型、包装器是深拷贝,对引用类型是浅拷贝,需要自己实现拷贝逻辑
还有例如removeIf(Predicate<? super E> filter),replaceAll(UnaryOperator operator),sort(Comparator<? super E> c)等方法没有解析,因为里面包含了Predicate,UnaryOperator,Comparator等其他结构,之后一一解析。