ArrayList源码解析(基于JDK1.8.0_144)

标签: Java源码 Java集合 JDK1.8
5人阅读 评论(0) 收藏 举报
分类:

ArrayList简介

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

    ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。ArrayList允许包括null在内的所有元素。

    ArrayList 的 size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add操作以分摊的固定时间运行,为什么说分摊?因为add操作有可能会导致数组扩容,总体来说,插入n个元素需要O(n)时间。

    ArrayList 的 iterator 和 listIterator 方法返回的迭代器是fail-fast的:modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化(如 set 方法)。在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。

ArrayList构造函数

// 自定义初始容量
public ArrayList(int initialCapacity) 

// 默认构造函数
public ArrayList()

// 创建一个包含Collection元素的ArrayList  
public ArrayList(Collection<? extends E> c)      

ArrayList的继承关系图

ArrayList部分源码分析

默认容量大小是10

private static final int DEFAULT_CAPACITY = 10;

所有空的 ArrayList 共享一个空数组(给非默认初始容量的空数组使用)

private static final Object[] EMPTY_ELEMENTDATA = {};

所有空的 ArrayList 共享一个空数组(给默认初始容量的空数组使用)

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
真正存储数据的数组,使用 transient 修饰,该关键字声明数组默认不会被序列化。ArrayList 具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。ArrayList 重写了 writeObject() readObject() 来控制只序列化数组中有元素填充那部分内容
/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData;

集合中元素的个数

private int size;
trimToSize 方法将此 ArrayList 实例的容量调整为列表的当前大小(即有数据的部分)
public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}

ensureCapacity 方法确保ArrayList有足够的容量,如果没有足够容量会进行扩容,调用ensureExplicitCapacity方法。在这里只要elementData 不是给默认容量(10)使用的空数组,都会调用到ensureExplicitCapacity方法。如果是默认容量的空数组则会比较一下传入的minCapacity是否大于10。ensureCapacityInternal 为私有方法,当添加数据时会被调用,如果初始为默认初始容量的空数组,则会直接扩大到长度为10,所以在调用默认构造器初始化数组时并没有new数组,而是将 elementData 指向了一个空数组

public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // larger than default for default empty table. It's already
        // supposed to be at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity 方法简单判断了一下传入的容量是否大于当前存储数据的elementData数组的长度,如果大于这个长度才调用grow方法真正扩大数组的长度,newCapacity 实际有可能为3个值:原数组长度的1.5倍;传入的长度;MAX_ARRAY_SIZE 的长度。扩容操作需要把原数组整个复制到新数组中,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数
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);
}
数组的最大长度比Integer的最大值小8,这是因为有些vm会在数组中保存一些头信息
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

contains: 通过遍历数组来判断集合中是否包含某个元素,耗时和数组的长度有关,返回布尔类型的值。indexOf: 通过遍历数组来判断集合中是否包含某个元素,耗时和数组的长度有关,返回元素所在的下标。lastIndexOf: 和indexof类似,这是从后向前遍历数组

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

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

这里的 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);
    }
}
如果 a 的长度小于当前 ArrayList 中元素的个数,则直接返回一个用当前 ArrayList 拷贝的数组。反之,则将当前数组拷贝到传入数组 a 中,并且将拷贝结束后的位置的值置为 null
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;
}
get 方法在检查完 index 是否合法后,调用 elementData 方法,通过数组寻址获取数据,效率很高
E elementData(int index) {
    return (E) elementData[index];
}

public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

set 方法与 get 方法类似,同样高效,同时会返回 oldValue

public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
add(E e) 方法直接将值加到数组末尾,注意在 ensureCapacityInternal 方法中会将 modCount+1。add(int index, E element) 方法在指定位置添加元素,首先判断 index 的范围,确保数组容量足够,然后将数组中从 index 开始的数据都向后移动一位,然后将 index 位置设置成插入的 element
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    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));
}
remove(int index) 方法,先取得 index 位置的 value,用于删除后返回。然后把 index+1 开始的所有数据左移一位,然后size-1,并且将数组中 size-1 的位置设置为 null方便虚拟机GC。remove(Object o) 是删除一个对象,像查找一样,需要遍历数组查找第一个和它相同的对象的index,然后调用 fastRemove 直接将它删除,fastRemove 和 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;
}

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
}
addAll(Collection<? extends E> c) 方法调用的底层的 System.arraycopy 方法进行拷贝。addAll(int index, Collection<? extends E> c) 方法内部需要调用两次 System.arraycopy,第一次将 elementData 的index位置开始的数据向后移动,为待插入的集合腾出位置,然后再调用一次,将待插入集合所有数据复制到从index开始的区域中
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;
}
区域删除也是类似,先将 toIndex 后的数据都移动到 fromIndex 开始的区域上,然后将 newSize 位置以后的数据都置为null,并且重置 size 的值
protected void removeRange(int fromIndex, int toIndex) {
    modCount++;
    int numMoved = size - toIndex;
    System.arraycopy(elementData, toIndex, elementData, fromIndex,
                     numMoved);

    // clear to let GC do its work
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        elementData[i] = null;
    }
    size = newSize;
}
removeAll 和 retainAll 都是调用的 batchRemove 方法,只是传入的complement参数不同。在 batchRemove 方法中会按照size遍历数组,判断集合 c 是否包含这个元素,如果这个 boolean 值和传入的 complement 参数相等(即 removeAll 是求差集,retainAll 是求交集),则从数组的头位置依次保存这些元素。在遍历完后 finally 代码块中再进行操作,注意在这里首先判断了如果 try 中出错的情况,如果出错了,则 r 一定不等于 size,则将数组出错位置 r 以后的所有元素复制到数组的 w 位置开始的区域,并将 w 置为复制完成后最后一个元素的位置。然后再将数组中 w 后的位置全部置位 null,并重新调整 size 的值。
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}


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

