JDK1.8 ArrayList源碼淺析


我的jdk版本

拆開源碼,我們從頭道來,不足或誤解,請指正

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList是一個泛型類,繼承自AbstractList,重寫了IndexOf(Object o)等方法,

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

 

實現了get(index),size()抽象方法

    public T get(int index) {
        rangeCheck(index);
        return elementData(index);
    }

    public int size() {
        return size;
    }

 

實際上,ArrayList底層是這個玩意

transient Object[] elementData;//沒有被私有化是為了簡化內部類訪問

當然還有其他的幾個屬性:

private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private int size;

 

在ArrayList中有三個構造器,分別為

//初始化容量的構造器
    public ArrayList(int initCapacity){
        if(initCapacity > 0) 
            this.elementData = new Object[initCapacity];
        else if(initCapacity == 0) 
            this.elementData = EMPTY_ELEMENTDATA;
        else 
            throw new IllegalArgumentException("cant init ArrayList for this initCapacity:"+initCapacity);
    }
    /*默認構造器*/
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    /**
     * 傳入繼承集合類的實例
     * @param c
     */
    public ArrayList(Collection<? extends T> c) {
        elementData = c.toArray();
        if((size = elementData.length) != 0){
            if(elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size,Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

注意的是,默認構造器中,

this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

是一個默認的空Object數組

/**
     * 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 = {};

並沒有設置初始值,對於網上一片文章說ArrayList初始化長度為10,我又翻了一下jdk1.7源碼

   /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }
    /**
     * Shared empty array instance used for empty instances.
   * 這里是用final 和 static 修飾的
*/ private static final Object[] EMPTY_ELEMENTDATA = {};

也是空數組,所以下面這種構造器最起碼不會出現在jdk1.7和jdk1.8中,也就是說兩個版本中的實例化ArrayList的時候沒有給他設置默認容量

public ArrayList() {
        this(10);
}

那么,什么時候這個空數組才會真正的擴容呢,也就是大家說的容量為10的時候

 在源碼中,ArrayList提供了這樣幾個方法,也就是關於ArrayList容量的關鍵的幾個方法

/*方法名直譯為:確認容量*/
    public void ensureCapacity(int minCapacity) {
        //最小擴展
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0: DEFAULT_CAPACITY;
        
        if(minCapacity > minExpand) 
            ensureExplicitCapacity(minCapacity);
    }

上面的這個方法我是看了一陣子,腦回路差點跑偏,換種思路可能會更直觀一些

int minExpand = (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? DEFAULT_CAPACITY : 0 ;

我對這個方法的大概理解就是如果elementData是空的,返回一個默認容量(DEFAULT_CAPACITY),長度在上面已經定義好了,是10,如果傳入的參數大於這個最小擴展(10),

進入ensureExplicitCapacity()方法,

 

/*方法名直譯為:確認內部容量*/
    public void ensureCapacityInternal(int minCapacity) {
        if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 
            //取值 10 - 傳入最小值之間最大
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            
        ensureExplicitCapacity(minCapacity);
    }

 判斷elementData是否為空數組,是則返回默認長度(10)和傳入參數中大的那一個

進入ensureExplicitCapacity()方法,

 

/* 明確容量*/
    public void ensureExplicitCapacity(int minCapacity){
        modCount++;
        //意識到長度溢出時,增長長度
        if(minCapacity - elementData.length > 0)
            grow(minCapacity);
    };

以上兩個方法結束時調用本方法

在ArrayList,LinkedList,HashMap等等的內部實現增,刪,改中我們總能看到modCount的身影,modCount字面意思就是修改次數,而擁有這個參數的方法都是線程不安全的

在一個迭代器初始的時候會賦予它調用這個迭代器的對象的mCount,如何在迭代器遍歷的過程中,一旦發現這個對象的mcount和迭代器中存儲的mcount不一樣那就拋異常

這里涉及到了一個機制叫做:

fail-fast機制:

 它是Java集合的一種錯誤檢測機制。當多個線程對集合進行結構上的改變的操作時,有可能會產生fail-fast機制。記住是有可能,而不是一定。例如:假設存在兩個線程(線程1、線程2),線程1通過Iterator在遍歷集合A中的元素,在某個時候線程2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的內容),那么這個時候程序就會拋出 ConcurrentModificationException 異常,從而產生fail-fast機制。

 

/*有人說int最大減8是盡可能的避免一些錯誤 */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

 

/**
     * 長度增長
     * @param minCapacity
     */
    public void grow(int minCapacity) {
        //原數組長度
        int oldCapacity = elementData.length;
        //位運算 右位移1位
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if(newCapacity - minCapacity < 0) //小於傳入長度時
            newCapacity = minCapacity;
        if(newCapacity - MAX_ARRAY_SIZE > 0) //大於上方自定義的最大數組長度
            newCapacity = hugeCapacity(minCapacity);
        
         elementData = Arrays.copyOf(elementData, newCapacity);
    };

數組長度在增長時,有位移1並代表增加0.5倍,具體增長的數值,可自行百度java位運算 

 

private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow 超出int整數范圍變負數
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE :  MAX_ARRAY_SIZE;
     }

 

了解到這幾個方法之后,我們來看一下常用的增/刪/改/查操作

查:

 @SuppressWarnings("unchecked")
 T elementData(int index){
         return (T) elementData[index];
 }
 //范圍檢測
     public void rangeCheck(int index) {
         if(index >= size) {
             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
         }
     }
/*來自父類的抽象方法*/
    public T get(int index) {
        rangeCheck(index);//檢測傳入參數范圍,超過范圍數組下標越界
        return elementData(index);
    }

沒什么好說的,只是代碼抽象化了一點

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

 

public boolean add(T t) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = t;
        return true;
    }

 

public void add(int index, T element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // 增加容量
        
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = element;
        size++;//??為什么又加了
    }

 

改:

public T set(int index, T element) {
         rangeCheck(index);
         
         T oldValue = elementData(index);//這里的elementData是方法
         elementData[index] = element;//這里的elementData是數組 return oldValue;
 }
//這里的返回值是被替換的元素
刪:
public T remove(int index) {
        rangeCheck(index);
        
        modCount++;
        T oldValue = elementData(index);//方法
        
        int numMoved = size - index -1;//是否超長
        if(numMoved > 0)
             System.arraycopy(elementData, index+1, elementData, index, numMoved);
        
        elementData[--size] = null;//最后一個設置為null 數組
        
        return oldValue;
    }

 

到這里ArrayList一些基礎內容也講的差不多了,更為深入的一些機制現在就不提了,以后再深入研究吧

學習是一種態度,不管是jdk源碼還是spring或者tomcat源碼,抱着借鑒和模仿的心態,努力提高自己.

寫代碼應該是一件很快樂的事情,過程中也許會有些痛苦,但是往往快樂最后遠大於痛苦

不要因為別人一句看這玩意有什么用而放棄自己的追求,不要被外界干擾,自己要堅定

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM