1. 程式人生 > 實用技巧 >Java常見集合的預設大小及擴容機制

Java常見集合的預設大小及擴容機制

在面試後臺開發的過程中,集合是面試的熱話題,不僅要知道各集合的區別用法,還要知道集合的擴容機制,今天我們就來談下ArrayList 和 HashMap的預設大小以及擴容機制。

在 Java 7 中,檢視原始碼可以知道:ArrayList 的預設大小是 10 個元素,HashMap 的預設大小是16個元素(必須是2的冪,為什麼呢???下文有解釋)。這就是 Java 7 中 ArrayList 和 HashMap 類 的程式碼片段:

// from ArrayList.java JDK 1.7
private static final int DEFAULT_CAPACITY = 10;
 
//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

這裡要討論這些常用的預設初始容量和擴容的原因是:

當底層實現涉及到擴容時,容器或重新分配一段更大的連續記憶體(如果是離散分配則不需要重新分配,離散分配都是插入新元素時動態分配記憶體),要將容器原來的資料全部複製到新的記憶體上,

這無疑使效率大大降低。載入因子的係數小於等於1,意指即當元素個數超過容量長度*載入因子的係數時,進行擴容。另外,擴容也是有預設的倍數的,不同的容器擴容情況不同。

List元素是有序的、可重複

ArrayList、Vector預設初始容量為10

Vector:執行緒安全,但速度慢

    底層資料結構是陣列結構

    載入因子為1:即當 元素個數 超過 容量長度 時,進行擴容

    擴容增量:原容量的 1倍

      如Vector的容量為10,一次擴容後是容量為20

ArrayList:執行緒不安全,查詢速度快

    底層資料結構是陣列結構

    擴容增量:原容量的 0.5倍+1

      如 ArrayList的容量為10,一次擴容後是容量為16

Set(集)元素無序的、不可重複。

HashSet:執行緒不安全,存取速度快

     底層實現是一個HashMap(儲存資料),實現Set介面

     預設初始容量為16(為何是16,見下方對HashMap的描述)

     載入因子為0.75:即當 元素個數 超過 容量長度的0.75倍 時,進行擴容

     擴容增量:原容量的 1 倍

      如 HashSet的容量為16,一次擴容後是容量為32

Map是一個雙列集合

HashMap:預設初始容量為16

     (為何是16:16是2^4,可以提高查詢效率,另外,32=16<<1)

     載入因子為0.75:即當 元素個數 超過 容量長度的0.75倍 時,進行擴容

     擴容增量:原容量的 1 倍

      如HashSet的容量為16,一次擴容後是容量為32

接下來我們來談談hashMap的陣列長度為什麼保持2的次冪?

hashMap的陣列長度一定保持2的次冪,比如16的二進位制表示為 10000,那麼length-1就是15,二進位制為01111,同理擴容後的陣列長度為32,二進位制表示為100000,length-1為31,二進位制表示為011111。

這樣會保證低位全為1,而擴容後只有一位差異,也就是多出了最左位的1,這樣在通過 h&(length-1)的時候,只要h對應的最左邊的那一個差異位為0,就能保證得到的新的陣列索引和老陣列索引一致(大大減少了

之前已經雜湊良好的老陣列的資料位置重新調換),還有,陣列長度保持2的次冪,length-1的低位都為1,會使得獲得的陣列索引index更加均勻。

1.    static int indexFor(int h, int length) {  
2.           return h & (length-1);  
3.    }

首先算得key得hashcode值,然後跟陣列的長度-1做一次“與”運算(&)。看上去很簡單,其實比較有玄機。比如陣列的長度是2的4次方,那麼hashcode就會和2的4次方-1做“與”運算。很多人都有這個疑問,

為什麼hashmap的陣列初始化大小都是2的次方大小時,hashmap的效率最高,我以2的4次方舉例,來解釋一下為什麼陣列大小為2的冪時hashmap訪問的效能最高。

看下圖,左邊兩組是陣列長度為16(2的4次方),右邊兩組是陣列長度為15。兩組的hashcode均為8和9,但是很明顯,當它們和1110“與”的時候,產生了相同的結果,也就是說它們會定位到陣列中的同

一個位置上去,這就產生了碰撞,8和9會被放到同一個連結串列上,那麼查詢的時候就需要遍歷這個連結串列,得到8或者9,這樣就降低了查詢的效率。同時,我們也可以發現,當陣列長度為15的時候,hashcode的

值會與14(1110)進行“與”,那麼最後一位永遠是0,而0001,0011,0101,1001,1011,0111,1101這幾個位置永遠都不能存放元素了,空間浪費相當大,更糟的是這種情況中,陣列可以使用的位置比陣列

長度小了很多,這意味著進一步增加了碰撞的機率,減慢了查詢的效率!

所以說,當陣列長度為2的n次冪的時候,不同的key算得得index相同的機率較小,那麼資料在陣列上分佈就比較均勻,也就是說碰撞的機率小,相對的,查詢的時候就不用遍歷某個位置上的連結串列,這樣查詢效率也就較高了。

說到這裡,我們再回頭看一下hashmap中預設的陣列大小是多少,檢視原始碼可以得知是16,為什麼是16,而不是15,也不是20呢,看到上面的解釋之後我們就清楚了吧,顯然是因為16是2的整數次冪的原因,

在小資料量的情況下16比15和20更能減少key之間的碰撞,而加快查詢的效率。