解析ArrayList

ArrayList剖析

数据结构概念

ArrayList的数据结构的本质是数组列表,是一段连续的空间,它支持对元素的快速随机访问(Random Access),即使用了索引遍历,通过ArrayList首位的地址+偏移量(即下标)来查找元素。

非线程安全的。

源码层次

  1. 继承抽象类与接口
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{......}
  1. 遍历效率问题

实现了RandomAccess接口,意味着索引遍历(for循环遍历)快于迭代器遍历

RandomAccess标志接口描述如下:


/**
 * <pre>
 *     for (int i=0, n=list.size(); i &lt; n; i++)
 *         list.get(i);
 * </pre>
 * runs faster than this loop:
 * <pre>
 *     for (Iterator i=list.iterator(); i.hasNext(); )
 *         i.next();
 * </pre>
 *
 * <p>This interface is a member of the
 * <a href="{@docRoot}/../technotes/guides/collections/index.html">
 * Java Collections Framework</a>.
 *
 * @since 1.4
 */
public interface RandomAccess {
}
  1. 构造

ArrayList有两个构造方法:

private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

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);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

可见,无参构造实际是建立了一个空数组,长度为0;有参构造传入参数即需要创建的数组大小。

  1. 扩容机制

    在ArrayList中添加元素时使用add方法

    public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
    }
    

    可见,其第一步是增加长度ensureCapacityInternal(size + 1),看看ensureCapacityInternal方法

    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    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++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
    }
    

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
} return minCapacity;

可见如果在添加元素时,数组为空数组,则初始化一个长度为10的数组,否则就 +1(因为minCapacity = size + 1)

if (minCapacity - elementData.length > 0)
            grow(minCapacity);

通过比较,当原来的数组长度小于所需长度,则调用grow扩容,现在看看grow方法

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);
}

注意,此方法使用的是int newCapacity = oldCapacity + (oldCapacity >> 1);

所以扩容是加上向右移动一次的大小,即每次扩容是原来大小的1.5倍

扩容后要确定新数组长度是否合理

下一步elementData = Arrays.copyOf(elementData, newCapacity);

将老数组的元素copy到新的已经扩容的数组中。


有错误请联系笔者或在评论区指出,感谢


posted @ 2020-07-13 16:18  Faink  阅读(172)  评论(0)    收藏  举报