1. 程式人生 > 程式設計 >聊一聊jdk1.8中的ArrayList 底層陣列是如何擴容的

聊一聊jdk1.8中的ArrayList 底層陣列是如何擴容的

一、結論先行

ArrayList在JDK1.8與JDK1.7底層區別

JDK1.7:ArrayList像餓漢式,直接建立一個初始容量為10的陣列,當陣列的長度不能容下所新增的內容時候,陣列會擴容至原大小的1.5倍

JDK1.8:ArrayList像懶漢式,一開始建立一個長度為0的陣列,當新增第一個元素時再建立一個始容量為10的陣列,當陣列的長度不能容下所新增的內容時候,陣列會擴容至原大小的1.5倍

二、JDK1.8 ArrayList原始碼分析

1、ArrayList 屬性

  /**
   * 預設容量的大小
   */
  private static final int DEFAULT_CAPACITY = 10;

  /**
   * 空陣列常量
   */
  private static final Object[] EMPTY_ELEMENTDATA = {};

  /**
   * 預設的空陣列常量,我們將其與EMPTY_ELEMENTDATA區分開來,
   * 以便知道新增第一個元素時需要膨脹多少
   */
  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


  /**
   * 存放元素的陣列,從這可以發現ArrayList的底層實現就是一個Object陣列
   */
  transient Object[] elementData; 

  /**
   * 陣列中包含元素的個數
   */
  private int size;

  /**
   *陣列的最大上限,超過上限可能導致OutOfMemoryError錯誤
   */
  private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

2、構造方法

  /**
   * 指定大小的時候,elementData就變成了我們所指定的初始化大小了
   */
  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);
    }
  }

  /**
   *  根據上面的屬性可知,DEFAULTCAPACITY_EMPTY_ELEMENTDATA={},所以預設建立的是  一個大小為0的空陣列
   */
  public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  }

從上面我可以知道,jkd1.8中預設的是大小為0空陣列,這個和jdk1.7之前都是不一樣的,這和設計模式的懶漢式很有相似之處

3、add 方法,底層擴容機制

 /**
   * 在指定的位置插入指定的元素
   */
  public void add(int index,E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1); // Increments modCount!!
    System.arraycopy(elementData,index,elementData,index + 1,size - index);
    elementData[index] = element;
    size++;
  }

  /**
   * 將指定的引數新增到列表的末尾,其中size是陣列中包含元素的個數
   * @param e 以附加到此列表中
   */
  public boolean add(E e) {
    ensureCapacityInternal(size + 1); 
    // 陣列的下標從0開始,所以size++保證elementData每次新增完一個元素,元素個數也隨之加1
    elementData[size++] = e;
    return true;
  }

 // 引數minCapacity 表達的意思是所需陣列的最小容量,也就是size+1,上面傳的值
  private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));
  }


  /**
  * 計算容量的方法,
  */
  private static int calculateCapacity(Object[] elementData,int minCapacity) {
   // 如果是一個空陣列,
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    // DEFAULT_CAPACITY從屬性可知預設是10,minCapactity為陣列的大小
    // 由此可以看出他是在新增第一個元素的時候,才建立了長度為10的陣列
      return Math.max(DEFAULT_CAPACITY,minCapacity);
    }
    return minCapacity;
  }


 private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
  // 如果此時所需要的最小的長度大於原陣列的長度,則需要進行擴容
    if (minCapacity - elementData.length > 0)
      grow(minCapacity);
  }
  
  /**
   * 增加容量,以確保它至少可以容納minCapacity指定的元素數量。
   * @param minCapacity 表示所需要的擴容的量
   */
  private void grow(int minCapacity) {
    int oldCapacity = elementData.length; // 原陣列的長度
   //原陣列的長度+原陣列的長度/2,表示擴容了原來大小的1.5倍,newCapacity :表示需要擴容的量
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
      newCapacity = minCapacity;
   // 如果需要的擴容量大於了本類中定義的最大擴容限制,則擴容到 int 型別最大長度
    if (newCapacity - MAX_ARRAY_SIZE > 0)
      newCapacity = hugeCapacity(minCapacity);
   // 呼叫的是陣列的複製方法,實現擴容
    elementData = Arrays.copyOf(elementData,newCapacity);
  }

  private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
      throw new OutOfMemoryError();
   // 如若需要擴容的量大於最大限制,則擴容量改為 int 最大限制量:2147483647,否則為本類中所限制長度:2147483647-8
    return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;
  }

解釋:int newCapacity = oldCapacity + (oldCapacity >> 1);

