精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

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

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

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

簽名(signature)

  1. public class HashMap<K,V> 
  2. extends AbstractMap<K,V> 
  3. implements Map<K,V>, Cloneable, Serializable 

可以看到HashMap繼承了

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

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

比較有意思的是,HashMap同時繼承了抽象類AbstractMap與接口Map,因為抽象類AbstractMap的簽名為

  1. public abstract class AbstractMap<K,V> implements Map<K,V> 

Stack Overfloooow上解釋到:

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

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

Cloneable接口

  1. <code>It's evil, don't use it. </code> 

Cloneable這個接口設(shè)計的非常不好,最致命的一點是它里面竟然沒有clone方法,也就是說我們自己寫的類完全可以實現(xiàn)這個接口的同時不重寫clone方法。

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

Map接口

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

 

Java HashMap 源碼解析
Map_field_method

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

 

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

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

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

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

AbstractMap抽象類

AbstractMapMap中的方法提供了一個基本實現(xiàn),減少了實現(xiàn)Map接口的工作量。

舉例來說:

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

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

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

哈希表(hash table)

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

 

Java HashMap 源碼解析
hash table demo

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

 

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

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

后面會重點介紹HashMap是如何解決這兩個問題的。

HashMap的一些特點

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

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

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

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

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

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

源碼剖析

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

 

  1. public HashMap(int initialCapacity, float loadFactor) { 
  2.     if (initialCapacity < 0
  3.         throw new IllegalArgumentException("Illegal initial capacity: " + 
  4.                                            initialCapacity); 
  5.     if (initialCapacity > MAXIMUM_CAPACITY) 
  6.         initialCapacity = MAXIMUM_CAPACITY; 
  7.     if (loadFactor <= 0 || Float.isNaN(loadFactor)) 
  8.         throw new IllegalArgumentException("Illegal load factor: " + 
  9.                                            loadFactor); 
  10.     this.loadFactor = loadFactor; 
  11.     threshold = initialCapacity; 
  12.     init(); 
  13. public HashMap(int initialCapacity) { 
  14.     this(initialCapacity, DEFAULT_LOAD_FACTOR); 
  15. public HashMap() { 
  16.     this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); 
  17.  
  18. 從代碼上可以看到,容量與平衡因子都有個默認(rèn)值,并且容量有個***值 
  19.  
  20. /** 
  21. * The default initial capacity - MUST be a power of two. 
  22. */ 
  23. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4// aka 16 
  24. /** 
  25. * The maximum capacity, used if a higher value is implicitly specified 
  26. * by either of the constructors with arguments. 
  27. * MUST be a power of two <= 1<<30. 
  28. */ 
  29. static final int MAXIMUM_CAPACITY = 1 << 30
  30. /** 
  31. * The load factor used when none specified in constructor. 
  32. */ 
  33. static final float DEFAULT_LOAD_FACTOR = 0.75f; 

可以看到,默認(rèn)的平衡因子為0.75,這是權(quán)衡了時間復(fù)雜度與空間復(fù)雜度之后的***取值(JDK說是***的),過高的因子會降低存儲空間但是查找(lookup,包括HashMap中的put與get方法)的時間就會增加。

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

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

  1. /** 
  2.   * Retrieve object hash code and applies a supplemental hash function to the 
  3.   * result hash, which defends against poor quality hash functions.  This is 
  4.   * critical because HashMap uses power-of-two length hash tables, that 
  5.   * otherwise encounter collisions for hashCodes that do not differ 
  6.   * in lower bits. Note: Null keys always map to hash 0, thus index 0. 
  7.   */ 
  8. final int hash(Object k) { 
  9.      int h = hashSeed; 
  10.      if (0 != h && k instanceof String) { 
  11.          return sun.misc.Hashing.stringHash32((String) k); 
  12.      } 
  13.      h ^= k.hashCode(); 
  14.      // This function ensures that hashCodes that differ only by 
  15.      // constant multiples at each bit position have a bounded 
  16.      // number of collisions (approximately 8 at default load factor). 
  17.      h ^= (h >>> 20) ^ (h >>> 12); 
  18.      return h ^ (h >>> 7) ^ (h >>> 4); 
  19. /** 
  20.   * Returns index for hash code h. 
  21.   */ 
  22. static int indexFor(int h, int length) { 
  23.      // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; 
  24.      return h & (length-1); 

看到這么多位操作,是不是覺得暈頭轉(zhuǎn)向了呢,還是搞清楚原理就行了,畢竟位操作速度是很快的,不能因為不好理解就不用了。

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

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

  1. 讓length為素數(shù),然后用hashCode(key) mod length的方法得到索引

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

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

因為本篇主題講的是HashMap,所以關(guān)于方法1為什么要用素數(shù),我這里不想過多介紹,大家可以看這里。

重點說說方法2的情況,方法2其實也比較好理解:

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

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

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

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

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

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

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

***,通過一系列無符號右移操作,來把高位與低位進行異或操作,來降低沖突發(fā)生的幾率

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

HashMap.Entry

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

  1. static class Entry<K,V> implements Map.Entry<K,V> { 
  2.     final K key; 
  3.     V value; 
  4.     Entry<K,V> next; 
  5.     int hash; 
  6.     Entry(int h, K k, V v, Entry<K,V> n) { 
  7.         value = v; 
  8.         next = n; 
  9.         key = k; 
  10.         hash = h; 
  11.     } 
  12.     // setter, getter, equals, toString 方法省略 
  13.     public final int hashCode() { 
  14.         //用key的hash值與上value的hash值作為Entry的hash值 
  15.         return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); 
  16.     } 
  17.     /** 
  18.      * This method is invoked whenever the value in an entry is 
  19.      * overwritten by an invocation of put(k,v) for a key k that's already 
  20.      * in the HashMap. 
  21.      */ 
  22.     void recordAccess(HashMap<K,V> m) { 
  23.     } 
  24.     /** 
  25.      * This method is invoked whenever the entry is 
  26.      * removed from the table. 
  27.      */ 
  28.     void recordRemoval(HashMap<K,V> m) { 
  29.     } 

可以看到,Entry實現(xiàn)了單向鏈表的功能,用next成員變量來級連起來。

介紹完Entry對象,下面要說一個比較重要的成員變量

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

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

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

 

Java HashMap 源碼解析
鏈地址法處理沖突得到的散列表

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

 

鏈地址法的可視化

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

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

get操作

get操作相比put操作簡單,所以先介紹get操作

  1. public V get(Object key) { 
  2.     //單獨處理key為null的情況 
  3.     if (key == null
  4.         return getForNullKey(); 
  5.     Entry<K,V> entry = getEntry(key); 
  6.     return null == entry ? null : entry.getValue(); 
  7. private V getForNullKey() { 
  8.     if (size == 0) { 
  9.         return null
  10.     } 
  11.     //key為null的Entry用于放在table[0]中,但是在table[0]沖突鏈中的Entry的key不一定為null 
  12.     //所以需要遍歷沖突鏈,查找key是否存在 
  13.     for (Entry<K,V> e = table[0]; e != null; e = e.next) { 
  14.         if (e.key == null
  15.             return e.value; 
  16.     } 
  17.     return null
  18. final Entry<K,V> getEntry(Object key) { 
  19.     if (size == 0) { 
  20.         return null
  21.     } 
  22.     int hash = (key == null) ? 0 : hash(key); 
  23.     //首先定位到索引在table中的位置 
  24.     //然后遍歷沖突鏈,查找key是否存在 
  25.     for (Entry<K,V> e = table[indexFor(hash, table.length)]; 
  26.          e != null
  27.          e = e.next) { 
  28.         Object k; 
  29.         if (e.hash == hash && 
  30.             ((k = e.key) == key || (key != null && key.equals(k)))) 
  31.             return e; 
  32.     } 
  33.     return null

put操作(含update操作)

因為put操作有可能需要對HashMap進行resize,所以實現(xiàn)略復(fù)雜些

  1. private void inflateTable(int toSize) { 
  2.     //輔助函數(shù),用于填充HashMap到指定的capacity 
  3.     // Find a power of 2 >= toSize 
  4.     int capacity = roundUpToPowerOf2(toSize); 
  5.     //threshold為resize的閾值,超過后HashMap會進行resize,內(nèi)容的entry會進行rehash 
  6.     threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); 
  7.     table = new Entry[capacity]; 
  8.     initHashSeedAsNeeded(capacity); 
  9. /** 
  10. * Associates the specified value with the specified key in this map. 
  11. * If the map previously contained a mapping for the key, the old 
  12. * value is replaced. 
  13. */ 
  14. public V put(K key, V value) { 
  15.     if (table == EMPTY_TABLE) { 
  16.         inflateTable(threshold); 
  17.     } 
  18.     if (key == null
  19.         return putForNullKey(value); 
  20.     int hash = hash(key); 
  21.     int i = indexFor(hash, table.length); 
  22.     //這里的循環(huán)是關(guān)鍵 
  23.     //當(dāng)新增的key所對應(yīng)的索引i,對應(yīng)table[i]中已經(jīng)有值時,進入循環(huán)體 
  24.     for (Entry<K,V> e = table[i]; e != null; e = e.next) { 
  25.         Object k; 
  26.         //判斷是否存在本次插入的key,如果存在用本次的value替換之前oldValue,相當(dāng)于update操作 
  27.         //并返回之前的oldValue 
  28.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
  29.             V oldValue = e.value; 
  30.             e.value = value; 
  31.             e.recordAccess(this); 
  32.             return oldValue; 
  33.         } 
  34.     } 
  35.     //如果本次新增key之前不存在于HashMap中,modCount加1,說明結(jié)構(gòu)改變了 
  36.     modCount++; 
  37.     addEntry(hash, key, value, i); 
  38.     return null
  39. void addEntry(int hash, K key, V value, int bucketIndex) { 
  40.     //如果增加一個元素會后,HashMap的大小超過閾值,需要resize 
  41.     if ((size >= threshold) && (null != table[bucketIndex])) { 
  42.         //增加的幅度是之前的1倍 
  43.         resize(2 * table.length); 
  44.         hash = (null != key) ? hash(key) : 0
  45.         bucketIndex = indexFor(hash, table.length); 
  46.     } 
  47.     createEntry(hash, key, value, bucketIndex); 
  48. void createEntry(int hash, K key, V value, int bucketIndex) { 
  49.     //首先得到該索引處的沖突鏈Entries,有可能為null,不為null 
  50.     Entry<K,V> e = table[bucketIndex]; 
  51.     //然后把新的Entry添加到?jīng)_突鏈的開頭,也就是說,后插入的反而在前面(***次還真沒看明白) 
  52.     //需要注意的是table[bucketIndex]本身并不存儲節(jié)點信息, 
  53.     //它就相當(dāng)于是單向鏈表的頭指針,數(shù)據(jù)都存放在沖突鏈中。 
  54.     table[bucketIndex] = new Entry<>(hash, key, value, e); 
  55.     size++; 
  56. //下面看看HashMap是如何進行resize,廬山真面目就要揭曉了 
  57. void resize(int newCapacity) { 
  58.     Entry[] oldTable = table; 
  59.     int oldCapacity = oldTable.length; 
  60.     //如果已經(jīng)達(dá)到***容量,那么就直接返回 
  61.     if (oldCapacity == MAXIMUM_CAPACITY) { 
  62.         threshold = Integer.MAX_VALUE; 
  63.         return
  64.     } 
  65.     Entry[] newTable = new Entry[newCapacity]; 
  66.     //initHashSeedAsNeeded(newCapacity)的返回值決定了是否需要重新計算Entry的hash值 
  67.     transfer(newTable, initHashSeedAsNeeded(newCapacity)); 
  68.     table = newTable; 
  69.     threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); 
  70. /** 
  71. * Transfers all entries from current table to newTable. 
  72. */ 
  73. void transfer(Entry[] newTable, boolean rehash) { 
  74.     int newCapacity = newTable.length; 
  75.     //遍歷當(dāng)前的table,將里面的元素添加到新的newTable中 
  76.     for (Entry<K,V> e : table) { 
  77.         while(null != e) { 
  78.             Entry<K,V> next = e.next; 
  79.             if (rehash) { 
  80.                 e.hash = null == e.key ? 0 : hash(e.key); 
  81.             } 
  82.             int i = indexFor(e.hash, newCapacity); 
  83.             e.next = newTable[i]; 
  84.             //***這兩句用了與put放過相同的技巧 
  85.             //將后插入的反而在前面 
  86.             newTable[i] = e; 
  87.             e = next; 
  88.         } 
  89.     } 
  90. /** 
  91. * Initialize the hashing mask value. We defer initialization until we 
  92. * really need it. 
  93. */ 
  94. final boolean initHashSeedAsNeeded(int capacity) { 
  95.     boolean currentAltHashing = hashSeed != 0
  96.     boolean useAltHashing = sun.misc.VM.isBooted() && 
  97.             (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); 
  98.     //這里說明了,在hashSeed不為0或滿足useAltHash時,會重算Entry的hash值 
  99.     //至于useAltHashing的作用可以參考下面的鏈接 
  100.     // http://stackoverflow.com/questions/29918624/what-is-the-use-of-holder-class-in-hashmap 
  101.     boolean switching = currentAltHashing ^ useAltHashing; 
  102.     if (switching) { 
  103.         hashSeed = useAltHashing 
  104.             ? sun.misc.Hashing.randomHashSeed(this
  105.             : 0
  106.     } 
  107.     return switching; 

remove操作

  1. public V remove(Object key) { 
  2.     Entry<K,V> e = removeEntryForKey(key); 
  3.     //可以看到刪除的key如果存在,就返回其所對應(yīng)的value 
  4.     return (e == null ? null : e.value); 
  5. final Entry<K,V> removeEntryForKey(Object key) { 
  6.     if (size == 0) { 
  7.         return null
  8.     } 
  9.     int hash = (key == null) ? 0 : hash(key); 
  10.     int i = indexFor(hash, table.length); 
  11.     //這里用了兩個Entry對象,相當(dāng)于兩個指針,為的是防治沖突鏈發(fā)生斷裂的情況 
  12.     //這里的思路就是一般的單向鏈表的刪除思路 
  13.     Entry<K,V> prev = table[i]; 
  14.     Entry<K,V> e = prev; 
  15.     //當(dāng)table[i]中存在沖突鏈時,開始遍歷里面的元素 
  16.     while (e != null) { 
  17.         Entry<K,V> next = e.next; 
  18.         Object k; 
  19.         if (e.hash == hash && 
  20.             ((k = e.key) == key || (key != null && key.equals(k)))) { 
  21.             modCount++; 
  22.             size--; 
  23.             if (prev == e) //當(dāng)沖突鏈只有一個Entry時 
  24.                 table[i] = next; 
  25.             else 
  26.                 prev.next = next; 
  27.             e.recordRemoval(this); 
  28.             return e; 
  29.         } 
  30.         prev = e; 
  31.         e = next; 
  32.     } 
  33.     return e; 

到現(xiàn)在為止,HashMap的增刪改查都介紹完了。
一般而言,認(rèn)為HashMap的這四種操作時間復(fù)雜度為O(1),因為它hash函數(shù)性質(zhì)較好,保證了沖突發(fā)生的幾率較小。

HashMap的序列化

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

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

為了解答這個問題,我們需要明確下面事實:

  • Object.hashCode方法對于一個類的兩個實例返回的是不同的哈希值

我們可以試想下面的場景:

我們在機器A上算出對象A的哈希值與索引,然后把它插入到HashMap中,然后把該HashMap序列化后,在機器B上重新算對象的哈希值與索引,這與機器A上算出的是不一樣的,所以我們在機器B上get對象A時,會得到錯誤的結(jié)果。

所以說,當(dāng)序列化一個HashMap對象時,保存Entry的table是不需要序列化進來的,因為它在另一臺機器上是錯誤的。

因為這個原因,HashMap重現(xiàn)了writeObjectreadObject 方法

  1. private void writeObject(java.io.ObjectOutputStream s) 
  2.     throws IOException 
  3.     // Write out the threshold, loadfactor, and any hidden stuff 
  4.     s.defaultWriteObject(); 
  5.  
  6.     // Write out number of buckets 
  7.     if (table==EMPTY_TABLE) { 
  8.         s.writeInt(roundUpToPowerOf2(threshold)); 
  9.     } else { 
  10.        s.writeInt(table.length); 
  11.     } 
  12.  
  13.     // Write out size (number of Mappings) 
  14.     s.writeInt(size); 
  15.  
  16.     // Write out keys and values (alternating) 
  17.     if (size > 0) { 
  18.         for(Map.Entry<K,V> e : entrySet0()) { 
  19.             s.writeObject(e.getKey()); 
  20.             s.writeObject(e.getValue()); 
  21.         } 
  22.     } 
  23.  
  24. private static final long serialVersionUID = 362498820763181265L; 
  25.  
  26. private void readObject(java.io.ObjectInputStream s) 
  27.      throws IOException, ClassNotFoundException 
  28.     // Read in the threshold (ignored), loadfactor, and any hidden stuff 
  29.     s.defaultReadObject(); 
  30.     if (loadFactor <= 0 || Float.isNaN(loadFactor)) { 
  31.         throw new InvalidObjectException("Illegal load factor: " + 
  32.                                            loadFactor); 
  33.     } 
  34.  
  35.     // set other fields that need values 
  36.     table = (Entry<K,V>[]) EMPTY_TABLE; 
  37.  
  38.     // Read in number of buckets 
  39.     s.readInt(); // ignored. 
  40.  
  41.     // Read number of mappings 
  42.     int mappings = s.readInt(); 
  43.     if (mappings < 0
  44.         throw new InvalidObjectException("Illegal mappings count: " + 
  45.                                            mappings); 
  46.  
  47.     // capacity chosen by number of mappings and desired load (if >= 0.25) 
  48.     int capacity = (int) Math.min( 
  49.                 mappings * Math.min(1 / loadFactor, 4.0f), 
  50.                 // we have limits... 
  51.                 HashMap.MAXIMUM_CAPACITY); 
  52.  
  53.     // allocate the bucket array; 
  54.     if (mappings > 0) { 
  55.         inflateTable(capacity); 
  56.     } else { 
  57.         threshold = capacity; 
  58.     } 
  59.  
  60.     init();  // Give subclass a chance to do its thing. 
  61.  
  62.     // Read the keys and values, and put the mappings in the HashMap 
  63.     for (int i = 0; i < mappings; i++) { 
  64.         K key = (K) s.readObject(); 
  65.         V value = (V) s.readObject(); 
  66.         putForCreate(key, value); 
  67.     } 
  68. private void putForCreate(K key, V value) { 
  69.     int hash = null == key ? 0 : hash(key); 
  70.     int i = indexFor(hash, table.length); 
  71.  
  72.     /** 
  73.      * Look for preexisting entry for key.  This will never happen for 
  74.      * clone or deserialize.  It will only happen for construction if the 
  75.      * input Map is a sorted map whose ordering is inconsistent w/ equals. 
  76.      */ 
  77.     for (Entry<K,V> e = table[i]; e != null; e = e.next) { 
  78.         Object k; 
  79.         if (e.hash == hash && 
  80.             ((k = e.key) == key || (key != null && key.equals(k)))) { 
  81.             e.value = value; 
  82.             return
  83.         } 
  84.     } 
  85.  
  86.     createEntry(hash, key, value, i); 

簡單來說,在序列化時,針對Entry的key與value分別單獨序列化,當(dāng)反序列化時,再單獨處理即可。

總結(jié)

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

  • 一方面是由于時間久不用

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

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

還有一點就是我們在分析某些具體類或方法時,不要花太多時間一些細(xì)枝末節(jié)的邊界條件上,這樣很得不償失,倒不是說這么邊界條件不重要,程序的bug往往就是邊界條件沒考慮周全導(dǎo)致的。

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

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

 

 

責(zé)任編輯:王雪燕 來源: ImportNew
相關(guān)推薦

2024-11-08 16:54:38

2015-06-15 10:32:44

Java核心源碼解讀

2015-08-10 15:12:27

Java實例源碼分析

2021-09-16 15:08:08

鴻蒙HarmonyOS應(yīng)用

2015-09-11 09:40:35

Java集合框架

2022-07-19 20:04:31

NAPI模塊鴻蒙

2009-06-29 16:50:27

Java集合框架

2023-10-09 07:57:14

JavaJCF

2021-03-11 07:27:22

Java 集合數(shù)據(jù)

2023-09-05 10:16:02

Java框架

2015-09-16 09:10:27

Java源碼解析

2024-01-18 08:31:22

go實現(xiàn)gorm框架

2012-11-06 11:07:59

jQueryJSjQuery框架

2012-03-12 15:36:29

Java框架

2017-01-05 15:13:03

Java數(shù)組算法解釋

2024-11-08 17:15:49

2020-12-01 15:00:20

Java 基礎(chǔ)

2016-12-15 09:44:31

框架Caffe源碼

2021-02-20 06:09:46

libtask協(xié)程鎖機制

2021-04-12 07:34:03

Java集合框架
點贊
收藏

51CTO技術(shù)棧公眾號

欧美影院精品一区| 国产一区在线看| 日韩精品视频在线观看网址| 欧美亚洲国产成人| 77777影视视频在线观看| 精品一区二区在线免费观看| 欧美激情精品久久久久久| 欧美日韩精品免费观看视频| 成人黄色免费片| 国产亚洲精品久久777777| 麻豆一区一区三区四区| 欧美色老头old∨ideo| 日韩激情视频一区二区| 国产一二三在线观看| 国产一区二区看久久| 欧美在线一区二区三区四| 懂色av蜜臀av粉嫩av永久| 99精品中文字幕在线不卡 | 日本黄色片免费观看| 农村少妇一区二区三区四区五区 | 欧美怡红院视频一区二区三区| 国产三级aaa| 天天躁日日躁狠狠躁欧美| 欧美精品久久99| 亚洲人成无码www久久久| 天堂av资源在线观看| 欧美国产禁国产网站cc| 国产伦精品一区二区三区| 一道本在线视频| 久久综合影视| 91av在线网站| 国产精品suv一区二区| 久久久久蜜桃| 一区二区在线视频| 国产精品三级在线观看无码| 日韩精品视频一区二区三区| 欧美日本视频在线| 88av.com| 性高爱久久久久久久久| 午夜影院在线观看欧美| 久久久天堂国产精品| 日本电影在线观看网站| 久久久不卡网国产精品二区| 国产自产精品| 视频一区 中文字幕| 国产精品88888| 96国产粉嫩美女| 一级特黄aaa大片| 美女在线一区二区| 国产精品爽爽ⅴa在线观看| 中文字字幕在线中文| 国产免费成人| 777777777亚洲妇女| 日韩av在线播放观看| 激情视频一区二区三区| 久久久久久欧美| 久久中文字幕无码| 影音先锋久久久| 欧美国产日韩精品| 免费一级肉体全黄毛片| 韩国自拍一区| 91大神在线播放精品| 波多野结衣国产| 久久国产精品久久w女人spa| 国产福利精品视频| 美女黄页在线观看| 国精产品一区一区三区mba视频| 国产伦精品免费视频| 国产一区二区三区在线观看| 天天干在线观看| 欧美激情精品久久久六区热门| 久久香蕉频线观| 九九视频免费在线观看| 亚洲午夜一级| 欧美一级高清免费| 精品一区二区无码| 另类的小说在线视频另类成人小视频在线| 国产精品高潮粉嫩av| 91禁在线观看| 成人性生交大片免费| 久久久av水蜜桃| 波多野结衣在线影院| 国产精品毛片无遮挡高清| 六月婷婷激情网| sqte在线播放| 黄网站色欧美视频| 欧美一级裸体视频| 日韩欧美久久| 亚洲男人7777| 国产精品三区在线观看| 亚洲精品九九| 国产欧美一区二区三区久久人妖 | 无遮挡亚洲一区| 高清免费电影在线观看| 亚洲18色成人| 国产视频1区2区3区| 最新精品在线| 中文字幕欧美国内| 久草视频手机在线观看| 免费视频一区| 亚洲自拍小视频| 少妇性bbb搡bbb爽爽爽欧美| 国产精品欧美久久久久一区二区| 91免费国产精品| 日韩不卡在线| 亚洲二区在线播放视频| 中文国语毛片高清视频| 国产一区二区精品| 成人欧美一区二区三区黑人孕妇| 91精品人妻一区二区三区| 中文字幕在线播放av| 裸体在线国模精品偷拍| 精品乱码一区| 2021国产在线| 欧美在线不卡一区| 久久一区二区电影| 欧美激情五月| 91久久精品美女高潮| 欧美大片aaa| 亚洲成人av电影在线| 中文字幕第一页在线视频| 美女久久久久| 97超级碰碰碰| 国产77777| 亚洲欧美日韩国产综合| 日本熟妇人妻中出| 色婷婷精品视频| 久久久久国产精品一区| 国产精品玖玖玖| 中文字幕免费观看一区| 欧美 国产 综合| 国产成人高清精品免费5388| 超碰97人人做人人爱少妇| 精品国产青草久久久久96| 91农村精品一区二区在线| 免费看日本黄色| 欧美亚洲人成在线| 中文字幕亚洲无线码在线一区| 欧美亚洲精品天堂| av电影在线观看一区| 久久这里只有精品18| 久久伊人久久| 久久成人综合视频| 精品人妻在线视频| www.蜜桃av.com| 99精品久久久久久| 精品久久一二三| 欧美91在线| 2019av中文字幕| 偷拍25位美女撒尿视频在线观看| 一个色在线综合| 69xxx免费视频| 亚洲欧洲综合| 久久久久久亚洲精品不卡4k岛国| av电影免费在线看| 亚洲电影免费观看高清| 中文字幕第15页| 久久综合99re88久久爱| 日本三级免费观看| 欧美精品一二| 国产精品久久国产精品99gif| 国产片在线观看| 欧美三级日韩三级| 国产性生活大片| 国产在线播放一区三区四| 超薄肉色丝袜足j调教99| 久久天堂久久| …久久精品99久久香蕉国产| 午夜视频在线免费播放| 一本色道久久加勒比精品| 先锋影音av在线| 国产一区二区三区免费看| 日韩极品视频在线观看 | 精品欧美一区二区三区久久久 | 国产精品久久久久久影视| 99视频在线观看地址| 欧美一区二区免费视频| 国产真实的和子乱拍在线观看| 91亚洲永久精品| 浓精h攵女乱爱av| 欧美国产91| 久久久久久a亚洲欧洲aⅴ| jvid一区二区三区| 精品中文字幕视频| 视频一区二区在线播放| 精品视频123区在线观看| 久久久久久久国产精品毛片| 91视频观看免费| 手机精品视频在线| 免费日韩av片| 国产911在线观看| 在线成人动漫av| 亚洲www永久成人夜色| 欧美a级在线观看| 精品国产欧美一区二区五十路| 蜜桃av鲁一鲁一鲁一鲁俄罗斯的| 91高清在线观看| 妺妺窝人体色www在线下载| 国产亚洲人成网站| 亚洲成人精品在线播放| 新67194成人永久网站| 大桥未久一区二区三区| 一道在线中文一区二区三区| 成人黄色短视频在线观看| xxxxxx欧美| 欧美激情日韩图片| 一级日本在线| 日韩精品免费观看| 亚洲av无码乱码国产精品久久| 色婷婷av一区二区三区之一色屋| 欧美精品乱码视频一二专区| 中文字幕欧美激情一区| 大地资源二中文在线影视观看| 国内一区二区在线| 亚洲 欧美 日韩系列| 亚洲黄色三级| 黄色免费高清视频| 欧美亚洲在线日韩| 久久99精品久久久久久久久久| 久久久久久亚洲精品美女| 国产精品香蕉在线观看| 无码小电影在线观看网站免费| 欧美日韩国产成人| 老司机av在线免费看| 曰本色欧美视频在线| 日日躁夜夜躁白天躁晚上躁91| 5858s免费视频成人| 亚洲午夜无码久久久久| 色综合一个色综合亚洲| 日韩成人高清视频| 亚洲午夜久久久久久久久久久| 91人妻一区二区三区蜜臀| 国产欧美日韩一区二区三区在线观看| 黑丝av在线播放| 不卡在线观看av| 91精品又粗又猛又爽| 国产精品系列在线播放| 五月天婷婷影视| 黄色小说综合网站| 91精品999| 精品一区二区在线看| www.这里只有精品| 六月丁香婷婷色狠狠久久| 奇米影音第四色| 免费高清在线视频一区·| 九色porny91| 蜜桃一区二区三区在线观看| 成人免费在线观看视频网站| 男女性色大片免费观看一区二区| 天堂在线资源视频| 免费在线观看精品| 色一情一区二区三区| 九色综合国产一区二区三区| 中文字幕亚洲影院| 国产乱码字幕精品高清av| 亚洲区 欧美区| 成人精品视频一区二区三区 | 日韩欧美ww| 欧美日韩综合网| 日韩三级在线| 中文字幕乱码免费| 欧美日韩ab| 女人和拘做爰正片视频| 久久黄色网页| 中文字幕在线综合| 国产福利一区在线| 老司机免费视频| 国产午夜精品在线观看| 亚洲激情图片网| 亚洲尤物视频在线| 影音先锋在线国产| 欧美撒尿777hd撒尿| 国产www视频| 亚洲精品成人久久电影| 国模精品一区二区| 不卡伊人av在线播放| 欧美少妇网站| 成人a视频在线观看| 亚洲一二三区视频| 欧美日韩亚洲免费| 亚洲国产精品日韩专区av有中文| 人人妻人人做人人爽| 丝袜亚洲精品中文字幕一区| 91精品999| 久久亚洲影视婷婷| 午夜激情视频在线播放| 亚洲中国最大av网站| 久草手机在线视频| 日韩一区二区不卡| 免费在线黄色影片| 欧美精品情趣视频| 三级成人在线| 国产精品一 二 三| 日韩av二区| 成熟了的熟妇毛茸茸| 国产在线视视频有精品| 一级国产黄色片| 亚洲男人都懂的| 久久99国产综合精品免费| 日韩视频123| shkd中文字幕久久在线观看| 一区二区三区视频在线播放| 99riav视频一区二区| 亚洲永久在线观看| 精品国产一区二区三区小蝌蚪 | 日本福利在线| 88国产精品欧美一区二区三区| 亚洲一区有码| 日韩高清专区| 亚洲一区网站| 香蕉视频免费网站| 亚洲日本电影在线| 性高潮视频在线观看| 亚洲精品国产精品国产自| 国内精品久久久久国产| 国产精品成人观看视频国产奇米| 51vv免费精品视频一区二区| 亚洲国产一区二区三区在线| a91a精品视频在线观看| 三上悠亚 电影| 亚洲欧洲精品天堂一级| 日本a级c片免费看三区| 亚洲电影在线看| 波多野结衣在线观看| 91亚色免费| 小说区亚洲自拍另类图片专区| 久久久久国产精品熟女影院| 91影院在线观看| 国产午夜小视频| 亚洲成人性视频| 污污网站在线观看| 亚洲最大的av网站| 国产精品国产一区| 亚洲高清免费在线观看| 日本一区二区成人| 糖心vlog精品一区二区| 亚洲欧美福利视频| 久久uomeier| 你懂的网址一区二区三区| 国产精品尤物| 亚洲第一成人网站| 色吊一区二区三区| 国产在线一二| 国产精品女主播视频| 精品国产午夜| 91亚洲精品久久久蜜桃借种| 国产精品网友自拍| 国产一区二区在线视频观看| 日韩中文字幕在线免费观看| 豆花视频一区| 992tv快乐视频| 岛国精品在线观看| 国产成人无码精品久久久久| 日韩电影中文 亚洲精品乱码| 韩国精品一区| 蜜桃av色综合| 日产国产高清一区二区三区| 少妇av片在线观看| 欧美人伦禁忌dvd放荡欲情| 免费网站成人| 99热最新在线| 一本色道久久综合亚洲精品不卡| 久久丫精品国产亚洲av不卡| 色爱区综合激月婷婷| 免费人成在线观看播放视频| 91亚洲精品久久久| 极品中文字幕一区| 99久久国产精| 欧美三级资源在线| 羞羞视频在线免费国产| 精品在线不卡| 青娱乐精品视频| 欧美成人一区二区三区高清| 精品国产91久久久久久久妲己| 欧美三级网站| 波多野结衣三级在线| 成人综合在线观看| 成人一二三四区| 久久大大胆人体| 亚洲精品无吗| 黄色一级片免费的| 天天色天天爱天天射综合| 成人在线高清视频| 91黄色精品| 久久精品主播| 91视频免费在线看| 亚洲欧美视频在线| 欧美黄色一级| 国产一区二区视频免费在线观看| 国产精品久久久久aaaa樱花| 亚洲AV无码精品色毛片浪潮| 国产精品高潮视频| 国产精品a久久久久| 亚洲一区视频在线播放| 日韩一级大片在线观看| 国产免费不卡| 国产成人永久免费视频| 亚洲国产岛国毛片在线| 天堂中文在线资源| 成人综合网网址|