繼上一篇文章Java集合框架綜述后,今天正式開始分析具體集合類的代碼,首先以既熟悉又陌生的HashMap開始。
簽名(signature)
public ? class ?HashMap<K,V>? extends ?AbstractMap<K,V>? implements ?Map<K,V>,?Cloneable,?Serializable?
可以看到HashMap繼承了
比較有意思的是,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í),需要:
后面會(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è)最大值? ? ? ? ? static ? final ? int ?DEFAULT_INITIAL_CAPACITY?=? 1 ?<<? 4 ;? ? ? ? ? ? ? static ? final ? int ?MAXIMUM_CAPACITY?=? 1 ?<<? 30 ;? ? ? ? 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ì)原理
? ? ? ? ? ? ? final ? int ?hash(Object?k)?{? ?????int ?h?=?hashSeed;? ?????if ?( 0 ?!=?h?&&?k? instanceof ?String)?{? ?????????return ?sun.misc.Hashing.stringHash32((String)?k);? ?????}? ?????h?^=?k.hashCode();? ?????? ?????? ?????? ?????h?^=?(h?>>>?20 )?^?(h?>>>? 12 );? ?????return ?h?^?(h?>>>? 7 )?^?(h?>>>? 4 );? }? ? ? ? static ? int ?indexFor( int ?h,? int ?length)?{? ?????? ?????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;? ????}? ????? ????public ? final ? int ?hashCode()?{? ????????? ????????return ?Objects.hashCode(getKey())?^?Objects.hashCode(getValue());? ????}? ????? ? ? ? ? ????void ?recordAccess(HashMap<K,V>?m)?{? ????}? ????? ? ? ? ????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)不知多少倍。
下面的鏈接可以模仿哈希表采用鏈地址法解決沖突,大家可以自己去玩玩
get操作
get操作相比put操作簡(jiǎn)單,所以先介紹get操作
public ?V?get(Object?key)?{? ????? ????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 ;? ????}? ????? ????? ????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);? ????? ????? ????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)?{? ????? ????? ????int ?capacity?=?roundUpToPowerOf2(toSize);? ????? ????threshold?=?(int )?Math.min(capacity?*?loadFactor,?MAXIMUM_CAPACITY?+? 1 );? ????table?=?new ?Entry[capacity];? ????initHashSeedAsNeeded(capacity);? }? ? ? ? ? ? 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);? ????? ????? ????for ?(Entry<K,V>?e?=?table[i];?e?!=? null ;?e?=?e.next)?{? ????????Object?k;? ????????? ????????? ????????if ?(e.hash?==?hash?&&?((k?=?e.key)?==?key?||?key.equals(k)))?{? ????????????V?oldValue?=?e.value;? ????????????e.value?=?value;? ????????????e.recordAccess(this );? ????????????return ?oldValue;? ????????}? ????}? ????? ????modCount++;? ????addEntry(hash,?key,?value,?i);? ????return ? null ;? }? 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?=?indexFor(hash,?table.length);? ????}? ????createEntry(hash,?key,?value,?bucketIndex);? }? void ?createEntry( int ?hash,?K?key,?V?value,? int ?bucketIndex)?{? ????? ????Entry<K,V>?e?=?table[bucketIndex];? ????? ????? ????? ????table[bucketIndex]?=?new ?Entry<>(hash,?key,?value,?e);? ????size++;? }? ? void ?resize( int ?newCapacity)?{? ????Entry[]?oldTable?=?table;? ????int ?oldCapacity?=?oldTable.length;? ????? ????if ?(oldCapacity?==?MAXIMUM_CAPACITY)?{? ????????threshold?=?Integer.MAX_VALUE;? ????????return ;? ????}? ????Entry[]?newTable?=?new ?Entry[newCapacity];? ????? ????transfer(newTable,?initHashSeedAsNeeded(newCapacity));? ????table?=?newTable;? ????threshold?=?(int )Math.min(newCapacity?*?loadFactor,?MAXIMUM_CAPACITY?+? 1 );? }? ? ? ? void ?transfer(Entry[]?newTable,? boolean ?rehash)?{? ????int ?newCapacity?=?newTable.length;? ????? ????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];? ????????????? ????????????? ????????????newTable[i]?=?e;? ????????????e?=?next;? ????????}? ????}? }? ? ? ? ? final ? boolean ?initHashSeedAsNeeded( int ?capacity)?{? ????boolean ?currentAltHashing?=?hashSeed?!=? 0 ;? ????boolean ?useAltHashing?=?sun.misc.VM.isBooted()?&&? ????????????(capacity?>=?Holder.ALTERNATIVE_HASHING_THRESHOLD);? ????? ????? ????? ????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);? ????? ????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);? ????? ????? ????Entry<K,V>?prev?=?table[i];? ????Entry<K,V>?e?=?prev;? ????? ????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)? ? ????????????????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í):
我們可以試想下面的場(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? {? ????? ????s.defaultWriteObject();? ? ????? ????if ?(table==EMPTY_TABLE)?{? ????????s.writeInt(roundUpToPowerOf2(threshold));? ????}?else ?{? ???????s.writeInt(table.length);? ????}? ? ????? ????s.writeInt(size);? ? ????? ????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? {? ????? ????s.defaultReadObject();? ????if ?(loadFactor?<=? 0 ?||?Float.isNaN(loadFactor))?{? ????????throw ? new ?InvalidObjectException( "Illegal?load?factor:?" ?+? ???????????????????????????????????????????loadFactor);? ????}? ? ????? ????table?=?(Entry<K,V>[])?EMPTY_TABLE;? ? ????? ????s.readInt();?? ? ????? ????int ?mappings?=?s.readInt();? ????if ?(mappings?<? 0 )? ????????throw ? new ?InvalidObjectException( "Illegal?mappings?count:?" ?+? ???????????????????????????????????????????mappings);? ? ????? ????int ?capacity?=?( int )?Math.min(? ????????????????mappings?*?Math.min(1 ?/?loadFactor,? 4 .0f),? ????????????????? ????????????????HashMap.MAXIMUM_CAPACITY);? ? ????? ????if ?(mappings?>? 0 )?{? ????????inflateTable(capacity);? ????}?else ?{? ????????threshold?=?capacity;? ????}? ? ????init();??? ? ????? ????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);? ? ????? ? ? ? ? ????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ò),歡迎將生活随笔 推薦給好友。