java.util.ArrayList是最常用的工具类之一, 它是一个线程不安全的动态数组. 本文将对JDK 1.8.0中ArrayList实现源码进行简要分析.

ArrayList底层采用Object[]来存储, 每次添加元素前都会检查数组是否有足够空间容纳新的元素.

若数组空间不足则会进行扩容操作, 即创建一个容量更大的数组 并将已有的元素复制到新数组中. 默认情况下新数组的容量是当前容量的1.5倍.

ArrayList使用Arrays.copyOfSystem.arraycopy调用原生(native)方法进行数组复制, 以提高效率.

addAll, removeAll等方法中通常使用c.toArray方法来获取容器中所有元素.

ArrayList提供了iterator()listIterator()两种迭代器, 前者只能向后移动, 而后者可以双向移动.

iterator()只能删除上一个访问的元素, 而listIterator()还可以在游标位置添加元素.

两种迭代器都采用fail-fast机制, 即使用modCount记录结构性改变(添加删除元素等)的次数, 迭代器在移动前会检查modCount是否发生改变. 若modCount改变, 则抛出异常中止迭代. 该方法是为了防止其它线程修改容器造成迭代结果不一致.

数据结构与构造器

在介绍构造器之前, 首先介绍一下ArrayList的数据结构:

// 默认初始容量
private static final int DEFAULT_CAPACITY = 10; /**
* elementData是实际存储数据的缓冲区
* 其类型为Object[], 即在内部用Object类存储元素在取出时进行类型转换
* 访问控制为默认(包内访问)是为了便于内部类访问
* transient关键字表示不对该域进行序列化, ArrayList内部重写了序列化/反序列化方法
*/
transient Object[] elementData; // 当前元素数目
private int size; // 用于表示空实例的数组
private static final Object[] EMPTY_ELEMENTDATA = {}; /**
* 默认构造器使用的空数组
* 当elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA时, 首次添加元素会使elementData扩容到DEFAULT_CAPACITY
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

接下来可以阅读ArrayList的几个构造器:

// 按照指定初始容量进行初始化
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 指定容量为0时使用EMPTY_ELEMENTDATA, 而非重新初始化空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 不允许负容量
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
} /**
* 按照DEFAULT_CAPACITY进行初始化
* 构造时并未实际建立缓冲区, 在首次添加元素时才会扩容到DEFAULT_CAPACITY
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} /**
* 根据其它集合对象创建
* 默认调用Collection.toArray方法,
* 若toArray方法返回类型不是Object[], 则利用Arrays.copyOf进行类型转换
*/
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;
}
}

Arrays.copyOf用于复制数组, 其封装了原生(native)方法System.arraycopy, 具有很高的效率.

ArrayList中广泛使用这两个方法用于扩容, 插入等操作.

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;
}

添加元素

ArrayList的底层数据结构为数组, 每次向其中添加元素前都会检查数组容量是否足够. 若数组已满则会进行扩容操作.

首先阅读添加单个元素的方法add(E):

// 向数组末尾添加一个元素, 返回值代表数组是否改变
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} private void ensureCapacityInternal(int minCapacity) {
// 对于默认构造器创建的实例, 保证容量不小于DEFAULT_CAPACITY
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
} private void ensureExplicitCapacity(int minCapacity) {
// modCount记录了实例发生结构性变化的次数, 用于迭代器的fail-fast机制
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} private void grow(int minCapacity) {
// 计算扩容后新容量, 默认为原容量的1.5倍
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // oldCapacity的1.5倍已经溢出, 所以出现反而变小的情况
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; // 若大于MAX_ARRAY_SIZE则由hugeCapacity取上限
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); // 创建新数组并把原有元素移动到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
} private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

在制定位置添加元素的add(index, e)方法非常类似:

public void add(int index, E element) {
rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!!
// elementData类型一定为Object[], 不用Arrays.copyOf进行类型检查直接调用System.arraycopy即可
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
} private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

addAll方法调用c.toArray获取c中所有元素:

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) {
rangeCheckForAdd(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;
}

访问元素

get方法可以访问指定位置的元素:

public E get(int index) {
rangeCheck(index); return elementData(index);
} private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
} E elementData(int index) {
return (E) elementData[index];
}

