日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java集合框架之 Java HashMap 源码解析

發(fā)布時(shí)間:2025/3/21 java 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java集合框架之 Java HashMap 源码解析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

繼上一篇文章Java集合框架綜述后,今天正式開始分析具體集合類的代碼,首先以既熟悉又陌生的HashMap開始。

簽名(signature)

  • public?class?HashMap<K,V>?
  • extends?AbstractMap<K,V>?
  • implements?Map<K,V>,?Cloneable,?Serializable?
  • 可以看到HashMap繼承了

    • 標(biāo)記接口Cloneable,用于表明HashMap對(duì)象會(huì)重寫java.lang.Object#clone()方法,HashMap實(shí)現(xiàn)的是淺拷貝(shallow copy)。

    • 標(biāo)記接口Serializable,用于表明HashMap對(duì)象可以被序列化

    比較有意思的是,HashMap同時(shí)繼承了抽象類AbstractMap與接口Map,因?yàn)槌橄箢怉bstractMap的簽名為

  • public?abstract?class?AbstractMap<K,V>?implements?Map<K,V>?
  • Stack Overfloooow上解釋到:

    在語法層面繼承接口Map是多余的,這么做僅僅是為了讓閱讀代碼的人明確知道HashMap是屬于Map體系的,起到了文檔的作用

    AbstractMap相當(dāng)于個(gè)輔助類,Map的一些操作這里面已經(jīng)提供了默認(rèn)實(shí)現(xiàn),后面具體的子類如果沒有特殊行為,可直接使用AbstractMap提供的實(shí)現(xiàn)。

    Cloneable接口

  • <code>It's?evil,?don't?use?it.?</code>?
  • Cloneable這個(gè)接口設(shè)計(jì)的非常不好,最致命的一點(diǎn)是它里面竟然沒有clone方法,也就是說我們自己寫的類完全可以實(shí)現(xiàn)這個(gè)接口的同時(shí)不重寫clone方法。

    關(guān)于Cloneable的不足,大家可以去看看《Effective Java》一書的作者給出的理由,在所給鏈接的文章里,Josh Bloch也會(huì)講如何實(shí)現(xiàn)深拷貝比較好,我這里就不在贅述了。

    Map接口

    在Eclipse中的outline面板可以看到Map接口里面包含以下成員方法與內(nèi)部類:

    Map_field_method

    可以看到,這里的成員方法不外乎是“增刪改查”,這也反映了我們編寫程序時(shí),一定是以“數(shù)據(jù)”為導(dǎo)向的。

    在上篇文章講了Map雖然并不是Collection,但是它提供了三種“集合視角”(collection views),與下面三個(gè)方法一一對(duì)應(yīng):

    • Set<K> keySet(),提供key的集合視角

    • Collection<V> values(),提供value的集合視角

    • Set<Map.Entry<K, V>> entrySet(),提供key-value序?qū)Φ募弦暯?#xff0c;這里用內(nèi)部類Map.Entry表示序?qū)?/p>

    AbstractMap抽象類

    AbstractMap對(duì)Map中的方法提供了一個(gè)基本實(shí)現(xiàn),減少了實(shí)現(xiàn)Map接口的工作量。

    舉例來說:

    如果要實(shí)現(xiàn)個(gè)不可變(unmodifiable)的map,那么只需繼承AbstractMap,然后實(shí)現(xiàn)其entrySet方法,這個(gè)方法返回的set不支持add與remove,同時(shí)這個(gè)set的迭代器(iterator)不支持remove操作即可。

    相反,如果要實(shí)現(xiàn)個(gè)可變(modifiable)的map,首先繼承AbstractMap,然后重寫(override)AbstractMap的put方法,同時(shí)實(shí)現(xiàn)entrySet所返回set的迭代器的remove方法即可。

    設(shè)計(jì)理念(design concept)

    哈希表(hash table)

    HashMap是一種基于哈希表(hash table)實(shí)現(xiàn)的map,哈希表(也叫關(guān)聯(lián)數(shù)組)一種通用的數(shù)據(jù)結(jié)構(gòu),大多數(shù)的現(xiàn)代語言都原生支持,其概念也比較簡(jiǎn)單:key經(jīng)過hash函數(shù)作用后得到一個(gè)槽(buckets或slots)的索引(index),槽中保存著我們想要獲取的值,如下圖所示

    hash table demo

    很容易想到,一些不同的key經(jīng)過同一hash函數(shù)后可能產(chǎn)生相同的索引,也就是產(chǎn)生了沖突,這是在所難免的。
    所以利用哈希表這種數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)具體類時(shí),需要:

    • 設(shè)計(jì)個(gè)好的hash函數(shù),使沖突盡可能的減少

    • 其次是需要解決發(fā)生沖突后如何處理。

    后面會(huì)重點(diǎn)介紹HashMap是如何解決這兩個(gè)問題的。

    HashMap的一些特點(diǎn)

    • 線程非安全,并且允許key與value都為null值,HashTable與之相反,為線程安全,key與value都不允許null值。

    • 不保證其內(nèi)部元素的順序,而且隨著時(shí)間的推移,同一元素的位置也可能改變(resize的情況)

    • put、get操作的時(shí)間復(fù)雜度為O(1)。

    • 遍歷其集合視角的時(shí)間復(fù)雜度與其容量(capacity,槽的個(gè)數(shù))和現(xiàn)有元素的大小(entry的個(gè)數(shù))成正比,所以如果遍歷的性能要求很高, 不要把capactiy設(shè)置的過高或把平衡因子(load factor,當(dāng)entry數(shù)大于capacity*loadFactor時(shí),會(huì)進(jìn)行resize,reside會(huì)導(dǎo)致key進(jìn)行rehash)設(shè)置的過 低。

    • 由于HashMap是線程非安全的,這也就是意味著如果多個(gè)線程同時(shí)對(duì)一hashmap的集合試圖做迭代時(shí)有結(jié)構(gòu)的上改變(添加、刪除entry,只改變entry的value的值不算結(jié)構(gòu)改變),那么會(huì)報(bào)ConcurrentModificationException,專業(yè)術(shù)語叫fail-fast,盡早報(bào)錯(cuò)對(duì)于多線程程序來說是很有必要的。

    • Map m = Collections.synchronizedMap(new HashMap(...));?通過這種方式可以得到一個(gè)線程安全的map。

    源碼剖析

    首先從構(gòu)造函數(shù)開始講,HashMap遵循集合框架的約束,提供了一個(gè)參數(shù)為空的構(gòu)造函數(shù)與有一個(gè)參數(shù)且參數(shù)類型為Map的構(gòu)造函數(shù)。除此之外,還提供了兩個(gè)構(gòu)造函數(shù),用于設(shè)置HashMap的容量(capacity)與平衡因子(loadFactor)。

  • public?HashMap(int?initialCapacity,?float?loadFactor)?{?
  • ????if?(initialCapacity?<?0)?
  • ????????throw?new?IllegalArgumentException("Illegal?initial?capacity:?"?+?
  • ???????????????????????????????????????????initialCapacity);?
  • ????if?(initialCapacity?>?MAXIMUM_CAPACITY)?
  • ????????initialCapacity?=?MAXIMUM_CAPACITY;?
  • ????if?(loadFactor?<=?0?||?Float.isNaN(loadFactor))?
  • ????????throw?new?IllegalArgumentException("Illegal?load?factor:?"?+?
  • ???????????????????????????????????????????loadFactor);?
  • ????this.loadFactor?=?loadFactor;?
  • ????threshold?=?initialCapacity;?
  • ????init();?
  • }?
  • public?HashMap(int?initialCapacity)?{?
  • ????this(initialCapacity,?DEFAULT_LOAD_FACTOR);?
  • }?
  • public?HashMap()?{?
  • ????this(DEFAULT_INITIAL_CAPACITY,?DEFAULT_LOAD_FACTOR);?
  • }?
  • ?
  • 從代碼上可以看到,容量與平衡因子都有個(gè)默認(rèn)值,并且容量有個(gè)最大值?
  • ?
  • /**?
  • *?The?default?initial?capacity?-?MUST?be?a?power?of?two.?
  • */?
  • static?final?int?DEFAULT_INITIAL_CAPACITY?=?1?<<?4;?//?aka?16?
  • /**?
  • *?The?maximum?capacity,?used?if?a?higher?value?is?implicitly?specified?
  • *?by?either?of?the?constructors?with?arguments.?
  • *?MUST?be?a?power?of?two?<=?1<<30.?
  • */?
  • static?final?int?MAXIMUM_CAPACITY?=?1?<<?30;?
  • /**?
  • *?The?load?factor?used?when?none?specified?in?constructor.?
  • */?
  • static?final?float?DEFAULT_LOAD_FACTOR?=?0.75f;?
  • 可以看到,默認(rèn)的平衡因子為0.75,這是權(quán)衡了時(shí)間復(fù)雜度與空間復(fù)雜度之后的最好取值(JDK說是最好的),過高的因子會(huì)降低存儲(chǔ)空間但是查找(lookup,包括HashMap中的put與get方法)的時(shí)間就會(huì)增加。

    這里比較奇怪的是問題:容量必須為2的指數(shù)倍(默認(rèn)為16),這是為什么呢?解答這個(gè)問題,需要了解HashMap中哈希函數(shù)的設(shè)計(jì)原理。

    哈希函數(shù)的設(shè)計(jì)原理

  • /**?
  • ??*?Retrieve?object?hash?code?and?applies?a?supplemental?hash?function?to?the?
  • ??*?result?hash,?which?defends?against?poor?quality?hash?functions.??This?is?
  • ??*?critical?because?HashMap?uses?power-of-two?length?hash?tables,?that?
  • ??*?otherwise?encounter?collisions?for?hashCodes?that?do?not?differ?
  • ??*?in?lower?bits.?Note:?Null?keys?always?map?to?hash?0,?thus?index?0.?
  • ??*/?
  • final?int?hash(Object?k)?{?
  • ?????int?h?=?hashSeed;?
  • ?????if?(0?!=?h?&&?k?instanceof?String)?{?
  • ?????????return?sun.misc.Hashing.stringHash32((String)?k);?
  • ?????}?
  • ?????h?^=?k.hashCode();?
  • ?????//?This?function?ensures?that?hashCodes?that?differ?only?by?
  • ?????//?constant?multiples?at?each?bit?position?have?a?bounded?
  • ?????//?number?of?collisions?(approximately?8?at?default?load?factor).?
  • ?????h?^=?(h?>>>?20)?^?(h?>>>?12);?
  • ?????return?h?^?(h?>>>?7)?^?(h?>>>?4);?
  • }?
  • /**?
  • ??*?Returns?index?for?hash?code?h.?
  • ??*/?
  • static?int?indexFor(int?h,?int?length)?{?
  • ?????//?assert?Integer.bitCount(length)?==?1?:?"length?must?be?a?non-zero?power?of?2";?
  • ?????return?h?&?(length-1);?
  • }?
  • 看到這么多位操作,是不是覺得暈頭轉(zhuǎn)向了呢,還是搞清楚原理就行了,畢竟位操作速度是很快的,不能因?yàn)椴缓美斫饩筒挥昧恕?/p>

    網(wǎng)上說這個(gè)問題的也比較多,我這里根據(jù)自己的理解,盡量做到通俗易懂。

    在哈希表容量(也就是buckets或slots大小)為length的情況下,為了使每個(gè)key都能在沖突最小的情況下映射到[0,length)(注意是左閉右開區(qū)間)的索引(index)內(nèi),一般有兩種做法:

  • 讓length為素?cái)?shù),然后用hashCode(key) mod length的方法得到索引

  • 讓length為2的指數(shù)倍,然后用hashCode(key) & (length-1)的方法得到索引

  • HashTable用的是方法1,HashMap用的是方法2。

    因?yàn)楸酒黝}講的是HashMap,所以關(guān)于方法1為什么要用素?cái)?shù),我這里不想過多介紹,大家可以看這里。

    重點(diǎn)說說方法2的情況,方法2其實(shí)也比較好理解:

    因?yàn)閘ength為2的指數(shù)倍,所以length-1所對(duì)應(yīng)的二進(jìn)制位都為1,然后在與hashCode(key)做與運(yùn)算,即可得到[0,length)內(nèi)的索引

    但是這里有個(gè)問題,如果hashCode(key)的大于length的值,而且hashCode(key)的二進(jìn)制位的低位變化不大,那么沖突就會(huì)很多,舉個(gè)例子:

    Java中對(duì)象的哈希值都32位整數(shù),而HashMap默認(rèn)大小為16,那么有兩個(gè)對(duì)象那么的哈希值分別為:0xABAB0000與0xBABA0000,它們的后幾位都是一樣,那么與16異或后得到結(jié)果應(yīng)該也是一樣的,也就是產(chǎn)生了沖突。

    造成沖突的原因關(guān)鍵在于16限制了只能用低位來計(jì)算,高位直接舍棄了,所以我們需要額外的哈希函數(shù)而不只是簡(jiǎn)單的對(duì)象的hashCode方法了。

    具體來說,就是HashMap中hash函數(shù)干的事了

    首先有個(gè)隨機(jī)的hashSeed,來降低沖突發(fā)生的幾率

    然后如果是字符串,用了sun.misc.Hashing.stringHash32((String) k);來獲取索引值

    最后,通過一系列無符號(hào)右移操作,來把高位與低位進(jìn)行異或操作,來降低沖突發(fā)生的幾率

    右移的偏移量20,12,7,4是怎么來的呢?因?yàn)镴ava中對(duì)象的哈希值都是32位的,所以這幾個(gè)數(shù)應(yīng)該就是把高位與低位做異或運(yùn)算,至于這幾個(gè)數(shù)是如何選取的,就不清楚了,網(wǎng)上搜了半天也沒統(tǒng)一且讓人信服的說法,大家可以參考下面幾個(gè)鏈接:

    • http://stackoverflow.com/questions/7922019/openjdks-rehashing-mechanism/7922219#7922219

    • http://stackoverflow.com/questions/9335169/understanding-strange-java-hash-function/9336103#9336103

    • http://stackoverflow.com/questions/14453163/can-anybody-explain-how-java-design-hashmaps-hash-function/14479945#14479945

    HashMap.Entry

    HashMap中存放的是HashMap.Entry對(duì)象,它繼承自Map.Entry,其比較重要的是構(gòu)造函數(shù)

  • static?class?Entry<K,V>?implements?Map.Entry<K,V>?{?
  • ????final?K?key;?
  • ????V?value;?
  • ????Entry<K,V>?next;?
  • ????int?hash;?
  • ????Entry(int?h,?K?k,?V?v,?Entry<K,V>?n)?{?
  • ????????value?=?v;?
  • ????????next?=?n;?
  • ????????key?=?k;?
  • ????????hash?=?h;?
  • ????}?
  • ????//?setter,?getter,?equals,?toString?方法省略?
  • ????public?final?int?hashCode()?{?
  • ????????//用key的hash值與上value的hash值作為Entry的hash值?
  • ????????return?Objects.hashCode(getKey())?^?Objects.hashCode(getValue());?
  • ????}?
  • ????/**?
  • ?????*?This?method?is?invoked?whenever?the?value?in?an?entry?is?
  • ?????*?overwritten?by?an?invocation?of?put(k,v)?for?a?key?k?that's?already?
  • ?????*?in?the?HashMap.?
  • ?????*/?
  • ????void?recordAccess(HashMap<K,V>?m)?{?
  • ????}?
  • ????/**?
  • ?????*?This?method?is?invoked?whenever?the?entry?is?
  • ?????*?removed?from?the?table.?
  • ?????*/?
  • ????void?recordRemoval(HashMap<K,V>?m)?{?
  • ????}?
  • }?
  • 可以看到,Entry實(shí)現(xiàn)了單向鏈表的功能,用next成員變量來級(jí)連起來。

    介紹完Entry對(duì)象,下面要說一個(gè)比較重要的成員變量

    /**
    * The table, resized as necessary. Length MUST Always be a power of two.
    */
    //HashMap內(nèi)部維護(hù)了一個(gè)為數(shù)組類型的Entry變量table,用來保存添加進(jìn)來的Entry對(duì)象
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

    你也許會(huì)疑問,Entry不是單向鏈表嘛,怎么這里又需要個(gè)數(shù)組類型的table呢?

    我翻了下之前的算法書,其實(shí)這是解決沖突的一個(gè)方式:鏈地址法(開散列法),效果如下:

    鏈地址法處理沖突得到的散列表

    就是相同索引值的Entry,會(huì)以單向鏈表的形式存在

    鏈地址法的可視化

    網(wǎng)上找到個(gè)很好的網(wǎng)站,用來可視化各種常見的算法,很棒。瞬間覺得國外大學(xué)比國內(nèi)的強(qiáng)不知多少倍。

    下面的鏈接可以模仿哈希表采用鏈地址法解決沖突,大家可以自己去玩玩

    • https://www.cs.usfca.edu/~galles/visualization/OpenHash.html

    get操作

    get操作相比put操作簡(jiǎn)單,所以先介紹get操作

  • public?V?get(Object?key)?{?
  • ????//單獨(dú)處理key為null的情況?
  • ????if?(key?==?null)?
  • ????????return?getForNullKey();?
  • ????Entry<K,V>?entry?=?getEntry(key);?
  • ????return?null?==?entry???null?:?entry.getValue();?
  • }?
  • private?V?getForNullKey()?{?
  • ????if?(size?==?0)?{?
  • ????????return?null;?
  • ????}?
  • ????//key為null的Entry用于放在table[0]中,但是在table[0]沖突鏈中的Entry的key不一定為null?
  • ????//所以需要遍歷沖突鏈,查找key是否存在?
  • ????for?(Entry<K,V>?e?=?table[0];?e?!=?null;?e?=?e.next)?{?
  • ????????if?(e.key?==?null)?
  • ????????????return?e.value;?
  • ????}?
  • ????return?null;?
  • }?
  • final?Entry<K,V>?getEntry(Object?key)?{?
  • ????if?(size?==?0)?{?
  • ????????return?null;?
  • ????}?
  • ????int?hash?=?(key?==?null)???0?:?hash(key);?
  • ????//首先定位到索引在table中的位置?
  • ????//然后遍歷沖突鏈,查找key是否存在?
  • ????for?(Entry<K,V>?e?=?table[indexFor(hash,?table.length)];?
  • ?????????e?!=?null;?
  • ?????????e?=?e.next)?{?
  • ????????Object?k;?
  • ????????if?(e.hash?==?hash?&&?
  • ????????????((k?=?e.key)?==?key?||?(key?!=?null?&&?key.equals(k))))?
  • ????????????return?e;?
  • ????}?
  • ????return?null;?
  • }?
  • put操作(含update操作)

    因?yàn)閜ut操作有可能需要對(duì)HashMap進(jìn)行resize,所以實(shí)現(xiàn)略復(fù)雜些

  • private?void?inflateTable(int?toSize)?{?
  • ????//輔助函數(shù),用于填充HashMap到指定的capacity?
  • ????//?Find?a?power?of?2?>=?toSize?
  • ????int?capacity?=?roundUpToPowerOf2(toSize);?
  • ????//threshold為resize的閾值,超過后HashMap會(huì)進(jìn)行resize,內(nèi)容的entry會(huì)進(jìn)行rehash?
  • ????threshold?=?(int)?Math.min(capacity?*?loadFactor,?MAXIMUM_CAPACITY?+?1);?
  • ????table?=?new?Entry[capacity];?
  • ????initHashSeedAsNeeded(capacity);?
  • }?
  • /**?
  • *?Associates?the?specified?value?with?the?specified?key?in?this?map.?
  • *?If?the?map?previously?contained?a?mapping?for?the?key,?the?old?
  • *?value?is?replaced.?
  • */?
  • public?V?put(K?key,?V?value)?{?
  • ????if?(table?==?EMPTY_TABLE)?{?
  • ????????inflateTable(threshold);?
  • ????}?
  • ????if?(key?==?null)?
  • ????????return?putForNullKey(value);?
  • ????int?hash?=?hash(key);?
  • ????int?i?=?indexFor(hash,?table.length);?
  • ????//這里的循環(huán)是關(guān)鍵?
  • ????//當(dāng)新增的key所對(duì)應(yīng)的索引i,對(duì)應(yīng)table[i]中已經(jīng)有值時(shí),進(jìn)入循環(huán)體?
  • ????for?(Entry<K,V>?e?=?table[i];?e?!=?null;?e?=?e.next)?{?
  • ????????Object?k;?
  • ????????//判斷是否存在本次插入的key,如果存在用本次的value替換之前oldValue,相當(dāng)于update操作?
  • ????????//并返回之前的oldValue?
  • ????????if?(e.hash?==?hash?&&?((k?=?e.key)?==?key?||?key.equals(k)))?{?
  • ????????????V?oldValue?=?e.value;?
  • ????????????e.value?=?value;?
  • ????????????e.recordAccess(this);?
  • ????????????return?oldValue;?
  • ????????}?
  • ????}?
  • ????//如果本次新增key之前不存在于HashMap中,modCount加1,說明結(jié)構(gòu)改變了?
  • ????modCount++;?
  • ????addEntry(hash,?key,?value,?i);?
  • ????return?null;?
  • }?
  • void?addEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{?
  • ????//如果增加一個(gè)元素會(huì)后,HashMap的大小超過閾值,需要resize?
  • ????if?((size?>=?threshold)?&&?(null?!=?table[bucketIndex]))?{?
  • ????????//增加的幅度是之前的1倍?
  • ????????resize(2?*?table.length);?
  • ????????hash?=?(null?!=?key)???hash(key)?:?0;?
  • ????????bucketIndex?=?indexFor(hash,?table.length);?
  • ????}?
  • ????createEntry(hash,?key,?value,?bucketIndex);?
  • }?
  • void?createEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{?
  • ????//首先得到該索引處的沖突鏈Entries,有可能為null,不為null?
  • ????Entry<K,V>?e?=?table[bucketIndex];?
  • ????//然后把新的Entry添加到?jīng)_突鏈的開頭,也就是說,后插入的反而在前面(第一次還真沒看明白)?
  • ????//需要注意的是table[bucketIndex]本身并不存儲(chǔ)節(jié)點(diǎn)信息,?
  • ????//它就相當(dāng)于是單向鏈表的頭指針,數(shù)據(jù)都存放在沖突鏈中。?
  • ????table[bucketIndex]?=?new?Entry<>(hash,?key,?value,?e);?
  • ????size++;?
  • }?
  • //下面看看HashMap是如何進(jìn)行resize,廬山真面目就要揭曉了?
  • void?resize(int?newCapacity)?{?
  • ????Entry[]?oldTable?=?table;?
  • ????int?oldCapacity?=?oldTable.length;?
  • ????//如果已經(jīng)達(dá)到最大容量,那么就直接返回?
  • ????if?(oldCapacity?==?MAXIMUM_CAPACITY)?{?
  • ????????threshold?=?Integer.MAX_VALUE;?
  • ????????return;?
  • ????}?
  • ????Entry[]?newTable?=?new?Entry[newCapacity];?
  • ????//initHashSeedAsNeeded(newCapacity)的返回值決定了是否需要重新計(jì)算Entry的hash值?
  • ????transfer(newTable,?initHashSeedAsNeeded(newCapacity));?
  • ????table?=?newTable;?
  • ????threshold?=?(int)Math.min(newCapacity?*?loadFactor,?MAXIMUM_CAPACITY?+?1);?
  • }?
  • /**?
  • *?Transfers?all?entries?from?current?table?to?newTable.?
  • */?
  • void?transfer(Entry[]?newTable,?boolean?rehash)?{?
  • ????int?newCapacity?=?newTable.length;?
  • ????//遍歷當(dāng)前的table,將里面的元素添加到新的newTable中?
  • ????for?(Entry<K,V>?e?:?table)?{?
  • ????????while(null?!=?e)?{?
  • ????????????Entry<K,V>?next?=?e.next;?
  • ????????????if?(rehash)?{?
  • ????????????????e.hash?=?null?==?e.key???0?:?hash(e.key);?
  • ????????????}?
  • ????????????int?i?=?indexFor(e.hash,?newCapacity);?
  • ????????????e.next?=?newTable[i];?
  • ????????????//最后這兩句用了與put放過相同的技巧?
  • ????????????//將后插入的反而在前面?
  • ????????????newTable[i]?=?e;?
  • ????????????e?=?next;?
  • ????????}?
  • ????}?
  • }?
  • /**?
  • *?Initialize?the?hashing?mask?value.?We?defer?initialization?until?we?
  • *?really?need?it.?
  • */?
  • final?boolean?initHashSeedAsNeeded(int?capacity)?{?
  • ????boolean?currentAltHashing?=?hashSeed?!=?0;?
  • ????boolean?useAltHashing?=?sun.misc.VM.isBooted()?&&?
  • ????????????(capacity?>=?Holder.ALTERNATIVE_HASHING_THRESHOLD);?
  • ????//這里說明了,在hashSeed不為0或滿足useAltHash時(shí),會(huì)重算Entry的hash值?
  • ????//至于useAltHashing的作用可以參考下面的鏈接?
  • ????//?http://stackoverflow.com/questions/29918624/what-is-the-use-of-holder-class-in-hashmap?
  • ????boolean?switching?=?currentAltHashing?^?useAltHashing;?
  • ????if?(switching)?{?
  • ????????hashSeed?=?useAltHashing?
  • ??????????????sun.misc.Hashing.randomHashSeed(this)?
  • ????????????:?0;?
  • ????}?
  • ????return?switching;?
  • }?
  • remove操作

  • public?V?remove(Object?key)?{?
  • ????Entry<K,V>?e?=?removeEntryForKey(key);?
  • ????//可以看到刪除的key如果存在,就返回其所對(duì)應(yīng)的value?
  • ????return?(e?==?null???null?:?e.value);?
  • }?
  • final?Entry<K,V>?removeEntryForKey(Object?key)?{?
  • ????if?(size?==?0)?{?
  • ????????return?null;?
  • ????}?
  • ????int?hash?=?(key?==?null)???0?:?hash(key);?
  • ????int?i?=?indexFor(hash,?table.length);?
  • ????//這里用了兩個(gè)Entry對(duì)象,相當(dāng)于兩個(gè)指針,為的是防治沖突鏈發(fā)生斷裂的情況?
  • ????//這里的思路就是一般的單向鏈表的刪除思路?
  • ????Entry<K,V>?prev?=?table[i];?
  • ????Entry<K,V>?e?=?prev;?
  • ????//當(dāng)table[i]中存在沖突鏈時(shí),開始遍歷里面的元素?
  • ????while?(e?!=?null)?{?
  • ????????Entry<K,V>?next?=?e.next;?
  • ????????Object?k;?
  • ????????if?(e.hash?==?hash?&&?
  • ????????????((k?=?e.key)?==?key?||?(key?!=?null?&&?key.equals(k))))?{?
  • ????????????modCount++;?
  • ????????????size--;?
  • ????????????if?(prev?==?e)?//當(dāng)沖突鏈只有一個(gè)Entry時(shí)?
  • ????????????????table[i]?=?next;?
  • ????????????else?
  • ????????????????prev.next?=?next;?
  • ????????????e.recordRemoval(this);?
  • ????????????return?e;?
  • ????????}?
  • ????????prev?=?e;?
  • ????????e?=?next;?
  • ????}?
  • ????return?e;?
  • }?
  • 到現(xiàn)在為止,HashMap的增刪改查都介紹完了。
    一般而言,認(rèn)為HashMap的這四種操作時(shí)間復(fù)雜度為O(1),因?yàn)樗黨ash函數(shù)性質(zhì)較好,保證了沖突發(fā)生的幾率較小。

    HashMap的序列化

    介紹到這里,基本上算是把HashMap中一些核心的點(diǎn)講完了,但還有個(gè)比較嚴(yán)重的問題:保存Entry的table數(shù)組為transient的,也就是說在進(jìn)行序列化時(shí),并不會(huì)包含該成員,這是為什么呢?

    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

    為了解答這個(gè)問題,我們需要明確下面事實(shí):

    • Object.hashCode方法對(duì)于一個(gè)類的兩個(gè)實(shí)例返回的是不同的哈希值

    我們可以試想下面的場(chǎng)景:

    我們?cè)跈C(jī)器A上算出對(duì)象A的哈希值與索引,然后把它插入到HashMap中,然后把該HashMap序列化后,在機(jī)器B上重新算對(duì)象的哈希值與索引,這與機(jī)器A上算出的是不一樣的,所以我們?cè)跈C(jī)器B上get對(duì)象A時(shí),會(huì)得到錯(cuò)誤的結(jié)果。

    所以說,當(dāng)序列化一個(gè)HashMap對(duì)象時(shí),保存Entry的table是不需要序列化進(jìn)來的,因?yàn)樗诹硪慌_(tái)機(jī)器上是錯(cuò)誤的。

    因?yàn)檫@個(gè)原因,HashMap重現(xiàn)了writeObject與readObject?方法

  • private?void?writeObject(java.io.ObjectOutputStream?s)?
  • ????throws?IOException?
  • {?
  • ????//?Write?out?the?threshold,?loadfactor,?and?any?hidden?stuff?
  • ????s.defaultWriteObject();?
  • ?
  • ????//?Write?out?number?of?buckets?
  • ????if?(table==EMPTY_TABLE)?{?
  • ????????s.writeInt(roundUpToPowerOf2(threshold));?
  • ????}?else?{?
  • ???????s.writeInt(table.length);?
  • ????}?
  • ?
  • ????//?Write?out?size?(number?of?Mappings)?
  • ????s.writeInt(size);?
  • ?
  • ????//?Write?out?keys?and?values?(alternating)?
  • ????if?(size?>?0)?{?
  • ????????for(Map.Entry<K,V>?e?:?entrySet0())?{?
  • ????????????s.writeObject(e.getKey());?
  • ????????????s.writeObject(e.getValue());?
  • ????????}?
  • ????}?
  • }?
  • ?
  • private?static?final?long?serialVersionUID?=?362498820763181265L;?
  • ?
  • private?void?readObject(java.io.ObjectInputStream?s)?
  • ?????throws?IOException,?ClassNotFoundException?
  • {?
  • ????//?Read?in?the?threshold?(ignored),?loadfactor,?and?any?hidden?stuff?
  • ????s.defaultReadObject();?
  • ????if?(loadFactor?<=?0?||?Float.isNaN(loadFactor))?{?
  • ????????throw?new?InvalidObjectException("Illegal?load?factor:?"?+?
  • ???????????????????????????????????????????loadFactor);?
  • ????}?
  • ?
  • ????//?set?other?fields?that?need?values?
  • ????table?=?(Entry<K,V>[])?EMPTY_TABLE;?
  • ?
  • ????//?Read?in?number?of?buckets?
  • ????s.readInt();?//?ignored.?
  • ?
  • ????//?Read?number?of?mappings?
  • ????int?mappings?=?s.readInt();?
  • ????if?(mappings?<?0)?
  • ????????throw?new?InvalidObjectException("Illegal?mappings?count:?"?+?
  • ???????????????????????????????????????????mappings);?
  • ?
  • ????//?capacity?chosen?by?number?of?mappings?and?desired?load?(if?>=?0.25)?
  • ????int?capacity?=?(int)?Math.min(?
  • ????????????????mappings?*?Math.min(1?/?loadFactor,?4.0f),?
  • ????????????????//?we?have?limits...?
  • ????????????????HashMap.MAXIMUM_CAPACITY);?
  • ?
  • ????//?allocate?the?bucket?array;?
  • ????if?(mappings?>?0)?{?
  • ????????inflateTable(capacity);?
  • ????}?else?{?
  • ????????threshold?=?capacity;?
  • ????}?
  • ?
  • ????init();??//?Give?subclass?a?chance?to?do?its?thing.?
  • ?
  • ????//?Read?the?keys?and?values,?and?put?the?mappings?in?the?HashMap?
  • ????for?(int?i?=?0;?i?<?mappings;?i++)?{?
  • ????????K?key?=?(K)?s.readObject();?
  • ????????V?value?=?(V)?s.readObject();?
  • ????????putForCreate(key,?value);?
  • ????}?
  • }?
  • private?void?putForCreate(K?key,?V?value)?{?
  • ????int?hash?=?null?==?key???0?:?hash(key);?
  • ????int?i?=?indexFor(hash,?table.length);?
  • ?
  • ????/**?
  • ?????*?Look?for?preexisting?entry?for?key.??This?will?never?happen?for?
  • ?????*?clone?or?deserialize.??It?will?only?happen?for?construction?if?the?
  • ?????*?input?Map?is?a?sorted?map?whose?ordering?is?inconsistent?w/?equals.?
  • ?????*/?
  • ????for?(Entry<K,V>?e?=?table[i];?e?!=?null;?e?=?e.next)?{?
  • ????????Object?k;?
  • ????????if?(e.hash?==?hash?&&?
  • ????????????((k?=?e.key)?==?key?||?(key?!=?null?&&?key.equals(k))))?{?
  • ????????????e.value?=?value;?
  • ????????????return;?
  • ????????}?
  • ????}?
  • ?
  • ????createEntry(hash,?key,?value,?i);?
  • }?
  • 簡(jiǎn)單來說,在序列化時(shí),針對(duì)Entry的key與value分別單獨(dú)序列化,當(dāng)反序列化時(shí),再單獨(dú)處理即可。

    總結(jié)

    在總結(jié)完HashMap后,發(fā)現(xiàn)這里面一些核心的東西,像哈希表的沖突解決,都是算法課上學(xué)到,不過由于“年代久遠(yuǎn)”,已經(jīng)忘得差不多了,我覺得忘

    • 一方面是由于時(shí)間久不用

    • 另一方面是由于本身沒理解好

    平時(shí)多去思考,這樣在遇到一些性能問題時(shí)也好排查。

    還有一點(diǎn)就是我們?cè)诜治瞿承┚唧w類或方法時(shí),不要花太多時(shí)間一些細(xì)枝末節(jié)的邊界條件上,這樣很得不償失,倒不是說這么邊界條件不重要,程序的bug往往就是邊界條件沒考慮周全導(dǎo)致的。

    只是說我們可以在理解了這個(gè)類或方法的總體思路后,再來分析這些邊界條件。

    如果一開始就分析,那真是丈二和尚——摸不著頭腦了,隨著對(duì)它工作原理的加深,才有可能理解這些邊界條件的場(chǎng)景。

    from:?http://developer.51cto.com/art/201509/491119.htm

    總結(jié)

    以上是生活随笔為你收集整理的Java集合框架之 Java HashMap 源码解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。