大家都知道hashmap是採用的懶載入機制,也就是說在你執行new hashmap()的時候,構造方法並沒有在構造出hashmap例項的同時也把hashmap例項裡所需的陣列給初始化出來。
那麼什麼時候才去初始化裡面的陣列呢?答案只有在第一次需要用到這個陣列的時候才會去初始化它,就是在你往hashmap裡面put元素的時候。
而初始化陣列時,它的容量是怎麼確定的呢?有兩種情況:
第一種是你在構造hashmap例項的時候,呼叫的是無參建構函式,此時預設的陣列初始化長度就是16,在後續put元素初始化陣列時生效。
public
hashmap()
第二種情況,則是你調的是帶了陣列容量引數的建構函式:
public
hashmap
(int initialcapacity)
或者
public
hashmap
(int initialcapacity,
float loadfactor)
當然可以看出上面那個構造方法裡面執行的是this(initialcapacity, default_load_factor),實際也就是下乙個構造方法。
當你呼叫帶參構造器初始化乙個指定陣列容量的hashmap時,構造器會根據你輸入的引數重新計算得到乙個hashmap初始化陣列之後陣列實際的長度,這個值也是會在put元素初始化陣列時起作用。計算的邏輯在tablesizefor(int cap)中:
static
final
inttablesizefor
(int cap)
這個方法的作用是什麼呢,比如你輸入的容量引數為a,返回值為b,那麼a與b的關係如下:
1、b大於或等於a
2、b為最接近a的乙個2的n次方
舉幾個栗子就是:
1、輸入1,返回1(2^0)
2、輸入2,返回2(2^1)
3、輸入3,返回4(2^2)
4、輸入4,返回4(2^2)
5、輸入5,返回8(2^3)
依此類推~
那麼方法體是如何實現這些邏輯的呢,從**中可以看到大部分**都是在做無符號右移(>>>)和位或(|),這裡其實都是在做2進製的位操作,描述起來比較困難,還是來舉幾個栗子吧,假如你輸入的引數為35:
int n = cap - 1;
那麼n = 34;
那麼34對應的2進製編碼為:100010
n | n >>> 1等同於n = n | n >>> 1,就是34與34右移一位之後做位或操作:
得到的二進位制是110011,轉化為10進製是51;
接下來n |= n >>> 2;
得到的二進位制是111111,轉化為10進製是63;
接下來是 n |= n >>> 4;
得到的二進位制也是111111,轉化為10進製是63;
接下來是 n |= n >>> 8;
得到的二進位制也是111111,轉化為10進製是63;
接下來是 n |= n >>> 16;
得到的二進位制也是111111,轉化為10進製是63;
最終 return (n < 0) ? 1 : (n >= maximum_capacity) ? maximum_capacity : n + 1這一行,
先判斷n是否大於0:
1、不大於0返回1
2、否則再判斷n是否大於定義的最大容量(1>>30):
1:若大於1>>30,則返回1>>30
2:否則返回n+1
實際最終這個方法,就是利用移位與位或運算,將n-1得到的值轉成2進製之後,從1的最高位開始將低位全部轉化為1,再加1之後就可以得到乙個2^n的數~
HashMap在jdk1 8前後的變化
hashmap在jdk1.8之前結構為陣列 鍊錶,缺點就是雜湊函式很難使元素百分百的均勻分布,這會產生一種極端的可能,就是大量的元素存在乙個桶裡,此時的時間複雜度為o n 極大的放慢了計算速率。在jdk1.8之後,hashmap採用陣列加鍊表或是紅黑樹的形式,1 在hashmap新增元素時,按照陣列...
HashMap 底層 原理(JDK 1 8)
原來看過1.7的hashmap底層,1.8更新後也稍微看了一下,沒有進行仔細的總結,今天總結一下1.8底層的原理。本文只討論1.8的底層原理。以下全文為1.8版本的 對於hashmap的資料結構,是老生常談了,面試的時候經常會被問道。底層資料結構為陣列 鍊錶 紅黑樹,儲存的是node節點,紅黑樹是t...
HashMap原始碼分析 JDK1 8
陣列 鍊錶 紅黑樹 陣列儲存元素,當有衝突時,就在該陣列位置形成乙個鍊錶,儲存衝突的元素,當鍊表元素個數大於閾值時,鍊錶就轉換為紅黑樹。transient node table transient int size int threshold final float loadfactor 預設初始容...