[轉(zhuǎn)載] HashSet 與 HashMap

總體介紹

之所以把HashSetHashMap放在一起講解,是因為二者在Java里有著相同的實現(xiàn),前者僅僅是對后者做了一層包裝颊咬,也就是說HashSet里面有一個HashMap(適配器模式)。因此本文將重點分析HashMap
HashMap實現(xiàn)了Map接口犬庇,允許放入null
元素,除該類未實現(xiàn)同步外侨嘀,其余跟Hashtable
大致相同臭挽,跟TreeMap不同,該容器不保證元素順序飒炎,根據(jù)需要該容器可能會對元素重新哈希埋哟,元素的順序也會被重新打散,因此不同時間迭代同一個HashMap的順序可能會不同郎汪。 根據(jù)對沖突的處理方式不同赤赊,哈希表有兩種實現(xiàn)方式,一種開放地址方式(Open addressing)煞赢,另一種是沖突鏈表方式(Separate chaining with linked lists)抛计。Java HashMap采用的是沖突鏈表方式

HashMap結(jié)構(gòu)圖

從上圖容易看出照筑,如果選擇合適的哈希函數(shù)吹截,put()get() 方法可以在常數(shù)時間內(nèi)完成。但在對HashMap 進行迭代時凝危,需要遍歷整個table以及后面跟的沖突鏈表波俄。因此對于迭代比較頻繁的場景,不宜將HashMap 的初始大小設(shè)的過大蛾默。

有兩個參數(shù)可以影響HashMap 的性能:初始容量(inital capacity)和負載系數(shù)(load factor)懦铺。初始容量指定了初始 table 的大小,負載系數(shù)用來指定自動擴容的臨界值支鸡。當entry 的數(shù)量超過capacity* load_factor 時冬念,容器將自動擴容并重新哈希。對于插入元素較多的場景牧挣,將初始容量設(shè)大可以減少重新哈希的次數(shù)急前。

將對向放入到HashMapHashSet 中時,有兩個方法需要特別關(guān)心:hashCode()equals()瀑构。hashCode() 方法決定了對象會被放到哪個bucket里裆针,當多個對象的哈希值沖突時,equals() 方法決定了這些對象是否是“同一個對象”。所以据块,如果要將自定義的對象放入到HashMapHashSet 中码邻,需要@Override hashCode() 和 equals() 方法。

方法剖析

get()

get(Object key) 方法根據(jù)指定的key 值返回對應(yīng)的value另假,該方法調(diào)用了getEntry(Object key) 得到相應(yīng)的entry像屋,然后返回 entry.getValue()。因此getEntry() 是算法的核心边篮。 算法思想是首先通過hash() 函數(shù)得到對應(yīng)bucket 的下標己莺,然后依次遍歷沖突鏈表,通過key.equals(k) 方法來判斷是否是要找的那個entry戈轿。

HashMap getEntry(Object key)

上圖中hash(k)&(table.length-1) * 等價于 hash(k)%table.length凌受,原因是HashMap* 要求table.length 必須是2的指數(shù),因此table.length-1就是二進制低位全是1思杯,跟hash(k) 相與會將哈希值的高位全抹掉胜蛉,剩下的就是余數(shù)了。

    // getEntry()方法
    final Entry<K,V> getEntry(Object key) {
        ......
        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[hash&(table.length-1)];//得到?jīng)_突鏈表
             e != null; e = e.next) {//依次遍歷沖突鏈表中的每個entry
            Object k;
            //依據(jù)equals()方法判斷是否相等
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }
put()

put(K key, V value) 方法是將指定的key, value 對添加到map 里色乾。該方法首先會對map 做一次查找誊册,看是否包含該元組,如果已經(jīng)包含則直接返回暖璧,查找過程類似于getEntry() 方法案怯;如果沒有找到,則會通過addEntry(int hash, K key, V value, int bucketIndex) 方法插入新的entry澎办,插入方式為頭插法嘲碱。

HashMap addEntry 方法
    // addEntry()
    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);// 自動擴容,并重新哈希
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = hash & (table.length - 1);// hash%table.length
        }
        // 在沖突鏈表頭部插入新的entry
        Entry<K, V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
remove()

remove(Object key) 的作用是刪除key 值對應(yīng)的entry局蚀,該方法的具體邏輯是在 removeEntryForKey(Object key) 里實現(xiàn)的麦锯。removeEntryForKey() 方法會首先找到key 值對應(yīng)的entry,然后刪除該entry(修改鏈表的相應(yīng)指針)琅绅。查找過程跟 getEntry() 過程類似离咐。

