1. 程式人生 > >List源碼解析之ArrayList源碼分析

List源碼解析之ArrayList源碼分析

mac 構造函數 tin final 源碼解析 避免 ++ 說明 http

ArrayList簡介

ArrayList是基於數組實現的, 是一個動態擴展的數組,容量可自動增長。
ArrayList是非線程安全的,只能在單線程環境下使用,多線程環境考慮使用Collections.synchronizedList(List list)函數返回一個線程安全的ArrayList類,也可以使用concurrent並發包下的CopyOnWriteArrayList類。
ArrayList實現了Serializable接口,因此它支持序列化,能夠通過序列化傳輸,實現了RandomAccess接口,支持快速隨機訪問,實際上就是通過下標序號進行快速訪問,實現了Cloneable接口,能被克隆。

屬性和構造函數

private static final int DEFAULT_CAPACITY = 10; // 默認初始值
transient Object[] elementData; // 存放數據的數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 存放的數組默認容量10
protected transient int modCount = 0; // List被修改的次數

// 構造函數
/**
 * Constructs an empty list with the specified initial capacity.
* * @param 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() { // 默認容量10的數組 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 構造一個包含指定collection 的元素的列表,這些元素按照 * 該collection 的叠代器返回它們的順序排列的 */ 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; } }

存儲

set(int index, E element)

// 用指定的元素替代此列表中指定位置上的元素,並返回以前位於該位置上的元素    
public E set(int index, E element) {
  rangeCheck(index); // 越界檢測

  E oldValue = elementData(index);
  elementData[index] = element; // 賦值到指定位置,復制的僅僅是引用
  return oldValue;
}

private void rangeCheck(int index) {
  if (index >= size)
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

add(E e)

// 添加一個元素到此列表的尾部 時間復雜度O(1),非常高效
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 容量 + 1 
    elementData[size++] = e;
    return true;
}

// 如有必要,增加此 ArrayList 實例的容量,以確保它至少能夠容納最小容量參數所指定的元素數
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
      minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(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); // 擴容到原始容量的1.5倍
    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); // 擴容並復制
    // 由於Java GC自動管理了內存,這裏也就不需要考慮源數組釋放的問題。
  // 關於Java GC這裏需要特別說明一下,有了垃圾收集器並不意味著一定不會有內存泄漏。對象能否被GC的依據是是否還有引用指向它,上面代碼中如果不手動賦null值,除非對應的位置被其他元素覆蓋,否則原來的對象就一直不會被回收。
}


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

技術分享圖片

空間問題解決後,插入就變得容易了

技術分享圖片

add(int index, E element)

// 將指定的元素添加到此列表中的指定位置
// 如果當前位置有元素,則向右移動當前位於該位置的元素以及所有後續元素(將其索引加1)
public void add(int index, E element) {
    rangeCheckForAdd(index); // 數組越界檢測
    // 如果數組長度不足,將進行擴容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 將 elementData 中從Index 位置開始、長度為size-index 的元素,
    // 拷貝到從下標為index+1 位置開始的新的elementData 數組中。
    // 即將當前位於該位置的元素以及所有後續元素右移一個位置。
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);  // 低效
    elementData[index] = element;
    size++;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
      throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

addAll(Collection<? extends E> c)

// 按照指定collection 的叠代器所返回的元素順序,將該collection 中的所有元素添加到此列表的尾部
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

addAll(int index, Collection<? extends E> c)

// 從指定的位置開始,將指定collection 中的所有元素插入到此列表中
public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount

    int numMoved = size - index;
    if (numMoved > 0)
      System.arraycopy(elementData, index, elementData, index + numNew,
                       numMoved);

    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

讀取

get(int index)

// 返回此列表中指定位置上的元素
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

E elementData(int index) {
  return (E) elementData[index];  //註意類型轉換
}

刪除

根據下標和指定對象刪除,刪除時,被移除元素以後的所有元素向左移動一個位置。

remove(int index)

// 刪除指定位置的元素,並返回刪除元素
public E remove(int index) {
    rangeCheck(index)![ArrayList_add.png](http://upload-images.jianshu.io/upload_images/626005-4973ec5f1213fd0f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
;

    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,清除該位置的引用,讓GC起作用

    return oldValue;
}

remove(Object o)

// 移除此列表中首次出現的指定元素(如果存在)。這是應為ArrayList 中允許存放重復的元素
public boolean remove(Object o) {
  // 由於ArrayList 中允許存放null,因此下面通過兩種情況來分別處理
    if (o == null) {
      for (int index = 0; index < size; index++)
        if (elementData[index] == null) {
          // 類似remove(int index),移除列表中指定位置上的元素
          fastRemove(index);
          return true;
        }
    } else {
      for (int index = 0; index < size; index++)
        if (o.equals(elementData[index])) {
          fastRemove(index);
          return true;
        }
    }
    return false;
}

// 私有刪除方法,不進行越界檢測
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
}

容量調整

如果添加元素前已經預測到了容量不足,可手動增加ArrayList實例的容量,以減少遞增式再分配的數量。

ensureCapacity(int minCapacity)

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

// 具體查看ensureExplicitCapacity

在ensureExplicitCapacity中,可以看出,每次擴容時,會將老數組中的元素重新拷貝一份到新的數組中(System.arraycopy()方法),每次數組容量的增長大約是其原容量的1.5 倍。這種操作的代價是很高的,因此要盡量避免數組容量擴容,使用時盡可能的指定容量,或者根據需求,通過調用ensureCapacity 方法來手動增加ArrayList 實例的容量。

trimToSize()

該方法是將底層數組的容量調整為當前列表保存的實際元素的大小。

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

遍歷

通過叠代器遍歷

Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}

隨機訪問,通過索引值去遍歷(推薦)

Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
    value = (Integer)list.get(i);        
}

ForEach循環遍歷

Integer value = null;
for (Integer integ:list) {
    value = integ;
}

經測試,耗時:ForEach循環 > 隨機 > 叠代器,優先使用叠代器方式 。

ForEach的本質也是叠代器模式,可以反編譯查看,而且還多了一步賦值操作,增加了開銷。

總結

size(), isEmpty(), get(), set()方法均能在常數時間內完成,add()方法的時間開銷跟插入位置有關,addAll()方法的時間開銷跟添加元素的個數成正比。其余方法大都是線性時間。

  • 排列有序(索引從0開始),可插入空值,可重復

  • 底層數組實現

  • 讀取快,增刪慢(需要移動元素,插入刪除效率低)

  • 非同步,線程不安全

  • 初始容量默認為10,當容量不夠時,ArrayList是當前容量 * 1.5 + 1
    擴容時,需要調用System.arraycopy,copy本來就是一個耗時的操作,所以盡量初始化容量。
    即使理論上效率還可以
    (System.arraycopy()方法是一個native的,最終調用了C語言的memmove()函數,比一般的復制方法的實現效率要高很多。)

  • 創建時,初始化最小容量

?

參考

  • CarpenterLee
  • 莫等閑
  • 蘭亭風雨

======華麗麗的分隔線======
作者:jimzhang
出處:http://www.jianshu.com/p/2f282e5ce305
版權所有,歡迎保留原文鏈接進行轉載:)

List源碼解析之ArrayList源碼分析