ArrayList源码分析

ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类。该类封装了一个动态再分配的Object[]数组,当向ArrayList中添加元素时,该属性值会自动增加。如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能。

1、继承结构

ArrayList通过继承AbstractList实现接口中一些通用的方法,而具体的类,然后自己在实现一些自己特有的方法,这样一来,让代码更简洁。

ArrayList还实现了一个RandomAccess接口,这个接口本社没有定义任何方法只是作为一个标记,List实现使用的标记接口,表示它们支持快速(通常是恒定时间)随机访问,具体原理下面会分析。

2、源码分析

2.1、类中定义的属性

    // 缺省容量,若构造方法没有指定初始容量,当添加元素的时候默认为数组分配10的长度
    private static final int DEFAULT_CAPACITY = 10;
    // 空对象数组,所有容量为0的对象elementData属性共用此值节约内存
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 缺省空对象数组,当构造方法未指定初始容量时elementData赋此值
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 元素数组
    transient Object[] elementData;
    // 元素个数大小,小于等于elementData.length
    private int size;
    // 最大数组容量,ArrayList容量时有限的
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

2.2、AbstractList的实现

AbstractList是实现了Iterable、Collection和List接口的抽象类,其中最主要的就是实现了返回迭代器的方法。

2.2.1、iterator()方法

iterator()方法是Iterable接口定义的方法,下面看一下它的实现。

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

Itr是AbstractList的私有内部类。

private class Itr implements Iterator<E> {
    // 后续调用next返回的元素索引。
    int cursor = 0;

    // 最近一次调用返回到下一个或上一个的元素索引。 如果通过删除调用删除此元素,则重置为-1。
    int lastRet = -1;

    // 迭代器认为后备列表应具有的modCount值。 如果违反了此期望,则迭代器已检测到并发
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size();
    }

    public E next() {
        // 每次进行迭代之前都要检测一下迭代器创建时的版本和List最新版本是否一致,不一致抛出异常
        checkForComodification();
        try {
            int i = cursor;
            E next = get(i);
            lastRet = i;
            cursor = i + 1;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        //  每次删除元素之前都要检测一下迭代器创建时的版本和List最新版本是否一致,不一致抛出异常
        checkForComodification();

        try {
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            // 删除元素后更新expectedModCount 
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }
    // 检测一下迭代器创建时的版本和List最新版本是否一致,不一致抛出异常,目的是为了防止并发操作List
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

Itr有一个成员变量expectedModCount ,初始值被赋值为modCount,modCount是AbstractList内定义的protected变量,此变量会在该类的对象结构改变的时候自增,如在添加或删除元素的时候如下(ArrayList的添加操作),如果在使用iterator方法返回Itr类型的迭代器进行元素迭代期间如果有对该迭代器所属的List进行添加或删除操作就会更改modCount(版本号)值,待到下次迭代操作调用next方法时检测迭代器版本号与List最新版本号不相同就会抛出ConcurrentModificationException异常。

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

以上就是不能再迭代元素的时候对List进行添加或删除的原因,但是如果需要在迭代的时候删除元素可是使用迭代器的remove方法,因为此方法删除元素时会将modCount++,随后又将expectedModCount 更新为最新的modCount,这样在每次检测版本号的时候又是相同的。

2.2.2、listIterator方法

listIterator方法是List接口定义的方法。

public ListIterator<E> listIterator(final int index) {
    // index在0到size之间
    rangeCheckForAdd(index);

    return new ListItr(index);
}

ListItr继承于Itr,增加了向前访问和增加元素的方法。

2.3、构造方法

ArrayList有三个构造方法:

2.3.1、无参构造方法

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

将elementData 赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空数组,但是在首次添加元素的时候会将elementData 初始化为一个大小的10的数组,这个下面将。

2.3.2、带有int参数的构造方法

/**
 * 构造具有指定初始容量的链表
 */
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);
    }
}

