1. 程式人生 > >HashMap方法hash()、tableSizeFor()

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()

hashmaphash方法源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

【資料結構】30hashmap=》hash 計算方式

前提知識 寫在前面,為什麼num&(length - 1) 在length是2的n次冪的時候等價於num%length n - 1意味著比n最高位小的位都為1,而高的位都為0,因此通過與可以剔除位數比n最高位更高的部分,只保留比n最高位小的部分,也就是取餘了。 而且用位運算取代%,效率會比較高。

內置方法mapreducefilter

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、方法重載 重載可以使具有同樣名稱但不同數目和類型參數的類傳遞給方法。 註: 一是重載方法的參數列表必須與被重載的方法不同

測試方法addAllcontainsAll的用法

col tco sys 用法 src img cti -1 php import java.util.ArrayList;import java.util.Collection;import java.util.HashSet;import org.junit.Test;

HashMaphash沖突解決方案

bsp 大小 不同的 bject 一個 發生 next tro 來看 Hash函數   非哈希表的特點:關鍵字在表中的位置和它之間不存在一個確定的關系,查找的過程為給定值一次和各個關鍵字進行比較,查找的效率取決於和給定值進行比較的次數。 哈希表的特點:關鍵字在表中

【JS設計模式】溫習簡單工廠模式工廠方法模式抽象工廠模式概念

his mac script 開頭 str new 每一個 簡單工廠 pattern 註:空心箭頭表示的是種繼承關系,工廠類和產品類之間是一種依賴關系。是用箭頭加虛線表示的,以下的模型圖是用的實線是不正確(時間不夠用,在這裏我偷懶了。這個習慣不好,呵呵)簡單工廠模式(S

php使用cUrl方法 getpost請求

http log lds ray lan init foreach get方法 spa php使用curl方法,請確保已經開啟curl擴展。傳送門:http://www.cnblogs.com/wgq123/p/7450667.html /**Curl請求get方法 *@

python列表排序方法reversesortsorted

正向 無需 .so 得到 har () 因此 好的 返回鍵 python語言中的列表排序方法有三個:reverse反轉/倒序排序、sort正序排序、sorted可以獲取排序後的列表。在更高級列表排序中,後兩中方法還可以加入條件參數進行排序。 reverse()方法 將列表

Robotium_斷言方法assertissearch

控件 指定 兩種方法 gpo instance strong box name sspi 下面的這些方法都主要用來判斷測試結果是否與預期結果相符,一般把is和search方法放在assert裏面判斷。assert最常用的還是assertThat方法,是Junit的判斷,這裏

詳解webpack中的hashchunkhashcontenthash區別

con tro 們的 tex trac extra lena fig files hash、chunkhash、contenthash hash一般是結合CDN緩存來使用,通過webpack構建之後,生成對應文件名自動帶上對應的MD5值。如果文件內容改變的話,那麽對應文件

線程同步的方法synchronizedReentrantLock

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多態性 面向對象的三大特性:封裝、繼承、多態。 多態的類型,分為以下兩種: 編譯時多態: 指的是 方法重載。編譯時多態是在編譯時確定調用處選擇那個重載方法,所以也叫 靜態多態,算不上真正的多

安裝軟件包的三種方法yumrpm源碼安裝

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,一般被翻譯成“散列”,也有直接音譯"哈希"的,就是把任意長度的輸入,通過哈希算法,變換成固定長度的輸出,輸出的結果就叫做哈希值,這是一種壓縮映射,也就是說一般哈希值的空間要小於