set方法用于修改某位置的元素, 未发生结构性改变不会修改modCount:

public E set(int index, E element) {
rangeCheck(index); E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

toArray方法可以将ArrayList中所有元素作为数组返回:

public Object[] toArray() {
return Arrays.copyOf(elementData, size);
} public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}

出于类型安全的原因, 建议使用第二个toArray方法:

List<String> list = new ArrayList<>();
// add sth in list
String[] arr = new String[list.size()];
arr = list.toArray(arr);

删除元素

remove(index)方法用于移除指定位置的元素:

public 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; // clear to let GC do its work return oldValue;
}

使用System.arraycopy将index后面的元素向前移动一位, 覆盖被删除的元素.

将最后位置上的元素设为null便于GC进行回收.

remove(obj)方法会移除第一个与obj相同的元素, 相同关系使用obj.equals方法来判断:

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;
} 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
}

removeAll(c)方法移除所有包含在容器c中的元素, retainAll(c)方法移除所有未包含在容器c中的元素.

public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
} public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}

实际完成该操作的是batchRemove方法:

private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
// 遍历ArrayList, 使用`c.contains`判断是否包含
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
// 将需要保留的元素移动到数组前部
elementData[w++] = elementData[r];
} finally {
// 保持与AbstractCollection的行为一致
// 即使c.contains抛出异常仍完成操作
if (r != size) {
// r != size 说明发生了contains异常.
// 将后部未判断的部分移动到前面予以保留
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// w != size 说明有元素被删除, 执行清理
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}

迭代器

ArrayList提供了两个迭代器: iterator()listIterator(). 它们都采用fail-fast机制, 即当迭代器遍历过程中容器发生结构性改变时, next方法会抛出ConcurrentModificationException异常, 终止迭代.

所谓结构性改变是指modCount发生改变的情况, 所有的add, removey操作以及clear()方法都会修改modCount. fail-fast机制主要为了应对其它线程修改容器导致的不一致问题.

首先阅读iterator()源码:

// 获得迭代器实例
public Iterator<E> iterator() {
return new Itr();
} // 迭代器内部实现类
private class Itr implements Iterator<E> {
int cursor; // 下一个要返回元素的下标
int lastRet = -1; // 上一个返回元素的下标, 默认为-1.
int expectedModCount = modCount; // 检查是否可以继续遍历
public boolean hasNext() {
return cursor != size;
} // 返回cursor指向的元素, 并将cursor后移一个位置
@SuppressWarnings("unchecked")
public E next() {
// 检查modCount是否一致
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
} // 删除上一次返回的元素
public void remove() {
// 检查是否返回过元素(成功调用过next方法), 且该元素未被删除
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
// 删除元素
ArrayList.this.remove(lastRet);
// 修正游标位置
cursor = lastRet;
// 标记上次返回的元素已被删除, 避免误删
lastRet = -1;
// 更新expectedModCount, 保证迭代器可以继续执行
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

listIterator()可以双向移动, 除了删除元素外还可以在游标位置添加元素:

public ListIterator<E> listIterator() {
return new ListItr(0);
} private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
} public boolean hasPrevious() {
return cursor != 0;
} public int nextIndex() {
return cursor;
} public int previousIndex() {
return cursor - 1;
} @SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
} public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
} public void add(E e) {
checkForComodification(); try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}

ArrayList还有两个内部类用于处理子序列操作:

  • SubList extends AbstractList<E>
  • ArrayListSpliterator<E> implements Spliterator<E>

序列化

ArrayList的序列化会写入modCount, size和实际的元素. 同样会检查modCount是否一致, 以避免并发问题.

private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size); // Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
} if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}

自定义序列化机制的根本目的在于避免写入无意义的字段. readObject也按照同样的策略进行重写:

private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff
s.defaultReadObject(); // Read in capacity
s.readInt(); // ignored if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size); Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}

