我們知道,Objects中定義了hashcode()函數(shù),用于計(jì)算對(duì)象的哈希值。并且在很多類中都對(duì)hashcode()函數(shù)進(jìn)行了覆蓋。但是在HashMap中并沒有直接使用各個(gè)類的hash值,而是使用hash()函數(shù)將它再次進(jìn)行了計(jì)算。
一、列舉一些基本類型對(duì)應(yīng)的普通類型的hashcode()
Objects
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; }//將字符串轉(zhuǎn)換為字符數(shù)組,然后對(duì)字符的哈希值進(jìn)行①處的運(yùn)算
Character
public static int hashCode(char value) { return (int)value; } //為字符對(duì)應(yīng)的asc11碼
Date
public int hashCode() { long ht = this.getTime(); return (int)ht^(int)(ht >> 32); }
二、在HashMap中有hash()函數(shù)對(duì)Objects的哈希值再進(jìn)行了一次計(jì)算
1.計(jì)算方法
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
重新計(jì)算的哈希值是用hashCode()計(jì)算出的哈希值和它自己左移16位之后的數(shù)進(jìn)行異或
比如:
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.為什么要這么計(jì)算
哈希值是32位的二進(jìn)制數(shù),我們可以看出,將哈希值右移16位正好是32bit的一半,也就是說它將自己的高16位和低16位做異或,那么當(dāng)table數(shù)組的長(zhǎng)度較小的時(shí)候,哈希值的高位也能參加數(shù)組位置的計(jì)算
三、table的閾值
在HashMap是什么一節(jié)中我們看到table的閾值并不一定是我們自己設(shè)定的容量×加載因子,而是經(jīng)過了tableSizeFor()函數(shù)的計(jì)算,那么這個(gè)函數(shù)又是什么
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. 那為什么通過這種方式就可以找到呢?讓我們來看
假設(shè)我們要尋找的數(shù)是離14最近的且大于它的2的冪,明顯這個(gè)數(shù)是16,為2的4次方
16的2進(jìn)制: 0001 0000 14的2進(jìn)制: 0000 1100 15的2進(jìn)制: 0000 1111
可以這樣想,我們想利用14計(jì)算出16,那我們可以先計(jì)算出15,再加一,而15有一個(gè)特點(diǎn)那就是它有四個(gè)1,那現(xiàn)在的問題就成為了如何讓14從它的第一位非0數(shù)開始將后面的數(shù)字全部轉(zhuǎn)換成1.
首先14和15的第一位非0數(shù)在同一位上,也就是說我們不用管后面的數(shù),只需要管后面的數(shù)。那有什么方法可以讓所有的數(shù)都轉(zhuǎn)換為1呢,那就是和1進(jìn)行或運(yùn)算。
于是我們想到可以利14的第一位(或前幾位)非零數(shù)與后面的數(shù)進(jìn)行或運(yùn)算。
Cap == 14; N = cap-1==13;
-1是為了防止若用戶給的數(shù)本身就是2的冪,如16,那么將16-1后依舊//可以利用下面的方法計(jì)算出想要的結(jié)果而不會(huì)出錯(cuò)
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; ...
從舉得例子來看,當(dāng)n做第一次或運(yùn)算后,n的前兩位都轉(zhuǎn)換為了1,那么現(xiàn)在就可以利用前兩位將3,4,位轉(zhuǎn)換為1,所以第二次與運(yùn)算將n右移了兩位;同理,進(jìn)行了第二次運(yùn)算后,n的前四位都已經(jīng)是1了可以用它們?nèi)ジ淖?,6,7,8位,后面同理。這就是為什么是以1,2,4,8,16的順序右移。那為什么到右移16位后就停止了呢?
int在java中是4個(gè)子節(jié) 即32位,而向右移動(dòng)16位后,可以從高位第一個(gè)出現(xiàn)1的位置開始向右連續(xù)32位為1,已經(jīng)超越了int的最大值,所以不用在進(jìn)行位移操作了
在方法最后將n+1即的到了我們想要的2的冪
四、如何根據(jù)哈希值計(jì)算出在數(shù)組中的位置
if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //n為數(shù)組的大小,為2的指數(shù)倍
這是putVal函數(shù)中的一小段代碼。我們發(fā)現(xiàn),在數(shù)組中的位置是用n-1和hash做與運(yùn)算得到的
3.1為什么是n-1?
由于數(shù)組的長(zhǎng)度n一定為二的指數(shù)倍(tableSizeFor()函數(shù)中決定),所以n-1轉(zhuǎn)換二進(jìn)制時(shí),后幾位全部是1,前面全是0. 如 0000 0000 0000 0000 0000 0000 0000 1111(15)
這樣,在與哈希值做與運(yùn)算時(shí)(用上面的例子)
0000 0000 0000 0000 0000 0000 0000 1111 1111 0101 1111 1111 0010 1110 0000 1010 0000 0000 0000 0000 0000 0000 0000 1010
在做與運(yùn)算時(shí),任何數(shù)和0與都會(huì)成為0,那么這樣就可以保證與運(yùn)算的結(jié)果在0~n-1之間,即數(shù)組下標(biāo)的范圍,不會(huì)發(fā)生數(shù)組越界
下一節(jié):hashmap的快速存取
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/69984.html
摘要:也就是說,紅黑樹是一種相對(duì)平衡的查找二叉樹,這使他不僅便于查找,也便于插入和刪除,這對(duì)于既需要插入也需要查找的是非常有利的下一節(jié)數(shù)據(jù)哈希值的計(jì)算和在中的存儲(chǔ)位置 showImg(https://segmentfault.com/img/bVNgfR?w=427&h=361); 在jdk8中,HashMap是用了數(shù)組和鏈表以及紅黑樹這三種數(shù)據(jù)結(jié)構(gòu) 首先,在hashmap類中,都有一個(gè)ta...
摘要:上一篇數(shù)據(jù)結(jié)構(gòu)與算法鏈表寫在前面說明數(shù)據(jù)結(jié)構(gòu)與算法系列文章的代碼和示例均可在此找到一集合集合數(shù)據(jù)結(jié)構(gòu)集合是一種包含不同元素的數(shù)據(jù)結(jié)構(gòu)。集合中的元素成為成員。 上一篇:JS數(shù)據(jù)結(jié)構(gòu)與算法_鏈表 寫在前面 說明:JS數(shù)據(jù)結(jié)構(gòu)與算法 系列文章的代碼和示例均可在此找到 一、集合Set 1.1 集合數(shù)據(jù)結(jié)構(gòu) 集合set是一種包含不同元素的數(shù)據(jù)結(jié)構(gòu)。集合中的元素成為成員。集合的兩個(gè)最重要特性是:...
摘要:舉個(gè)例子,比如我們要在哈希表中執(zhí)行插入操作查找操作同理,先通過哈希函數(shù)計(jì)算出實(shí)際存儲(chǔ)地址,然后從數(shù)組中對(duì)應(yīng)地址取出即可。這也是數(shù)組長(zhǎng)度設(shè)計(jì)為必須為的次冪的原因。 前言 hashMap在平時(shí)工作和面試中,常常使用到和問到,本文將從一下幾個(gè)方面進(jìn)行記錄: 什么是哈希表 HashMap實(shí)現(xiàn)原理 為何HashMap的數(shù)組長(zhǎng)度一定是2的次冪? 1. 什么是哈希表 在討論哈希表之前,我們先...
摘要:哈希碰撞的概率取決于計(jì)算方式和空間容量的大小。超過后執(zhí)行擴(kuò)容操作。當(dāng)一個(gè)哈希桶存儲(chǔ)的鏈表長(zhǎng)度大于會(huì)將鏈表轉(zhuǎn)換成紅黑樹,小于時(shí)則從紅黑樹轉(zhuǎn)換成鏈表。換句話來說,就是為了減少哈希碰撞。紅黑樹相關(guān)的操作雖然代碼不同,但是實(shí)際上要干的事情是一樣的。 前言 學(xué)習(xí)情況記錄 學(xué)習(xí)情況記錄 時(shí)間:week 3 SMART子目標(biāo) :Java 容器 記錄在學(xué)習(xí)Java容器 知識(shí)點(diǎn)中,關(guān)于HashM...
閱讀 3301·2023-04-25 14:35
閱讀 3423·2021-11-15 18:00
閱讀 2570·2021-11-12 10:34
閱讀 2502·2021-11-11 16:54
閱讀 3485·2021-10-08 10:12
閱讀 2770·2021-09-06 15:02
閱讀 3327·2021-09-04 16:48
閱讀 2806·2019-08-29 14:02