源码阅读系列——基础篇(ArrayList 集合源码分析)

ArrayList是我们非常常用的一个线程不安全的List集合。ArrayList和Vector大体比较相似。我们继续阅读源码找答案。

1、类定义

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final int DEFAULT_CAPACITY = 10; // 默认容量
    private static final Object[] EMPTY_ELEMENTDATA = {}; // 无元素的默认数组,见构造函数1
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 无元素的默认数组定义,见构造函数2
    transient Object[] elementData; // 元素数组
    private int size;  // 元素数量
   
    // 构造函数1:指定大小的构造函数
    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);
        }
    }

    // 构造函数2
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
}

从上面的源码,得出以下:
ArrayList默认是一个空数组。只有制定长度时,才会生产一个定长数组。

2、扩容机制

已增加元素为例

public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index); // 整体后移index后的所有元素
    elementData[index] = element;
    size++;
}

我看一下ensureCapacityInternal实现

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); //如果当前长度不到DEFAULT_CAPACITY(10)的长度,先设置为DEFAULT_CAPACITY
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);// 原来长度,加上原长度右移一位(相当于1.5倍原长度)
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0)
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

由源码得出:
1、如果长度不到10,则先扩容到10。
2、如果长度大于10了,则每次扩容1.5倍。比Vector每次扩容2倍,省空间,如果不频繁扩容,效率差不多。

3、迭代器

我们常常有这样的需求,遍历数组的过程中,修改元素,但是如果像下面这样的写法,一定会有数组越界的问题:

public void removeElement(int value){
    for (int i = 0 ; i < elementList.size() ; i++){
        if(value == elementList.get(i).value){
            elementList.remove(i);
        }
    }
}

Java提供了迭代器的方式来满足以上需求,看一下迭代器的主要代码:

private class Itr implements Iterator<E> {
        protected int limit = ArrayList.this.size;

        int cursor;       // 游标,可以理解为遍历数组的指针(或者索引)。
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount; // modCount表示List被修改操作的次数。(我们在List里面的所有影响list数组的操作,都会自增1,比如List.remove(),List.clear())

        public boolean hasNext() {
            return cursor < limit;  // 是否已经遍历到头
        }

        @SuppressWarnings("unchecked")
        public E next() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException(); // 如果发现迭代器在遍历的过程中,modCount发生变化了,则抛异常,这就是Fast-fail机制
            int i = cursor;
            if (i >= limit)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException(); // 发现数组长度越界,抛异常(理论上遍历的过程不会越界,所以理解迭代器之外有改动)
            cursor = i + 1;  // 索引后移一位,表示下一个要被遍历的元素
            return (E) elementData[lastRet = i]; // lastRet赋值为我们当前遍历的元素
        }

        public void remove() {
            if (lastRet< 0)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

            try {
                ArrayList.this.remove(lastRet); // 移除当前遍历到的元素
                cursor = lastRet;// 下一个遍历的元素,位置index也需要-1
                lastRet = -1;
                expectedModCount = modCount; // 更新modCount
                limit--;   // 长度减1
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

从上面的源码解析,我们大致可以发现迭代器的作用:他其实可以理解建立了一个封闭的空间,可以让开发者在遍历的同时修改元素,甚至影响到元素数组的大小。而由于迭代器里面实现一套对长度、遍历索引的动态变更,所以在遍历的过程中,我们不能在迭代器之外对数组有任何改动。

你可能感兴趣的:(java,数据结构,java)