1. 程式人生 > >hashMap tableSizeFor 實現原理

hashMap tableSizeFor 實現原理

static aci 規律 void 需要 obi hashmap for class

基於jdk1.8

hashMap實現,要求容量大小是2的整次方,例如:2/4/8/16/32/64/128...,而不能是中間的某個值。這是為什麽呢?

map是數組+鏈表的數據結構,讀寫數據都需要首先獲取數組中的下標值,獲取的方式是通過hashcode取余。取余so easy,我們都會,假定運算後的hashcode=17,容量大小capacity=16,17%16=1,很容易得出元素落在數組的下標[1]內。

但是還有一種方式可以獲取到正確的下標值,17 &(16-1)=1。

17二進制: 10001

15二進制: 01111

17&15: 00001 = 1

計算機的物理特性,決定位運算才是取余的正確打開方式。但是卻有一個限制,被位與的數值有效位必須全部都是1,如15:1111可以,13:1101則不行

這裏位與運算得到的是下標位置,數組容量要在最大下標值的基礎上加1,等價於二進制中被位與的數值進位,如15進1位=16:10000,2的4次方。

如果我們new HashMap<>(13),輸入一個非整次方的數值,hashmap會自動調整成最近的整次方,例如這裏的13最終會轉換成16,實現方法為:java.util.HashMap#tableSizeFor

怎麽實現呢?

2的整次方的特性是二進制有效位只有一個1,退位後當前1消失,後面bit位全補1,例如16:10000,退位後01111。13的二進制:1101,結構上看只要第二個bit補1(1101->1111),再進位(1111->10000)就可以了。1101->1111->

我們來分析實現

首先把方法復制出來,加上一些日誌方便分析:

static final int MAXIMUM_CAPACITY = 1 << 30;

static final int tableSizeFor(int cap) {
    System.out.println(Integer.toBinaryString(cap));
    int n = cap - 1;
    System.out.println(Integer.toBinaryString(n));
    n |= n >>> 1;
    System.out.println(Integer.toBinaryString(n));
    n 
|= n >>> 2; System.out.println(Integer.toBinaryString(n)); n |= n >>> 4; System.out.println(Integer.toBinaryString(n)); n |= n >>> 8; System.out.println(Integer.toBinaryString(n)); n |= n >>> 16; System.out.println(Integer.toBinaryString(n)); return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } public static void main(String[] args) { tableSizeFor(MAXIMUM_CAPACITY); }
int n = cap - 1先退位,所以最終實現的結果是n的bit位全補1

使用的最大容量+1,這樣看日誌比較清晰。

1000000000000000000000000000001
1000000000000000000000000000000
1100000000000000000000000000000
1111000000000000000000000000000
1111111100000000000000000000000
1111111111111111000000000000000
1111111111111111111111111111111

看到規律了嗎?tableSizeFor首先獲取最高位的1,二進制退位規則決定一定能夠獲取到最高位的1,然後進行不停的bit復制,1生2,2生4等等。int類型只有32位,所以復制到16位終止。

最後將n進位,即得到2的整次方,不過限定不能大於1>>30;

hashMap tableSizeFor 實現原理