HashMap removeEntryForKey(Object k)
    // removeEntryForKey()
    final Entry<K,V> removeEntryForKey(Object key) {
        ......
        int hash = (key == null) ? 0 : hash(key);
       // hash&(table.length-1)
        int i = indexFor(hash, table.length);
       // 得到?jīng)_突鏈表
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;
        
       // 遍歷沖突鏈表
        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            // 找到要刪除的entry
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++; size--;
                // 刪除的是沖突鏈表的第一個entry
                if (prev == e) 
                    table[i] = next;
                else 
                    prev.next = next;
                return e;
            }
            prev = e; 
            e = next;
        }
        return e;
    }
HashSet

前面已經(jīng)說過HashSet 是對HashMap 的簡單包裝,對HashSet 的函數(shù)調(diào)用都會轉(zhuǎn)換成合適的HashMap 方法奉件,因此HashSet 的實現(xiàn)非常簡單,只有不到300行代碼昆著。這里不再贅述县貌。

    // HashSet是對HashMap的簡單包裝
    public class HashSet<E>
    {
        ......
        // HashSet里面有一個HashMap
        private transient HashMap<E,Object> map;
        // Dummy value to associate with an Object in the backing Map
        private static final Object PRESENT = new Object();
        public HashSet() {
            map = new HashMap<>();
        }
        ......
        // 簡單的方法轉(zhuǎn)換
        public boolean add(E e) {
            return map.put(e, PRESENT) == null;
        }
        ......
    }

原文地址:
https://github.com/CarpenterLee/JCFInternals/blob/master/markdown/6-HashSet%20and%20HashMap.md

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凛俱,一起剝皮案震驚了整個濱河市锐极,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌橡类,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摆碉,死亡現(xiàn)場離奇詭異塘匣,居然都是意外死亡,警方通過查閱死者的電腦和手機巷帝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門忌卤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人楞泼,你說我怎么就攤上這事驰徊。” “怎么了堕阔?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵棍厂,是天一觀的道長。 經(jīng)常有香客問我超陆,道長牺弹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任时呀,我火速辦了婚禮张漂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘退唠。我一直安慰自己鹃锈,他們只是感情好,可當我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布瞧预。 她就那樣靜靜地躺著屎债,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垢油。 梳的紋絲不亂的頭發(fā)上盆驹,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天,我揣著相機與錄音滩愁,去河邊找鬼躯喇。 笑死,一個胖子當著我的面吹牛硝枉,可吹牛的內(nèi)容都是我干的廉丽。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼妻味,長吁一口氣:“原來是場噩夢啊……” “哼正压!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起责球,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤焦履,失蹤者是張志新(化名)和其女友劉穎拓劝,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘉裤,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡郑临,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了屑宠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厢洞。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖侨把,靈堂內(nèi)的尸體忽然破棺而出犀变,到底是詐尸還是另有隱情,我是刑警寧澤秋柄,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布获枝,位于F島的核電站,受9級特大地震影響骇笔,放射性物質(zhì)發(fā)生泄漏省店。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一笨触、第九天 我趴在偏房一處隱蔽的房頂上張望懦傍。 院中可真熱鬧,春花似錦芦劣、人聲如沸粗俱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寸认。三九已至,卻和暖如春串慰,著一層夾襖步出監(jiān)牢的瞬間偏塞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工邦鲫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灸叼,地道東北人。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓庆捺,卻偏偏與公主長得像古今,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子滔以,可洞房花燭夜當晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 一醉者、基本數(shù)據(jù)類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對于byte類型而言...
    龍貓小爺閱讀 4,268評論 0 16
  • 實際上但狭,HashSet 和 HashMap 之間有很多相似之處,對于 HashSet 而言撬即,系統(tǒng)采用 Hash 算...
    曹振華閱讀 2,515評論 1 37
  • 我立磁,一個畢業(yè)四年,施工五年的現(xiàn)場施工人員剥槐。戀愛三年唱歧,結(jié)婚一年,孩子一歲粒竖,卻只能每天對著手機視頻看著兒子一點點成長颅崩。...
    一顆蝸牛閱讀 209評論 0 0
  • 春天最初的歌 是枝上鳥兒們開始的 婉轉(zhuǎn)清麗 她們用喜悅的情懷 來贊美春天的歸來 燕子北回 飛旋在春天的空氣中 唱著...
    清風201閱讀 189評論 0 0
  • 閨女考上三本沿后,高興!說因孩子有學上并且是她喜歡的朽砰;掙錢不多尖滚,高興!說明白有付出就有所得瞧柔;住得不是大平米漆弄,每天也樂呵...
    田真十閱讀 360評論 6 8