ArrayList 源码学习

本贴最后更新于 2117 天前,其中的信息可能已经斗转星移

ArrayList 源码分析

当前分析的源码版本为

jdk 1.7.0_80

ArrayList 是一个动态数组容器类。下方的代码为 ArrayList 的定义。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { // TODO ... }

一、add() 方法的扩容原理是什么

先看源码

public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }

代码中的 elementDatasizeArrayList 类的两个成员变量。

transient Object[] elementData; // non-private to simplify nested class access private int size;

代码注释中的 Increments modCount!! 在遍历的方法中再讲。modCount 代表容器被修改的次数。
首先调用 ensureCapacityInternal(size + 1) 方法确保容量是够的。然后对 elementData[size++] 进行赋值,最后 size 加 1。以下为 ensureCapacityInternal() 方法的代码。

private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }

grow() 方法的主要内容为

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

扩容的核心的代码是 int newCapacity = oldCapacity + (oldCapacity >> 1);,右移 >> 一位相当于除以 2。可以看出容器的容量扩大了原来的 1.5 倍。

二、remove() 方法

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; } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }

remove() 方法的代码相对简单,主要是调用了 System.arraycopy() 方法对数组 elementData 自身进行复制,起始位置从 index+1 复制到 index 。然后将索引位置在 size-- 的元素的引用置为 null, 最后 size 减一。

三、ArraysList 中的向前遍历

ArrayList 类中维护了一个私有的内部类 ListItrListItr 实现了 ListIterator 接口。以下为 ListItr 的定义。

private class ListItr extends Itr implements ListIterator<E> { // TODO ... }

接口 ListIterator 继承了 Iterator 接口,并新增了几个新的方法。下面是 ListIterator 的方法。

public interface ListIterator<E> extends Iterator<E> { boolean hasNext(); E next(); boolean hasPrevious(); E previous(); int nextIndex(); int previousIndex(); void remove(); void set(E e); void add(E e); }

来看一个简单的降序遍历的例子。

public static void main(String[] args) { List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3)); ListIterator<Integer> descItr = list.listIterator(list.size()); while (descItr.hasPrevious()) { Integer previous = descItr.previous(); System.out.println(previous); } }

输出

3 2 1

四、modCount 有什么作用

modCountAbstractList 中的一个成员变量,记录容器类结构发生变化的次数,使容器结构发生变化的方法比如有 add()remove() 方法。

protected transient int modCount = 0;

接下要从方法 iterator() 说起。

public Iterator<E> iterator() { return new Itr(); }

ItrArrayList 中的一个内部类,实现了 Iterator 接口。

private class Itr implements Iterator<E> { 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(){...} public E next() {...} public void remove() {...} final void checkForComodification() {...} }

cursor 是下一个要返回的元素的索引值。lastRet 是最后一次要返回的元素的索引值。expectModCount 表示期望地修改次数。在初始化迭代器的时候复赋值为 modCount

下面代码中要通过迭代器删除 list 中的一个元素,并解释迭代器中每个方法的工作原理。

public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); Iterator<Integer> iterator = list.iterator(); Integer next; while(iterator.hasNext()) { next = iterator.next(); if (next == 3) { iterator.remove(); } } System.out.println(list.size()); }

while 循环中,先执行 hasNext() 方法,判断元素的索引位置是否超过容器容量。

public boolean hasNext() { return cursor != size; }
public E next() { 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]; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }

next() 方法先调用 checkForComodification() 检查容器的结构是否发生变化。在方法 checkForComodification() 中,如果 modCount != expectedModCount 就抛出异常 throw new ConcurrentModificationException(); 。检查成功后对下一个元素的索引变量 cursor 进行赋值,然后赋值给变量 lastRet,最后返回当前索引位置所在的元素。

下面是删除方法 remove() 的源代码。

public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }

执行 remove() 方法的第一步就是对 lastRet 的检查。所以在使用迭代器遍历时,调用 remove() 方法前一定要调用 next() 方法。在 next() 方法的结尾处会对 lastRet 方法进行赋值,否则 lastRet 的初始值为 -1,就会抛出异常 java.lang.IllegalStateException

接下来调用 ArrayListremove() 方法。然后对 expectModCount 进行赋值,保证容器结构的一致。

五、为什么在 foreach 方法中删除元素会抛异常

下面使用 foreach 语法删除一个元素。

public static void main(String[] args) { List<Integer> list = new ArrayList<>(5); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); for (Integer i : list) { if (i == 5) { list.remove(i); } } }

运行后控制台抛出异常。

Exception in thread "main" java.util.ConcurrentModificationException

foreach 是 java 中的一颗语法糖,编译后的字节码文件是这样的。

public static void main(String[] args) { List<Integer> list = new ArrayList(); list.add(1); list.add(2); list.add(3); Iterator<Integer> iterator = list.iterator(); System.out.println(list.size()); List<Integer> list1 = new ArrayList(); list1.add(1); list1.add(2); list1.add(3); Iterator i$ = list1.iterator(); while(i$.hasNext()) { Integer integer = (Integer)i$.next(); if (integer == 3) { list1.remove(integer); } } }

上面代码中的 remove() 方法如下。

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 }

程序最后走到了 fastRemove() 方法中。fastRemove() 方法和迭代器中的 remove() 方法不同,程序中并没有维护 expectedModCount = modCount。所以在 next() 方法中执行 checkForComodification(),发现 expectedModCountmodCount 不同,容器的结构发生了变化,就会抛出异常 throw new ConcurrentModificationException()

六、ArrayList 中的其他方法

在开发过程中还有一些 ArraysList 中的常用方法。

// 构造器方法 public ArrayList(int initialCapacity) {...} public ArrayList() {...} public ArrayList(Collection<? extends E> c) {...} // 判断容器中元素的个数 public int size() {...} // 判断元素是否为空 public boolean isEmpty() {...} // 是否包含某个元素 public boolean contains(Object o) {...} // 找出元素的索引位置 public int indexOf(Object o) {...} public int lastIndexOf(Object o) {...} // 将容易转换为数组 public Object[] toArray() {...} public <T> T[] toArray(T[] a) {...} // 获取元素 public E get(int index) {...} // 替换元素 public E set(int index, E element) {...}

(完)

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3201 引用 • 8216 回帖
  • ArrayList
    5 引用 • 10 回帖
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    174 引用 • 538 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...