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

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;
tri mToSize 方法将此 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++;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JDK8.0是Java虚拟机的最新版本,加入了很多新的特性。Oracle甲骨文公司Oracle公司如期发布了Java 8正式版!现在你就可以下载Java 8正式版了,同期发布的还有JDK 8。JDK8的特性已经基本准备就绪,虽然一些特性还不是特别完善,但开发人员已经开始庆祝这一里程碑事件了。 据官方公告介绍:Java 8是款革命性开发平台,其全面升级现有Java编程模式,带来一项协同开发的JVM、 Java语言以及库。Java 8平台集合生产力、易用性、多语种编程、更高安全性和稳定性等特色。 JDK(Java Development Kit)是整个Java的核心,包括了Java运行环境、Java工具和Java基础类库。JDK作为JAVA开发的环境,运行JAVA程序不可缺少的环境,不管是做JAVA开发还是做安卓开发,都必须在电脑上安装JDK。 Oracle甲骨文公司Oracle公司推出了Java SE 8 Update 51 (JDK 8u51) 最新版!该版更新主要例行更新数据、安全补丁、修复BUG! Java SE 8自去年发布以来,应用率已创造了纪录。官方公告介绍:Java 8是款革命性开发平台,其全面升级现有Java编程模式,带来一项协同开发的JVM、Java语言以及库。Java 8平台集合生产力、易用性、多语种编程、更高安全性和稳定性等特色。 经过2年半的努力、屡次的延期和9个里程碑版本,甲骨文的Java开发团队终于发布了Java 8正式版本。Java 8版本最大的改进就是Lambda表达式,其目的是使Java更易于为多核处理器编写代码;其次,新加入的Nashorn引擎也使得Java程序可以和JavaScript代码互操作;再者,新的日期时间API、GC改进、并发改进也相当令人期待。另外,原本要加入Java 8的Jigsaw项目(标准模块系统)由于开发时间关系,被推迟到了Java 9中,不过Java 8已经在朝着这个方向努力了。 Java SE 8 有哪些主要特性? Lambda表达式,一种新的语言特性,能够使功能作为方法参数或将代码作为数据; 显著增加和完善Java语言特性,增添标准库,包括默认方法,新的java.util.stream包及 Date-Time API; Compact Profiles包含 Java SE平台预定义子集,使程序员无需部署在整个平台,可运行在小型设备上; 安全性包括更新可Java加密体系架构;限制doPrivileged;SSL/TLS Server Name Indication (SNI) Extension以及增强密钥库; JavaFX更新文档 新的JavaScript引擎Nashorn
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值