Fork me on GitHub

Java集合--ArrayList源码

ArrayList是Java的动态数组集合,主要用于快速读取数组元素,且在读多写少的情况下具有较好的表现;相比于LinedList,更节省空间,因为LinkedList的元素还要多存储前后继节点的指针,相较于ArrayList只存储元素本身有一定的差距,但是ArrayList在使用不当的时候也容易浪费内存,例如大量数据不断写入ArrayList时,会不断分配新的内存,并复制原有数据到新的内存中。

1  /**
2      * The array buffer into which the elements of the ArrayList are stored.
3      * The capacity of the ArrayList is the length of this array buffer. Any
4      * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
5      * will be expanded to DEFAULT_CAPACITY when the first element is added.
6      */
7     transient Object[] elementData; // non-private to simplify nested class access

elementData数组是ArrayList最重要的属性之一,用于存储实际的数据;elementData为什么会被定义为transient关键字修饰呢?因为ArrayList多数情况下会出现空余数组空间,如果序列化全部的数组,那么会造成时间和空间的额外消耗;ArrayList提供了自己的writeObject方法和readObject方法,只序列化了实际存储的元素。

 /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

size是elementData数组中实际存储的元素的个数,区别于elementData.length,在ArrayList中,elementData数组的长度是ArrayList的实际开辟空间的长度

 /**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),
     * which helps when add(E) is called in a C1-compiled loop.
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow(); //grow()方法扩充开辟新空间,并复制数据到新空间
        elementData[s] = e;
        size = s + 1;
    }

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

从add(E e)方法中可以分析出,数组中当前元素的个数等于数组的长度时,会增长当前数组的长度;

/**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

    private Object[] grow() {
        return grow(size + 1);
    }

/**
     * Returns a capacity at least as large as the given minimum capacity.
     * Returns the current capacity increased by 50% if that suffices.
     * Will not return a capacity greater than MAX_ARRAY_SIZE unless
     * the given minimum capacity is greater than MAX_ARRAY_SIZE.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    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);
    }

扩充数组容量时,会成倍增长,如果数据量较大又未开辟足够的空间,且不断写入新数据的时候,ArrayList需要不断开辟新空间又要复制数据,性能较差且可能浪费内存空间;

在add(),set(),remove()等写方法中,会看到modCount得增加,这个字段有什么作用呢?在AbstractList中能看到modCount的定义

 /**
     * The number of times this list has been <i>structurally modified</i>.
     * 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.
     *
     * <p>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
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
    protected transient int modCount = 0;

modCount用来记录链表被修改的次数;典型的场景是:当迭代器对链表操作时,其他方法修改了该链表,那么通过对比modCount的值就可以判断链表在使用的时候是否被修改过,如果被修改过,立即抛出ConcurrentModificationException异常。这就是AbstractList的fail-fast机制,而不是出现不确定的行为。

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

checkForComodification()方法定义在ArrayList的私有内部类中,迭代器常用的next方法和remove方法都会调用该方法,用于快速检测链表是否被其他行为修改

 

CopyOnWriteArrayList是一个写时拷贝的动态数组,是线程安全的;

    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();
/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

在进行add操作的时候,首先获取重入锁,更新数组的副本后在将副本赋值给array。读操作不会加锁

posted @ 2019-02-19 23:23  gitmoji  阅读(209)  评论(0编辑  收藏  举报