Java容器類框架分析(1)ArrayList源碼分析

概述

在分析ArrayList源碼以前,先看一下ArrayList在數據結構中的位置,常見的數據結構按照邏輯結構跟存儲結構以下:數組

數據結構分類
數據結構分類

先看看源碼的註釋:安全

  • Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including
    null. In addition to implementing the List interface,
    this class provides methods to manipulate the size of the array that is
    used internally to store the list. (This class is roughly equivalent to
    Vector, except that it is unsynchronized.)
  • 實現了List接口的可調整大小的數組,也就是常常說的動態數組,實現了全部的可選的列表的操做,而且可以插入任何元素,包括空值。除了實現了List接口以外,對於內部存儲的數組,也提供了相應的改變數組大小的方法。(這個類除了不是線程安全的,幾乎能夠至關於Vector)

很明顯,ArrayList是一個線性結構,底層經過數組實現,而且是動態數組。bash

下面看一下ArrayList的繼承關係,這個是經過IDEA生成的繼承關係圖,很清晰,終於不用本身畫了。數據結構

ArrayList繼承關係
ArrayList繼承關係

經過圖能夠看到ArrayList繼承自AbstractList,而且實現了List, RandomAccess, Cloneable, Serializable接口,而AbstractList實現了Collection接口,則ArrayList具備Collection的全部方法,並且可以進行clone,序列化,下面開始開始對ArrayList的源碼進行分析,吼吼。dom

正文

成員變量

//序列化ID
 private static final long serialVersionUID = 8683452581122892189L;
//默認的初始化數組的容量爲10
 private static final int DEFAULT_CAPACITY = 10;
//共享的空數組,也就是調用空的構造方法的時候會進行默認用這個數組進行初始化
private static final Object[] EMPTY_ELEMENTDATA = {};
//數組緩衝區,也就是歷來存放List元素的數組。ArrayList的容量就是緩衝區數組的長度。對於一個空數組,在添加第一個元素的時候,List的容量會被設置成默認的DEFAULT_CAPACITY,也就是10,
transient Object[] elementData;
//elementData的長度
private int size;複製代碼

構造方法

構造一個空數組,採用默認容量(Constructs an empty list with an initial capacity of ten)

public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }

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

     private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }複製代碼

前面有提到過,當初始化數組爲空的時候,在add的時候會進行判斷,若是數組的長度爲0,數組的長度就是默認的數組長度ide

構造一個空的數組,自定義數組容量(Constructs an empty list with the specified initial capacity)

public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }複製代碼

這個跟上面的區別在於建立了一個新的數組,數組的最大長度就是傳入的初始化容量,數組的長度爲0。性能

傳入一個集合進行初始化(Constructs a list containing the elements of the specified collection, in the order they are returned by the collection's iterator.)

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }複製代碼

數組的長度就是傳入的集合的長度,將集合傳入緩衝數組ui

Add方法

在末尾添加一個元素(Appends the specified element to the end of this list.)

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

在指定位置添加一個元素(Inserts the specified element at the specified position in this list.)

public void add(int index, E element) {
 //判斷index是否合乎規範
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//檢查是否須要擴容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
//拷貝數組
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
//進行賦值
        elementData[index] = element;
//將數組的長度自增
        size++;
    }複製代碼

在末尾添加一個集合(Appends all of the elements in the specified collection to the end of this list)

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;
    }複製代碼

在指定位置添加集合(Inserts all of the elements in the specified collection into this list, starting at the specified position)

public boolean addAll(int index, Collection<? extends E> c) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(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;
    }複製代碼

Add操做有如上幾個重載方法,仔細觀察一下,大同小異,咱們選擇第二個重載方法,也就是在指定位置添加一個元素:this

  1. 判斷index是否合理
  2. 檢查是否須要擴容
  3. 拷貝數組
  4. 進行賦值
  5. 將數組的長度自增

這裏面比較有養分的就是第二步了,下面重點分析第二步:spa

調用ensureCapacityInternal(size++1)

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }複製代碼

傳入了此時數組須要的長度,只要數組爲初始化的時候沒有指定容量,就會在minCapacity(size+1)跟DEFAULT_CAPACITY中間取一個最大值,之因此這樣,是由於若是將數組的容量設置成過小,會致使數組頻繁的擴容,影響性能。

調用ensureExplicitCapacity(minCapacity)

private void ensureExplicitCapacity(int minCapacity) {
    //這個變量來自於ArrayList的父類AbstractList,主要用來記錄集合的操做次數
        modCount++;
        // overflow-conscious code
        //若是此時須要的數組長度大於數組自己的長度,則進行擴容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }複製代碼

執行擴容操做grow(minCapacity)

private void grow(int minCapacity) {
        // overflow-conscious code
        //數組當前的容量
        int oldCapacity = elementData.length;
        //擴容後新數組的容量,增大了0.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);
    }複製代碼

Remove方法

remove指定位置元素(Removes the element at the specified position in this list)

public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        modCount++;
        E oldValue = (E) 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;
    }複製代碼

移除某個具體元素(Removes the first occurrence of the specified element from this list,if it is present. If the list does not contain the element, it is unchanged)

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 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
    }複製代碼

仔細觀察一下上面兩個重載方法,實際上是差很少的,刪除元素分兩步走:

  1. 找到這個元素
  2. 執行刪除操做

比較簡單,不過多描述

Set方法

public E set(int index, E element) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        E oldValue = (E) elementData[index];
        elementData[index] = element;
        return oldValue;
    }複製代碼

直接替換掉對應的元素

查找索引操做

尋找元素第一次出現的下標(Returns the index of the first occurrence of the specified element)

public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }複製代碼

尋找最後出現的某個元素的索引(Returns the index of the last occurrence of the specified element in this list)

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;
    }複製代碼

這些都是經過遍歷對元素進行查找,一個是從頭至尾遍歷,一個是從未到頭遍歷,查到結構就返回對應的下表,不然返回-1.

Get方法

public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        return (E) elementData[index];
    }複製代碼

總結

ArrayList的特色

  • 有序的,元素能夠重複
  • 查詢快,增刪慢
  • 當容量不夠的時候,會進行擴容至當前size的1.5倍
  • 非線程安全

關於ArrayList的源碼就分析到這裏,由於底層的實現是數組,因此無論是擴容仍是其它的增刪改查操做都是對數組進行操做,因此只要對數組足夠了解,基本上仍是挺好理解的。

相關文章
相關標籤/搜索