【Java集合】ArrayList底层原理

1 前言

本人使用的是jdk1.8版本。

2 List集合继承结构

3 底层实现

可以看到底层是通过一个Object[] elementData来存储数据的,在不指定初始容量的情况下(空参构造)默认容量为10,不过要在第一次add操作时才会为elementData变量赋值(懒扩充)。

public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable
{
    // 默认初始容量
    private static final int DEFAULT_CAPACITY = 10;

    // 最大容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    // 空参构造一个ArrayList时,elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,在第一次添加            
    // 元素时将elementData 长度扩充到10(懒扩充)
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 存放数据的仓库
    transient Object[] elementData;

    // ArrayList中已添加的元素的个数
    private int size;

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

    public ArrayList(int initialCapacity) {    ......    }

}

4 基本操作和扩容方法

这里基本操作只选取了一种,实现中有多个重载方法。

4.1 add()

可以看到add方法首先会进行扩容判断,若添加成功结果返回true。调用成功modCount+1,modCount在下面会有介绍

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 扩容判断及实现
        elementData[size++] = e;
        return true;
    }

4.2 remove()

调用成功modCount+1.

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

        modCount++;                            // 修改次数+1
        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;                        // 返回被删除的元素对象
    }

4.3 set()

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

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

4.4 get()

get方法也十分简单,首先判断给定索引是否越界,否则返回指定位置的元素。

    public E get(int index) {
        rangeCheck(index);            // 判断索引是否越界

        return elementData(index);
    }

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

4.5 扩容方法

ArrayList中的扩容方法是ensureCapacityInternal(),与StringBuilder的扩容机制很相近,可以参考:string、StringBuilder和StringBuffer。在ArrayList中,默认扩容机制是newCapacity = oldCapacity * 1.5。若默认扩容长度不够,则取elementData.length + addCount;若elementData.length + addCount为负数或者大于Integer.MAX_VALUE则会抛出OutOfMemoryError。从下面代码可知:elementData的长度最大可以取到Integer.MAX_VALUE,并不受到字段MAX_ARRAY_SIZE的限制,这里要注意一下。

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    // 空参构造则赋给elementData默认容量
    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++;                                // 修改次数+1

        // 若elementData不能满足容量要求,则进行扩容
        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;           // 若默认长度还不能满足需求,取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);
    }

    // 若minCapacity为负数,则抛出OutOfMemoryError
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?    
            Integer.MAX_VALUE :                // minCapacity最大能取到Integer.MAX_VALUE
            MAX_ARRAY_SIZE;
    }

5 modCount介绍

modCount是ArrayList的父类AbstractList中的一个字段,通过字段上的注释可以知道:

modCount记录了当前list容器进行“结构性”修改的次数,通俗点讲就是所有改变list容器size字段值的操作,如:add、addAll、remove、clear。每当调用add等方法时,modCount会+1。

该字段主要用在迭代器中,如通过iterator和ListIterator方法返回的迭代器类中。当返回一个迭代器类时,迭代器会记录下当前modCount的值,在通过迭代器遍历集合容器的过程中(使用迭代器的next、previous、set、remove、add方法),会一直判断modCount与一开始记录的值是否相同,若不同则抛出ConcurrentModificationException。

    /**
     * The number of times this list has been structurally modified.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * 

This field is used by the iterator and list iterator implementation * returned by the {@code iterator} and {@code listIterator} methods. * If the value of this field changes unexpectedly, the iterator (or list * iterator) will throw a {@code ConcurrentModificationException} in * response to the {@code next}, {@code remove}, {@code previous}, * {@code set} or {@code add} operations. This provides * fail-fast behavior, rather than non-deterministic behavior in * the face of concurrent modification during iteration. */ protected transient int modCount = 0;

6 Iterator()和ListIterator()

ArrayList中有Iterator()和ListIterator(),这两个方法都是继承自AbstractList,但进行了优化,分别调用后会返回Itr和ListItr两个对象,对集合容器的迭代可以通过这两个对象提供的方法进行。

6.1 Itr类底层介绍

该类提供了next()和remove()两个方法来对集合进行迭代。

    private class Itr implements Iterator {
        int cursor = 0;                    // 下一次调用next()会返回的元素的索引
    
        int lastRet = -1;                  // 最近一次调用next()返回的元素的索引

        int expectedModCount = modCount;   // 记录下对象创建时modCount字段的值

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

        public E next() {
            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();
            checkForComodification();                 // 先进行修改检查

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                // 删除后将expectedModCount重置为,modCount,下一次调用    
                // checkForComodification()就不会抛出异常
                expectedModCount = modCount;                      
                    
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        // 若在迭代器创建之后对容器进行了增删操作,则抛出异常
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

6.2 ListItr

ListLtr继承了Itr,并且增加了previous、set、add等方法,功能比Itr类更强大,一般更推荐用ListLtr来对集合进行迭代。

private class ListItr extends Itr implements ListIterator {
    ListItr(int index) {    cursor = index;    }

    public boolean hasPrevious() {    return cursor != 0;    }

    public E previous() {    checkForComodification();   ......    }

    public int nextIndex() {    return cursor;    }

    public int previousIndex() {    return cursor-1;    }

    public void set(E e) {    checkForComodification();   ......    }

    public void add(E e) {    checkForComodification();   ......    }
}

6.3 checkForComodification()

当返回一个迭代器类时,迭代器会记录下当前modCount的值,在通过迭代器遍历集合容器的过程中(使用迭代器的next、previous、set、remove、add方法),会一直判断modCount与一开始记录的值是否相同,若不同则抛出ConcurrentModificationException。

之所以抛出异常,是因为调用集合的add、remove等方法后会使modCount+1。正因为考虑到这个因素,所以Ltr和ListLtr提供了对集合的操作来代替集合本身的add、remove等操作,在上面Ltr的remove方法中可以看到,删除指定位置的元素后,会使 expectedModCount = modCount;这样集合的状态就又恢复到初始时了。

你可能感兴趣的:(java基础)