這個>>1 表示的是右移一位,轉為二進位制我們應該一下就明白了:比如16,轉為2進製為

10000,右移一位則成為01000,換為十進位制就是8,擴容的大小也就相當於oldCapacity +oldCapacity /2了

4、總結

jdk1.8中:

add方法總結起來就是在插入資料之前,會先檢查是否需要擴容,通過無參構造方法來建立 ArrayList 時,它的大小其實是為 0 的,只有在使用到 的時候,才會通過 grow 方法去建立一個大小為 10 的陣列。

public boolean add(E e) 方法的複雜度為O(1),涉及到擴容的操作是非常少的,可以忽略不計,它的本質是新增元素到陣列中最後一個元素的後面。

public void add(int index,E element) 這個是帶指定下標的add 方法,複雜度為O(n),因為涉及到陣列中元素的移動,這一操作非常耗時,由此可見ArrayList不適合插入和刪除操作。

三、ArrayList與Vector的區別

現在Vector已經很少有人用了,這裡只是簡單的記錄下二者區別:

1、ArrayList執行緒不安全,Vector是執行緒安全的

通過Vector原始碼我們可以知道很多方法都是加了synchronized關鍵字,所以Vector是執行緒安全的。

2、ArrayList建立的預設大小為0,Vector建立時的預設大小是10。

3、ArrayList 每次擴容都以當前陣列大小的 1.5 倍去擴容, Vector 每次擴容都以當前陣列大小的 2 倍去擴容。當指定了 capacityIncrement 之 後,每次擴容僅在原先基礎上增加 capacityIncrement 個單位空間。

補充知識:ArrayList詳解,底層是陣列,實現Serializable介面

一、對於ArrayList需要掌握的七點內容

ArrayList的建立:即構造器

往ArrayList中新增物件:即add(E)方法

獲取ArrayList中的單個物件:即get(int index)方法

刪除ArrayList中的物件:即remove(E)方法

遍歷ArrayList中的物件:即iterator,在實際中更常用的是增強型的for迴圈去做遍歷

判斷物件是否存在於ArrayList中:contain(E)

ArrayList中物件的排序:主要取決於所採取的排序演算法(以後講)

二、原始碼分析

2.1、ArrayList的建立(常見的兩種方式)

List<String> strList = new ArrayList<String>();

List<String> strList2 = new ArrayList<String>(2);

ArrayList原始碼:

基本屬性:

//物件陣列:ArrayList的底層資料結構
private transient Object[] elementData;
//elementData中已存放的元素的個數,注意:不是elementData的容量
private int size;

注意:

transient關鍵字的作用:在採用Java預設的序列化機制的時候,被該關鍵字修飾的屬性不會被序列化。

ArrayList類實現了java.io.Serializable介面,即採用了Java預設的序列化機制

上面的elementData屬性採用了transient來修飾,表明其不使用Java預設的序列化機制來例項化,但是該屬性是ArrayList的底層資料結構,在網路傳輸中一定需要將其序列化,之後使用的時候還需要反序列化,那不採用Java預設的序列化機制,那採用什麼呢?直接翻到原始碼的最下邊有兩個方法,發現ArrayList自己實現了序列化和反序列化的方法

View Code

構造器:

/**
* 建立一個容量為initialCapacity的空的(size==0)物件陣列
*/
public ArrayList(int initialCapacity) {
super();//即父類protected AbstractList() {}
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity:" + initialCapacity);
this.elementData = new Object[initialCapacity];
}

/**
* 預設初始化一個容量為10的物件陣列
*/
public ArrayList() {
this(10);//即上邊的public ArrayList(int initialCapacity){}構造器
}

在我們執行new ArrayList<String>()時,會呼叫上邊的無參構造器,創造一個容量為10的物件陣列。

在我們執行new ArrayList<String>(2)時,會呼叫上邊的public ArrayList(int initialCapacity),創造一個容量為2的物件陣列。

注意:

上邊有參構造器的super()方法是ArrayList父類AbstractList的構造方法,這個構造方法如下,是一個空構造方法:

protected AbstractList() {
}

在實際使用中,如果我們能對所需的ArrayList的大小進行判斷,有兩個好處:

節省記憶體空間(eg.我們只需要放置兩個元素到陣列,new ArrayList<String>(2))

避免陣列擴容(下邊會講)引起的效率下降(eg.我們只需要放置大約37個元素到陣列,new ArrayList<String>(40))

2.2、往ArrayList中新增物件(常見的兩個方法add(E)和addAll(Collection<? extends E> c))

以上這篇聊一聊jdk1.8中的ArrayList 底層陣列是如何擴容的就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。