iterator 方法和 listIterator 方法分别返回了 Itr 类和 ListItr 类。Itr使用了游标 cursor 来维护当前元素,以实现 next 等方法。ListItr继承自Itr,但是提供了向前遍历的方法 previous

public ListIterator<E> listIterator() {
    return new ListItr(0);
}

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

subList 方法返回指定范围的一个子集合(这是一个内部类,SubList中维护了父 List 的引用,并且记录了一个偏移量 offset,以便确认索引在父 List 中的真实索引,例如在 SubList 的 set 方法中,所以subList方法并没有生成一个新的数组),前闭后开

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}
sort方法直接调用了Arrays.sort方法
public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}
查看评论

【JDk源码解析之一】ArrayList源码解析

1.ArrayList的继承关系如图所示: 2.宏观上说,ArrayList是基于动态数组实现的,数组具有按索引查找的特性,所以访问很快,适合经常查询的数据。 3.具体源码解析。    为什么说A...
  • weililansehudiefei
  • weililansehudiefei
  • 2017-04-15 02:12:21
  • 1070

Java笔记---ArrayList源码分析

一、前言一直就想看看java的源码,学习一下大牛的编程。这次下狠心花了几个晚上的时间,终于仔细分析了下 ArrayList 的源码(PS:谁说的一个晚上可以看完的?太瞎扯了)。现在记录一下所得。二、A...
  • GuLu_GuLu_jp
  • GuLu_GuLu_jp
  • 2016-05-19 21:48:19
  • 6455

java集合框架03——ArrayList和源码分析

上一章学习了Collection的架构,并阅读了部分源码,这一章开始,我们将对Collection的具体实现进行详细学习。首先学习List。而ArrayList又是List中最为常用的,因此本章先学习...
  • eson_15
  • eson_15
  • 2016-04-11 15:05:57
  • 3752

ArrayList源码分析(JDK1.8)

ArrayList源码分析JDK1.8
  • chun0801
  • chun0801
  • 2016-05-23 15:14:32
  • 4693

面试必备:ArrayList源码解析(JDK8)

正巧最近在看`jdk`的`Collection`集合源码,这种单个类的源码解析,写起来还算比较方便。 **关键代码处加上注释,核心处做个总结**,就可以成文,拿出来和大家**讨论分享。** 且网上绝大...
  • zxt0601
  • zxt0601
  • 2017-08-16 22:52:44
  • 1622

Java容器类源码-ArrayList的最全的源码分析

本文是针对Java 1.8的源代码进行解析的,可能会和其他版本有所出入。 笔者技术真的是一般般,写这个为了加深理解的同时给害怕看源代码的朋友一点鼓励,所以笔者在写的过程中有查阅很多资料来努力减少错误,...
  • ljcITworld
  • ljcITworld
  • 2016-07-27 08:36:16
  • 49957

Java源码解读-ArrayList

写在前面 本文是针对Java 1.8的源代码进行解析的,可能会和其他版本有所出入。 转载自http://blog.csdn.net/ljcitworld/article/details/520...
  • dadaxiongdebaobao
  • dadaxiongdebaobao
  • 2016-08-04 22:05:37
  • 1007

Java集合框架--ArrayList源码解析(JDK1.7)

ArrayList类是List接口的实现类,ArrayList可以看成一个动态数据,可以根据大小动态变化。由于其数组的底层结构,所以它的访问速度非常快,删除和插入操作较慢。...
  • m0_37241851
  • m0_37241851
  • 2017-03-02 12:33:59
  • 149

Android的数据结构与算法----ArrayList源码解析

首先得明白ArrayList在数据结构中是个什么,从名字看,可以直译为“数组集合”,内部的实现八九不离十是用数组来实现的,因此在数据结构中属于线性表结构(0个或者多个元素的有限序列)...
  • abren32
  • abren32
  • 2017-02-23 13:01:27
  • 297

ArrayList源码分析(基于JDK8)

ArrayList 源代码解读,以及ArrayList特点总结
  • fighterandknight
  • fighterandknight
  • 2017-03-12 00:23:19
  • 3099
    个人资料
    等级:
    访问量: 41
    积分: 46
    排名: 181万+
    文章存档