java集合之LinkedHashSet 和 LinkedHashMap(6)
原文:LinkedHashSet 和 LinkedHashMap
介紹
LinkedHashSet和LinkedHashMap在Java裡也有著相同的實現,前者僅僅是對後者做了一層包裝,也就是說LinkedHashSet裡面有一個LinkedHashMap(介面卡模式)。因此本文將重點分析LinkedHashMap。
LinkedHashMap實現了Map介面,即允許放入key
為null
的元素,也允許插入value
為null
的元素。從名字上可以看出該容器是linked list和HashMap的混合體,也就是說它同時滿足HashMap和linked list的某些特性。可將LinkedHashMap看作採用linked list增強的HashMap。
事實上LinkedHashMap是HashMap的直接子類,二者唯一的區別是LinkedHashMap在HashMap的基礎上,採用雙向連結串列(doubly-linked list)的形式將所有entry
連線起來,這樣是為保證元素的迭代順序跟插入順序相同。上圖給出了LinkedHashMap的結構圖,主體部分跟HashMap完全一樣,多了header
指向雙向連結串列的頭部(是一個啞元),該雙向連結串列的迭代順序就是entry
的插入順序。
除了可以保迭代歷順序,這種結構還有一個好處:迭代LinkedHashMap時不需要像HashMap那樣遍歷整個table
,而只需要直接遍歷header
entry
的個數相關,而跟table
的大小無關。
有兩個引數可以影響LinkedHashMap的效能:初始容量(inital capacity)和負載係數(load factor)。初始容量指定了初始table
的大小,負載係數用來指定自動擴容的臨界值。當entry
的數量超過capacity*load_factor
時,容器將自動擴容並重新雜湊。對於插入元素較多的場景,將初始容量設大可以減少重新雜湊的次數。
將物件放入到LinkedHashMap或LinkedHashSet中時,有兩個方法需要特別關心:hashCode()
equals()
。hashCode()
方法決定了物件會被放到哪個bucket
裡,當多個物件的雜湊值衝突時,equals()
方法決定了這些物件是否是“同一個物件”。所以,如果要將自定義的物件放入到LinkedHashMap
或LinkedHashSet
中,需要@OverridehashCode()
和equals()
方法。
注:LinkedHashMap是非同步的(not synchronized)
方法剖析
put()
put(K key, V value)
方法是將指定的key, value
對新增到map
裡。該方法首先會對map
做一次查詢,看是否包含該元組,如果已經包含則直接返回,查詢過程類似於get()
方法;如果沒有找到,則會通過addEntry(int hash, K key, V value, int bucketIndex)
方法插入新的entry
注意,這裡的插入有兩重含義:
|
// LinkedHashMap.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 } // 1.在衝突連結串列頭部插入新的entry HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<>(hash, key, value, old); table[bucketIndex] = e; // 2.在雙向連結串列的尾部插入新的entry e.addBefore(header); size++; }
上述程式碼中用到了addBefore()
方法將新entry e
插入到雙向連結串列頭引用header
的前面,這樣e
就成為雙向連結串列中的最後一個元素。addBefore()
的程式碼如下:
// LinkedHashMap.Entry.addBefor(),將this插入到existingEntry的前面 private void addBefore(Entry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; }