HashMap方法hash()、tableSizeFor()
HashMap#hash()
/** * Computes key.hashCode() and spreads (XORs) higher bits of hash * to lower. Because the table uses power-of-two masking, sets of * hashes that vary only in bits above the current mask will * always collide. (Among known examples are sets of Float keys * holding consecutive whole numbers in small tables.) So we * apply a transform that spreads the impact of higher bits * downward. There is a tradeoff between speed, utility, and * quality of bit-spreading. Because many common sets of hashes * are already reasonably distributed (so don't benefit from * spreading), and because we use trees to handle large sets of * collisions in bins, we just XOR some shifted bits in the * cheapest possible way to reduce systematic lossage, as well as * to incorporate impact of the highest bits that would otherwise * never be used in index calculations because of table bounds. */ static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
從上面的程式碼可以看到key的hash值的計算方法。key的hash值高16位不變,低16位與高16位異或作為key的最終hash值。(h >>> 16,表示無符號右移16位,高位補0,任何數跟0異或都是其本身,因此key的hash值高16位不變。)
為什麼要這麼幹呢?
這個與HashMap中table下標的計算有關。
n = table.length;
index = (n-1) & hash;
因為,table的長度都是2的冪,因此index僅與hash值的低n位有關,hash值的高位都被與操作置為0了。
假設table.length=2^4=16。
由上圖可以看到,只有hash值的低4位參與了運算。
這樣做很容易產生碰撞。設計者權衡了speed, utility, and quality,將高16位與低16位異或來減少這種影響。設計者考慮到現在的hashCode分佈的已經很不錯了,而且當發生較大碰撞時也用樹形儲存降低了衝突。僅僅異或一下,既減少了系統的開銷,也不會造成的因為高位沒有參與下標的計算(table長度比較小時),從而引起的碰撞。
HashMap#tableSizeFor()
原始碼:
static final int MAXIMUM_CAPACITY = 1 << 30; /** * Returns a power of two size for the given target capacity. */ 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; }
這個方法被呼叫的地方:
public HashMap(int initialCapacity, float loadFactor) {
/**省略此處程式碼**/
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
由此可以看到,當在例項化HashMap例項時,如果給定了initialCapacity,由於HashMap的capacity都是2的冪,因此這個方法用於找到大於等於initialCapacity的最小的2的冪(initialCapacity如果就是2的冪,則返回的還是這個數)。
下面分析這個演算法:
首先,為什麼要對cap做減1操作。int n = cap - 1;
這是為了防止,cap已經是2的冪。如果cap已經是2的冪, 又沒有執行這個減1操作,則執行完後面的幾條無符號右移操作之後,返回的capacity將是這個cap的2倍。如果不懂,要看完後面的幾個無符號右移之後再回來看看。
下面看看這幾個無符號右移操作:
如果n這時為0了(經過了cap-1之後),則經過後面的幾次無符號右移依然是0,最後返回的capacity是1(最後有個n+1的操作)。
這裡只討論n不等於0的情況。
第一次右移
n |=
n >>> 1;
由於n不等於0,則n的二進位制表示中總會有一bit為1,這時考慮最高位的1。通過無符號右移1位,則將最高位的1右移了1位,再做或操作,使得n的二進位制表示中與最高位的1緊鄰的右邊一位也為1,如000011xxxxxx。
第二次右移
n |= n >>> 2;
注意,這個n已經經過了n |= n >>> 1;
操作。假設此時n為000011xxxxxx ,則n無符號右移兩位,會將最高位兩個連續的1右移兩位,然後再與原來的n做或操作,這樣n的二進位制表示的高位中會有4個連續的1。如00001111xxxxxx
。
第三次右移
n |= n >>> 4;
這次把已經有的高位中的連續的4個1,右移4位,再做或操作,這樣n的二進位制表示的高位中會有8個連續的1。如00001111 1111xxxxxx 。
以此類推
注意,容量最大也就是32bit的正數,因此最後n |= n >>> 16;
,最多也就32個1,但是這時已經大於了MAXIMUM_CAPACITY
,所以取值到MAXIMUM_CAPACITY
。
舉一個例子說明下吧。
這個演算法著實牛逼啊!
注意,得到的這個capacity卻被賦值給了threshold。
this.threshold
=
tableSizeFor(initialCapacity);
開始以為這個是個Bug,感覺應該這麼寫:
this.threshold
= tableSizeFor(initialCapacity) * this.loadFactor;
這樣才符合threshold的意思(當HashMap的size到達threshold這個閾值時會擴容)。
但是,請注意,在構造方法中,並沒有對table這個成員變數進行初始化,table的初始化被推遲到了put方法中,在put方法中會對threshold重新計算
相關推薦
HashMap方法hash()、tableSizeFor()
HashMap#hash() /** * Computes key.hashCode() and spreads (XORs) higher bits of hash * to lower. Because the table uses powe
HashMap原始碼註解 之 靜態工具方法hash()、tableSizeFor()(四)
注意 , 本文基於JDK 1.8 HashMap#hash() 為什麼要有HashMap的hash()方法,難道不能直接使用KV中K原有的hash值嗎?在HashMap的put、get操作時為什麼不能直接使用K中原有的hash值。 /*
javaSE (二十六)map集合遍歷(兩種方法)、輸入字元,計算字元出現次數(用map實現)、HashMap巢狀HashMap
1、map集合遍歷: map集合沒有iterator方法,所以不能直接迭代 直接看下面的程式碼和第一行的註解(加了註釋之後變黑看不清了,所以前面沒加註釋) 1、map的第一種遍歷:遍歷map的所有值:method1() 獲取所有的鍵的集合:Set<K> keySet()
hashmap的hash方法源doc解讀
/** * Computes key.hashCode() and spreads (XORs) higher bits of hash * to lower. Because the table uses power-of-two masking, sets of *
java基礎--HashMap解決hash衝突的方法
在Java程式語言中,最基本的結構就是兩種,一種是陣列,一種是模擬指標(引用),所有的資料結構都可以用這兩個基本結構構造,HashMap也一樣。當程式試圖將多個 key-value 放入 HashMap
【資料結構】30、hashmap=》hash 計算方式
前提知識 寫在前面,為什麼num&(length - 1) 在length是2的n次冪的時候等價於num%length n - 1意味著比n最高位小的位都為1,而高的位都為0,因此通過與可以剔除位數比n最高位更高的部分,只保留比n最高位小的部分,也就是取餘了。 而且用位運算取代%,效率會比較高。
內置方法map、reduce、filter
enc -- cti initial 代碼實現 cto top 過濾 port map: map(func, *iterables) --> map object Make an iterator that computes the function
JAVA學習(七):方法重載與方法重寫、thiskeyword和superkeyword
格式 hello new 初始 per 而且 方法重寫 學習 方式 方法重載與方法重寫、thiskeyword和superkeyword 1、方法重載 重載可以使具有同樣名稱但不同數目和類型參數的類傳遞給方法。 註: 一是重載方法的參數列表必須與被重載的方法不同
測試方法addAll、containsAll的用法
col tco sys 用法 src img cti -1 php import java.util.ArrayList;import java.util.Collection;import java.util.HashSet;import org.junit.Test;
HashMap的hash沖突解決方案
bsp 大小 不同的 bject 一個 發生 next tro 來看 Hash函數 非哈希表的特點:關鍵字在表中的位置和它之間不存在一個確定的關系,查找的過程為給定值一次和各個關鍵字進行比較,查找的效率取決於和給定值進行比較的次數。 哈希表的特點:關鍵字在表中
【JS設計模式】溫習簡單工廠模式、工廠方法模式、抽象工廠模式概念
his mac script 開頭 str new 每一個 簡單工廠 pattern 註:空心箭頭表示的是種繼承關系,工廠類和產品類之間是一種依賴關系。是用箭頭加虛線表示的,以下的模型圖是用的實線是不正確(時間不夠用,在這裏我偷懶了。這個習慣不好,呵呵)簡單工廠模式(S
php使用cUrl方法 get、post請求
http log lds ray lan init foreach get方法 spa php使用curl方法,請確保已經開啟curl擴展。傳送門:http://www.cnblogs.com/wgq123/p/7450667.html /**Curl請求get方法 *@
python列表排序方法reverse、sort、sorted
正向 無需 .so 得到 har () 因此 好的 返回鍵 python語言中的列表排序方法有三個:reverse反轉/倒序排序、sort正序排序、sorted可以獲取排序後的列表。在更高級列表排序中,後兩中方法還可以加入條件參數進行排序。 reverse()方法 將列表
Robotium_斷言方法assert、is、search
控件 指定 兩種方法 gpo instance strong box name sspi 下面的這些方法都主要用來判斷測試結果是否與預期結果相符,一般把is和search方法放在assert裏面判斷。assert最常用的還是assertThat方法,是Junit的判斷,這裏
詳解webpack中的hash、chunkhash、contenthash區別
con tro 們的 tex trac extra lena fig files hash、chunkhash、contenthash hash一般是結合CDN緩存來使用,通過webpack構建之後,生成對應文件名自動帶上對應的MD5值。如果文件內容改變的話,那麽對應文件
線程同步的方法synchronized、ReentrantLock
tar lock 線程同步 href spa ext tex cnblogs 方法 參見:synchronized詳解 ReentrantLock實現原理線程同步的方法synchronized、ReentrantLock
【python】實例屬性的顯示方法-dir、__dict__
python在測試實例的屬性時,產生過一個誤解。 class Test(): name = ‘python‘ def printest(): print ‘Test‘ a = Test() print dir(a) print a.__dict__ 其中dir(a)打印出的內
類與接口(五)java多態、方法重寫、隱藏
增加 object 方法簽名 進行 tcl 覆蓋 get 註意 表現 一、Java多態性 面向對象的三大特性:封裝、繼承、多態。 多態的類型,分為以下兩種: 編譯時多態: 指的是 方法重載。編譯時多態是在編譯時確定調用處選擇那個重載方法,所以也叫 靜態多態,算不上真正的多
安裝軟件包的三種方法yum、rpm、源碼安裝
rpm yum 一、軟件安裝方法1.rpm安裝rpm安裝軟件時不會自動安裝依賴2.yum安裝yum安裝軟件時會自動安裝軟件所需的依賴3.源碼安裝最難,需要編譯二、rpm介紹首先將之前的系統光驅掛載,mount /dev/cdrom /mnt/執行上面命令將iso掛載到/mnt/下ls /mnt/ c
day9(Hash、字典)
序列 報錯 多級 空間 log 速度 多個 md5加密 翻譯 一、Hash 簡介: Hash,一般被翻譯成“散列”,也有直接音譯"哈希"的,就是把任意長度的輸入,通過哈希算法,變換成固定長度的輸出,輸出的結果就叫做哈希值,這是一種壓縮映射,也就是說一般哈希值的空間要小於