咱們知道,Objects中定義了hashcode()函數,用於計算對象的哈希值。而且在不少類中都對hashcode()函數進行了覆蓋。可是在HashMap中並無直接使用各個類的hash值,而是使用hash()函數將它再次進行了計算。
1、列舉一些基本類型對應的普通類型的hashcode()
Objectsjava
public static int hashCode(Object o) { return o != null ? o.hashCode() : 0; }
Integer數組
public int hashCode(){ return Integer.hashCode(value); }//就是它本身
String函數
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for(int i=0; i< value.length; i++){ h = 31 * h + val[i];//① } hash = h; } return h; }//將字符串轉換爲字符數組,而後對字符的哈希值進行①處的運算
Characterthis
public static int hashCode(char value) { return (int)value; } //爲字符對應的asc11碼
Datecode
public int hashCode() { long ht = this.getTime(); return (int)ht^(int)(ht >> 32); }
2、在HashMap中有hash()函數對Objects的哈希值再進行了一次計算對象
1.計算方法字符串
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
從新計算的哈希值是用hashCode()計算出的哈希值和它本身左移16位以後的數進行異或
好比:get
h = hashCode()-> 1111 0101 1111 1111 1101 1011 1111 0101 h>>>16 -> 0000 0000 0000 0000 1111 0101 1111 1111 h^(h>>>16) -> 1111 0101 1111 1111 0010 1110 0000 1010 = hash
2.爲何要這麼計算hash
哈希值是32位的二進制數,咱們能夠看出,將哈希值右移16位正好是32bit的一半,也就是說它將本身的高16位和低16位作異或,那麼當table數組的長度較小的時候,哈希值的高位也能參加數組位置的計算it
3、table的閾值
在HashMap是什麼一節中咱們看到table的閾值並不必定是咱們本身設定的容量×加載因子,而是通過了tableSizeFor()函數的計算,那麼這個函數又是什麼
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n<0)?1:(n>=MAXIMUM_CAPACITY)? MAXIMUM_CAPACITY:n+1; }
首先,咱們這樣作的目的是爲了找出大等於用戶給出的哈希表的容量的最小的2的冪,好比用戶給了7,那咱們就要找到8,用戶給了14,咱們就要找到16. 那爲何經過這種方式就能夠找到呢?讓咱們來看
假設咱們要尋找的數是離14最近的且大於它的2的冪,明顯這個數是16,爲2的4次方
16的2進制: 0001 0000 14的2進制: 0000 1100 15的2進制: 0000 1111
能夠這樣想,咱們想利用14計算出16,那咱們能夠先計算出15,再加一,而15有一個特色那就是它有四個1,那如今的問題就成爲了如何讓14從它的第一位非0數開始將後面的數字所有轉換成1.
首先14和15的第一位非0數在同一位上,也就是說咱們不用管後面的數,只須要管後面的數。那有什麼方法可讓全部的數都轉換爲1呢,那就是和1進行或運算。
因而咱們想到能夠利14的第一位(或前幾位)非零數與後面的數進行或運算。
Cap == 14; N = cap-1==13;
-1是爲了防止若用戶給的數自己就是2的冪,如16,那麼將16-1後依舊//能夠利用下面的方法計算出想要的結果而不會出錯
n |= n >>> 1; 0000 1100 0000 0110 0000 1110 n |= n >>> 2; 0000 1110 0000 0011 0000 1111 n |= n >>> 4; ... n |= n >>> 8; ... n |= n >>> 16; ...
從舉得例子來看,當n作第一次或運算後,n的前兩位都轉換爲了1,那麼如今就能夠利用前兩位將3,4,位轉換爲1,因此第二次與運算將n右移了兩位;同理,進行了第二次運算後,n的前四位都已是1了能夠用它們去改變5,6,7,8位,後面同理。這就是爲何是以1,2,4,8,16的順序右移。那爲何到右移16位後就中止了呢?
int在java中是4個子節 即32位,而向右移動16位後,能夠從高位第一個出現1的位置開始向右連續32位爲1,已經超越了int的最大值,因此不用在進行位移操做了
在方法最後將n+1即的到了咱們想要的2的冪
4、如何根據哈希值計算出在數組中的位置
if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //n爲數組的大小,爲2的指數倍
這是putVal函數中的一小段代碼。咱們發現,在數組中的位置是用n-1和hash作與運算獲得的
3.1爲何是n-1?
因爲數組的長度n必定爲二的指數倍(tableSizeFor()函數中決定),因此n-1轉換二進制時,後幾位所有是1,前面全是0. 如 0000 0000 0000 0000 0000 0000 0000 1111(15)
這樣,在與哈希值作與運算時(用上面的例子)
0000 0000 0000 0000 0000 0000 0000 1111 1111 0101 1111 1111 0010 1110 0000 1010 0000 0000 0000 0000 0000 0000 0000 1010
在作與運算時,任何數和0與都會成爲0,那麼這樣就能夠保證與運算的結果在0~n-1之間,即數組下標的範圍,不會發生數組越界
下一節:hashmap的快速存取