当initialCapacity==0的时候使用上面类变量EMPTY_ELEMENTDATA代表的空数组,当initialCapacity>0的时候将elementData 初始化为一个数组长度为initialCapacity的Object数组。

2.3.3、带有Collection参数的构造方法

/**
 * 按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表。
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // defend against c.toArray (incorrectly) not returning Object[]
        // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

此方法使用一个已有的List复制elementData中的元素到一个新的ArrayList对象,并计算好size。

2.4、add()方法

2.4.1、将指定的元素追加到此列表的末尾

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

modCount++是为了防止迭代的时候修改List,之后调用add的重载方法。

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

该方法是向elementData指定位置s添加元素e,在添加时先判断如果elementData数组满了,则先调用grow()方法扩容elementData。

private Object[] grow() {
    return grow(size + 1);
}
private Object[] grow(int minCapacity) {
    return elementData = Arrays.copyOf(elementData,
                                       newCapacity(minCapacity));
}

grow方法内调用newCapacity方法新数组的大小,然后拷贝elementData 元素到新数组在赋值给elementData完成扩容。

newCapacity方法返回至少与给定最小容量一样大的容量。 如果满足,则返回当前容量增加50%。 除非给定的最小容量大于MAX_ARRAY_SIZE,否则不会返回大于MAX_ARRAY_SIZE的容量。

private int newCapacity(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity <= 0) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return minCapacity;
    }
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity
        : hugeCapacity(minCapacity);
}

2.4.2、将指定的元素追加到指定的位置

public void add(int index, E element) {
    // 检查index不能超范围
    rangeCheckForAdd(index);
    modCount++;
    final int s;
    Object[] elementData;
    // 空间不足扩容
    if ((s = size) == (elementData = this.elementData).length)
        elementData = grow();
    System.arraycopy(elementData, index,
                     elementData, index + 1,
                     s - index);
    // 插入到指定位置
    elementData[index] = element;
    size = s + 1;
}

2.4.3、将指定的元素集合追加到此列表的末尾

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    modCount++;
    int numNew = a.length;
    if (numNew == 0)
        return false;
    Object[] elementData;
    final int s;
    if (numNew > (elementData = this.elementData).length - (s = size))
        elementData = grow(s + numNew);
    System.arraycopy(a, 0, elementData, s, numNew);
    size = s + numNew;
    return true;
}

2.5、remove方法

2.5.1、删除指定位置上的元素

public E remove(int index) {
    Objects.checkIndex(index, size);
    final Object[] es = elementData;

    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    fastRemove(es, index);

    return oldValue;
}

删除元素的逻辑在fastRemove方法中。

private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}

可以看到删除元素不会重新分配elementData,elementData只能变大不能变小。

2.5.2、删除指定元素

public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}

遍历elementData数组通过equals比较找到元素位置,调用fastRemove删除元素。

2.6、clear方法

public void clear() {
    modCount++;
    final Object[] es = elementData;
    for (int to = size, i = size = 0; i < to; i++)
        es[i] = null;
}

将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉。

2.7、set方法

用指定的元素替换此列表中指定位置的元素,可以看到此方法并不会修改modCount。

public E set(int index, E element) {
    Objects.checkIndex(index, size);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

2.8、indexOf()方法

indexOf()方法并没有使用父类已经实现的,父类AbstractList实现的逻辑是通过迭代的方式,这样会很慢而ArrayList是基于数组的实现,按索引访问很快。

3、RandomAccess接口有何作用

ArrayList实现RandomAccess接口,但是RandomAccess接口里面是空的!LinkedList并没有实现RandomAccess接口。

只要List集合实现这个接口,就能支持快速随机访问,然而又有人问,快速随机访问是什么东西?有什么作用?

通过查看Collections类中的binarySearch()方法,源码如下:

public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
    if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
        return Collections.indexedBinarySearch(list, key);
    else
        return Collections.iteratorBinarySearch(list, key);
}

可以看到如果List实现了RandomAccess 接口,则是使用二分查找,因此这个方法传入的list需要是一个升序排列的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值