Java8 ArrayList源码分析的更多相关文章

  1. ArrayList源码分析超详细

    ArrayList源码分析超详解 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 IDEA 找到所需要分析的类(ztrl+N查找ArraLi ...

  2. ArrayList源码分析超详细(转载)

    ArrayList源码分析超详细   ArrayList源码分析超详解 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 IDEA 找到所需要 ...

  3. java8 ArrayList源码阅读

    转载自 java8 ArrayList源码阅读 本文基于jdk1.8 JavaCollection库中有三类:List,Queue,Set 其中List,有三个子实现类:ArrayList,Vecto ...

  4. Java集合干货——ArrayList源码分析

    ArrayList源码分析 前言 在之前的文章中我们提到过ArrayList,ArrayList可以说是每一个学java的人使用最多最熟练的集合了,但是知其然不知其所以然.关于ArrayList的具体 ...

  5. ArrayList 源码分析

    ArrayList 源码分析 1. 结构   首先我们需要对 ArrayList 有一个大致的了解就从结构来看看吧. 1. 继承   该类继承自 AbstractList 这个比较好说 2. 实现 这 ...

  6. Java - ArrayList源码分析

    java提高篇(二一)-----ArrayList 一.ArrayList概述 ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 nul ...

  7. java集合系列之ArrayList源码分析

    java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...

  8. ArrayList源码分析--jdk1.8

    ArrayList概述   1. ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合.  2. ArrayList支持随机访问.克隆.序列化,元素有序且可以重复.  3. ...

  9. Java ArrayList源码分析(有助于理解数据结构)

    arraylist源码分析 1.数组介绍 数组是数据结构中很基本的结构,很多编程语言都内置数组,类似于数据结构中的线性表 在java中当创建数组时会在内存中划分出一块连续的内存,然后当有数据进入的时候 ...

随机推荐

  1. python pip安装方法

    1.python安装,最好是按照32位的版本,64位版本有的时候出现奇怪问题. 2.python安装完成后,需要在系统的环境变量"path"中增加路径设置. 3.一般情况下,使用p ...

  2. 史上最难的一道Java面试题 (分析篇)

    博客园 匠心零度 转载请注明原创出处,谢谢! 无意中了解到如下题目,觉得蛮好. 题目如下: public class TestSync2 implements Runnable { int b = 1 ...

  3. java基础--static关键字的使用

    一.static关键字的作用 使类成员完全独立于该类的任何对象.通常情况下,类成员必须通过它的类的对象访问,但是被static修饰的成员,能够被自己访问,而不必引用特定的实例. 一旦一个成员被声明为s ...

  4. 理解HTTPS

    总结HTTPS HTTPS要使客户端与服务器端的通信过程得到安全保证,必须使用的对称加密算法,但是协商对称加密算法的过程,需要使用非对称加密算法来保证安全,然而直接使用非对称加密的过程本身也不安全, ...

  5. 张高兴的 Xamarin.Android 学习笔记:(三)活动生命周期

    本文将直接解释我写的一个示例.示例目的在于展示 Android 活动在 Xamarin 中的用法.如果有朋友对基础知识不太了解建议先学 Android . 新建一个 Xamarin.Android 项 ...

  6. php环境搭建工具推荐

    楼楼最近由于一系列原因,使用了几款php环境搭建工具,安装配置方便,所以在这里推荐一下.第一款是XAMPP(网址http://www.xampps.com/),软件包原来的名字是 LAMPP,但是为了 ...

  7. Loadrunner检查点使用总结

    在使用Loadrunner进行性能测试中,有时需要对性能测试中的功能是否全部正确进行判断.这里就需要用到“检查点”,本文总结了常用三种协议下检查点的使用方法,希望阅读本文后的小伙伴们能够掌握其使用方法 ...

  8. LINUX 笔记-Shell 脚本控制语句

    1.if 语句 if condition1;then command1 elif condition2;then command2 else command3 fi 2.case 语句 case va ...

  9. canvas绘制太阳系

    原文地址:http://jeffzhong.space/2017/10/26/solar/ 学习canvas有一段时间了,顺便写个小项目练手,该项目用到的知识点包括: ES6面向对象 基本的三角函数 ...

  10. 探索equals()和hashCode()方法

    探索equals()和hashCode()方法 在根类Object中,实现了equals()和hashCode()这两个方法,默认: equals()是对两个对象的地址值进行的比较(即比较引用是否相同 ...