HashMap 擴容機制

HashMap:數組

 
複製代碼
public HashMap(int initialCapacity, float loadFactor) {     
//初始容量不能<0  
if (initialCapacity < 0)         
throw new IllegalArgumentException("Illegal initial capacity: "                 + initialCapacity);     
//初始容量不能 > 最大容量值,HashMap的最大容量值爲2^30     
if (initialCapacity > MAXIMUM_CAPACITY)         
initialCapacity = MAXIMUM_CAPACITY;     
//負載因子不能 < 0     
if (loadFactor <= 0 || Float.isNaN(loadFactor))         
throw new IllegalArgumentException("Illegal load factor: "                 + loadFactor);     
// 計算出大於 initialCapacity 的最小的 2 的 n 次方值。    
 int capacity = 1;     
while (capacity < initialCapacity)        
 capacity <<= 1;    
 this.loadFactor = loadFactor;     
//設置HashMap的容量極限,當HashMap的容量達到該極限時就會進行擴容操做    
 threshold = (int) (capacity * loadFactor);     
//初始化table數組    
 table = new Entry[capacity];     init(); 
}
複製代碼
 

在這裏提到了兩個參數:初始容量,加載因子。性能

這兩個參數是影響HashMap性能的重要參數,其中容量表示哈希表中桶的數量,初始容量是建立哈希表時的容量,this

加載因子是哈希表在其容量自動增長以前能夠達到多滿的一種尺度,它衡量的是一個散列表的空間的使用程度,負載因子越大表示散列表的裝填程度越高,反之愈小。spa

對於使用鏈表法的散列表來講,查找一個元素的平均時間是O(1+a),所以若是負載因子越大,對空間的利用更充分,然然後果是查找效率的下降;線程

若是負載因子過小,那麼散列表的數據將過於稀疏,對空間形成嚴重浪費。系統默認負載因子爲0.75,通常狀況下咱們是無需修改的。ci

加載因子:hash

 loadFactorit

 

擴容:io

 
複製代碼
void addEntry(int hash, K key, V value, int bucketIndex) {    
 Entry<K,V> e = table[bucketIndex];        
 table[bucketIndex] = new Entry<K,V>(hash, key, value, e);         
if (size++ >= threshold) // 這裏是關鍵,一旦大於等於threshold的數值             
resize(2 * table.length); // 將會引發容量2倍的擴大     
}
複製代碼
 
複製代碼
void resize(int newCapacity) {         
Entry[] oldTable = table;         
int oldCapacity = oldTable.length;         
if (oldCapacity == MAXIMUM_CAPACITY) {             
threshold = Integer.MAX_VALUE;             
return;         
}         
 Entry[] newTable = new Entry[newCapacity]; // 新的容器空間   
     
 transfer(newTable); // 複製數據過去 
        
table = newTable;         
threshold = (int)(newCapacity * loadFactor); // 從新計算threshold的值    
 }
複製代碼
複製代碼
void transfer(Entry[] newTable) {          
// 保留原數組的引用到src中,         
 Entry[] src = table;          
// 新容量使新數組的長度          
int newCapacity = newTable.length;       
// 遍歷原數組          
for (int j = 0; j < src.length; j++) {              
// 獲取元素e              
Entry<K,V> e = src[j];              
if (e != null) {                 
 // 將原數組中的元素置爲null                  
src[j] = null;                  
// 遍歷原數組中j位置指向的鏈表                  
do {                      
Entry<K,V> next = e.next;                      
// 根據新的容量計算e在新數組中的位置                      
int i = indexFor(e.hash, newCapacity);                      
// 將e插入到newTable[i]指向的鏈表的頭部                      
e.next = newTable[i];                      
newTable[i] = e;                      
e = next;                  
}
 while (e != null);              
}          
}      
}
複製代碼

經過上面的transfer方法能夠看出,table

e.next=newTable[i];

newTable[i]=e;

鏈表存儲倒過來了,最早出來的會將其next指向null,後面的就指向前一個,固然數據只有原來的一部分。

===================================================================

隨着HashMap中元素的數量愈來愈多,發生碰撞的機率就愈來愈大,所產生的鏈表長度就會愈來愈長,這樣勢必會影響HashMap的速度,

爲了保證HashMap的效率,系統必需要在某個臨界點進行擴容處理。

該臨界點在當HashMap中元素的數量等於table數組長度*加載因子。

可是擴容是一個很是耗時的過程,由於它須要從新計算這些數據在新table數組中的位置並進行復制處理。

因此若是咱們已經預知HashMap中元素的個數,那麼預設元素的個數可以有效的提升HashMap的性能。

問題:

當從新調整HashMap大小的時候,確實存在條件競爭,由於若是兩個線程都發現HashMap須要從新調整大小了,它們會同時試着調整大小。

在調整大小的過程當中,存儲在鏈表中的元素的次序會反過來,由於移動到新的bucket位置的時候,HashMap並不會將元素放在鏈表的尾部,而是放在頭部,這是爲了避免尾部遍歷(tail traversing)。

若是條件競爭發生了,那麼就死循環了。

相關文章
相關標籤/搜索