Java源碼系列1——ArrayList

本文簡單介紹了 ArrayList,並對擴容,添加,刪除操作的源代碼做分析。能力有限,歡迎指正。

ArrayList是什麼?

ArrayList 就是數組列表,主要用來裝載數據。底層實現是數組 Object[] elementData,當我們裝載的是基本數據類型 int, long, boolean, shot...的時候我們只能存儲他們對應的包裝類型。

與它類似的是 LinkedList,和 LinkedList 相比,它的查找和訪問元素的速度較快,但新增,刪除的速度較慢。

線程安全嗎?

線程不安全。

正常使用場景中,ArrayList 都是用來查詢,不會涉及太頻繁的增刪,如果涉及頻繁的增刪,可以使用 LinkedList。如果需要線程安全就使用 Vector

VectorArrayList 的線程安全版本,實現方式就是在所有方法加上synchronized,性能較差。

如何擴容?

因爲數組的大小是固定,當容量超出了現有數組的大小,就需要進行擴容。

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 每次擴大原有容量的一半
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果擴大一半後還是無法滿足,則使用minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果超過最大size,則獲取最大容量的數組
    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插入效率低?

原因有兩點:

  1. 新增就要檢測容量夠不夠,如果不夠就需要擴容
  2. 尾部新增比較快,如果是在數組頭部或者中部新增就會慢很多,因爲要把後面的元素全部往後移一位
  3. 把元素往後移一位使用的是複製 System.arraycopy(),它是native方法(java定義了接口,其他語言進行實現),所以比較快
/**
 * 添加在尾部
 */
public boolean add(E e) {
    // 檢查容量
    ensureCapacityInternal(size + 1);
    // 添加在尾部
    elementData[size++] = e;
    return true;
}

/**
 * 按指定位置添加
 */
public void add(int index, E element) {
    // 檢查index
    rangeCheckForAdd(index);

    // 檢查容量
    ensureCapacityInternal(size + 1);
    // index後面的元素全部往後移一位
    System.arraycopy(elementData, index, elementData, index + 1,
                        size - index);
    elementData[index] = element;
    size++;
}

刪除元素效率如何?

效率和新增差不多,都是要移動元素,但是不需要檢查容量和擴容。

public E remove(int index) {
    // 檢查index
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        // index後面的元素全部往前移一位
        System.arraycopy(elementData, index+1, elementData, index,
                            numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

適合做隊列嗎?

非常不適合。

隊列是FIFO,在尾巴進,頭部出,出的時候需要移動後面所有數據,效率很低。鏈表比較適合做隊列。

new ArrayList<>(18) 會不會初始化數組大小?

不會初始化數組大小!!!

這是Java Bug。

而且將構造函數與initialCapcity結合使用,然後使用set()方法會拋出異常。

public static void main(String[] args) {
    ArrayList<Integer> a = new ArrayList<>(12);
    System.out.println(a.size());
    a.set(3, 3);
}

總結

  1. 底層實現是數組 Object[] elementData
  2. 查找和訪問元素的速度較快,但新增,刪除的速度較慢
  3. 線程不安全
  4. 每次擴容原有數組大小的一半

源碼系列文章

Java源碼系列1——ArrayList

Java源碼系列2——HashMap

Java源碼系列3——LinkedHashMap

本文首發於我的個人博客 http://chaohang.top

作者張小超

轉載請註明出處

歡迎關注我的微信公衆號 【超超不會飛】,獲取第一時間的更新。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章