Java 数据结构源码探究(一)—— ArrayList

413 阅读3分钟

线性表—— 顺序存储结构

线性表 顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

Java ArrayList

对象的定义

/**
 * Resizable-array implementation of the List interface
 */
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    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 = {};

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

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

本质是一个 Object[] 数组。

CRUD

Construct

/**
 *
 * Params: initialCapacity – the initial capacity of the list
 * Throws: IllegalArgumentException – if the specified initial capacity is negative
 */
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;
}

Add

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


public void add(int index, E element) {
     //检查 index 合法性 
     rangeCheckForAdd(index);
     //数组有足够的空间来进行数组 copy
     ensureCapacityInternal(size + 1);  // Increments modCount!!
     //数组 copy 
     System.arraycopy(elementData, index, elementData, index + 1, size - index);
     //进行 add 操作
     elementData[index] = element;
     //增加 size 
     size++;
}

/**
  * src – the source array.
  * srcPos – starting position in the source array.
  * dest – the destination array.
  * destPos – starting position in the destination data.
  * length – the number of array elements to be copied.
  */
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

Remove

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

/*
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 */
private void fastRemove(int index) {
    modCount++;
    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
}

/*
 * Removes all of the elements from this list. The list will be empty after this call returns.
 */
public void clear() {
    modCount++;
    // clear to let GC do its work
    for (int i = 0; i < size; i++) elementData[i] = null;
    size = 0;
}

Get

public E get(int index) {
  //判断数组越界
   rangeCheck(index);
   return elementData(index);
}


public E set(int index, E element) {
  //判断数据越界
   rangeCheck(index);
   E oldValue = elementData(index);
   elementData[index] = element;
   return oldValue;
}

public int lastIndexOf(Object o) {
   if (o == null) {
      for (int i = size-1; i >= 0; i--)
      	if (elementData[i]==null) return i;
        } else {
        	for (int i = size-1; i >= 0; i--)
          	if (o.equals(elementData[i])) return i;
        }
        return -1;
}

数组长度和线性表长度区别

数组长度

数组长度是存放线性表的存储空间的长度,存储分配后这个量是一般是不会变的。一般像 Java 这种高级语言,用编程手段实现动态分配数组,但是存在一定性能消耗。

Java ArrayList 添加元素时,保证数组长度够用。

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;
    // 扩容 1.5倍
    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);
}


线性表长度

线性袤的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的。

线性表顺序存储结构的优缺点

优点

  • 无须为表示表中元素之间的逻辑关系而增加额外的存储空间。
  • 可以快速获取表中任一位置元素。

缺点

  • 插入或者删除操作需要移动大量的元素。
  • 当线性表长度变化较大时,难以确定存储空间容量,进行扩容存在性能损耗。
  • 造成存储空间的碎片。

ArrayList 总结

  • ArrayList 底层实现为 Object[]
  • 指定位置的 add 与 remove 核心逻辑均为 System.arraycopy