java1.8 arraylist源码_ArrayList源码解析

前言

本文是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

↳ java.util.AbstractList

↳ java.util.ArrayList

public class ArrayList extends AbstractList

implements List, RandomAccess, Cloneable, java.io.Serializable{}

复制代码

java集合框架图如下:

6930885259923816456

问题

为什么是有序的

为什么是线程不安全的

为什么查询较快,删除插入慢

扩容逻辑

拷贝方式

带着问题开始解析,打开源码,我们先从属性入手。

一、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[] 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 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 iterator(){

return new Itr();

}

private class Itr implements Iterator{

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等其他结构,之后一一解析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值