ArrayList | Java 容器

99 阅读3分钟

简介

  1. ArrayList是一个基于数组实现的常用的容器类,实现了List接口,支持插入null。除了实现List接口基本方法外,还提供了方法能够手动操作内部用于存储数据的数组大小。
  2. size, isEmpty, get, set, iterator以及 listIterator等方法均为均摊常量级别时间复杂度。其他操作均为线性时间完成。ArrayList的时间复杂度要低于 LinkedList
  3. 每个ArrayList实例都有一个capacity属性,这个属性代表内部存储元素的数组的长度,capacity大于等于实际存储元素的数量。每当往ArrayList中添加新元素的时候,Capacity会自动调整大小。在使用过程中,如果存在添加大量数据的情况,可以使用ensureCapacity方法提前扩充capacity,这样会减少频繁修改数组大小和重新赋值操作的时间开销。
  4. ArrayList是非线程安全的。

类变量

private static final int DEFAULT_CAPACITY = 10;
  • 内部数组默认大小为10,但是实例化的时候并不立马创建默认大小的数组,只有首次add操作的时候才会创建长度为10的数组。

成员变量

transient Object[] elementData;
private int size;
  • elementData为内部存储数据的数组
  • size为所存数据的实际个数

构造函数

  • 无参构造函数
/**
   * Constructs an empty list with an initial capacity of ten.
   */
public ArrayList() {
     this.elementData = 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);
        }
    }

如果初始容量大于0则新建一个初始容量的数组,如果等于0则将EMPTY_ELEMENTDATA赋值给elementData,不然就抛异常。 *有参构造函数-传入集合

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

构造函数

  • 添加操作-add(E e)
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
	
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);
    }

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);
    }
  1. 如果是首次添加元素则新建容量为默认大小10的数组
  2. 如果非首次添加元素且超出了当前数组的长度,则扩容至1.5倍长度
  • 添加操作-add(int index, E element)
public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

在指定索引位置插入数据,首先确保数组长度足够,不够就扩容。然后将索引的后半部分全量右移一位,这里用到的方法是System.arraycopy,最后在index位置进行赋值。AddAll的操作也是差不多,首先确保数组长度足够,如果直接在后面追加值就将新的数组复制过去,如果在中间插值则将原先的数组复制后移一次然后再进行复制及需要两次System.arraycopy操作。

删除操作-remove(int index)

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

        modCount++;
        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;
    }

index右侧全量左移一位,并将最右侧的值赋为null,左移使用的方式同样也是System.arraycopy

  • trimToSize
public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

通过trimToSize可以将多余的数组空间给去除掉,如果数组确定不会再有变化可以通过这个方法进行程度的降内存。

  • ensureCapacity
public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

提前扩充数组长度的方法,如果提前知道数组添加的量,可以调用这个方法预置数组的大小。预置的思路也是优先扩充至当前容量的1.5倍,如果不够直接用minCapacity。这个方法再数据量较大的情况下会大大缩减赋值的时间。