標籤:java map 源碼
Map是鍵值對,也是常用的資料結構。Map介面定義了map的基本行為,包括最核心的get和put操作,此介面的定義的方法見:
JDK中有不同的的map實現,分別適用於不同的應用情境,如安全執行緒的hashTable和非安全執行緒的hashMap.
如是JDK中map介面的子類UML類圖,其中有個特例Dictionary已經不建議使用:
Map介面中的方法我們需要關注的就是get、put 和迭代器相關的方法如entrySet()、keySet()、values()方法。
Entry
在開始分析map之前,首先瞭解map中元素的儲存,我們知道map可以認為是鍵值對的集合,java中map使用Entry儲存鍵值對,這是一個介面,其定義如下,簡單明了,介面方法主要是對鍵和值進行操作。
interface Entry<K,V> { K getKey(); V getValue(); V setValue(V value); boolean equals(Object o); int hashCode(); }
AbstractMap
Map介面的抽象實現,見以下樣本實現代碼:
Map<String,String> a = /** * *抽象map實現示意,根據文檔說,和list介面及其類似。 * *map分為可變和不可變兩種,不可變只需實現 entrySet方法即可,且返回的 set的迭代器不能支援修改操作。 * *可變map,需要實現put方法,然後 entrySet的迭代器也需要支援修改操作 * * *AbstractMap 裡面實現了map的梗概,但是其效率難說,比如其get方法中時採用方法entrySet實現的。 * *通常子類用更有效率的方法覆蓋之。如hashMap中覆蓋了keySet 、values 、get方法等 */ new AbstractMap<String,String>(){ /* * 返回map中的元素集合,返回的集合通常繼承AbstractSet 即可。 */ @Override public Set<Map.Entry<String, String>> entrySet() { return new AbstractSet<Map.Entry<String,String>>() { @Override public Iterator<java.util.Map.Entry<String, String>> iterator() { return null; } @Override public int size() { return 0; } }; } /* * 預設實現拋出異常,可變map需要實現此方法 */ @Override public String put(String key, String value) { return null; } };
HashMap
hashMap繼承abstractMap,是相當常用的資料結構,採用hash散列的思想,可以在O(1)的時間複雜度插入入和擷取資料。其基本實現可以分析上個小節中的抽象方法,文章
淺析HashMap的實現和效能分析 已經對hashMap的實現、put和get操作進行了較詳細的說明。這裡不再贅述,關鍵看他的迭代器實現,這裡只分析下entrySet()方法,而keySet()和values()方法實現與之一脈相承。
關於迭代器,見下面摘出的部分源碼和相關注釋:
/** * 返回map中所有的索引值對集合,用於遍曆 */ public Set<Map.Entry<K,V>> entrySet() { return entrySet0(); } /** * 延遲初始化,只有使用的時候才構建。 * * values()和 keySet()方法也使用了類似的機制 */ private Set<Map.Entry<K,V>> entrySet0() { Set<Map.Entry<K,V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); } /** * 真正的 enterySet,是一個內部類,其關鍵實現是迭代器實現。 * * values()和 keySet()方法也對應了相應的內部類。 * 對應的自己的迭代器實現。關鍵在於這個迭代器 * */ private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<K,V> e = (Map.Entry<K,V>) o; Entry<K,V> candidate = getEntry(e.getKey()); return candidate != null && candidate.equals(e); } public boolean remove(Object o) { return removeMapping(o) != null; } public int size() { return size; } public void clear() { HashMap.this.clear(); } } /** * entrySet迭代器,繼承HashIterator,實現next方法。 * values()和 keySet()方法,也是繼承HashIterator,只是實現next 的方法不同, * * 可以對比下。 * * 關鍵在於HashIterator * * */ private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { return nextEntry(); } } /** * *keySet()對應的迭代器 */ private final class KeyIterator extends HashIterator<K> { public K next() { return nextEntry().getKey(); } } /** * * hashmap entrySet() keySet() values()的通用迭代器 */ private abstract class HashIterator<E> implements Iterator<E> { Entry<K,V> next; // next entry to return int expectedModCount; // For fast-fail int index; // current slot Entry<K,V> current; // current entry HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry Entry[] t = table; //構造時候,在數組中尋找第一個不為null的數組元素,即Entry鏈表,關於hashmap的實現請看 //本人前面的博文 while (index < t.length && (next = t[index++]) == null) ; } } public final boolean hasNext() { return next != null; } /** * 關鍵實現,很容易看懂,尋找next的時候,和構造迭代器的時候一樣 */ final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; if (e == null) throw new NoSuchElementException(); if ((next = e.next) == null) { Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; } public void remove() { if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } }
HashTable
實現和hashMap基本一致,只是在方法上加上了同步操作。多線程環境可以使用它。不過現在有ConcurrentHashMap了,在高並發的時候,可以用它替換hashtable.
LinkedHashMap
hashMap可能在某些情境下不符合要求,因為放入到其中的元素是無序的。而LinkedHashMap則在一定程度上解決這個問題。
其在實現上繼承了HashMap,在儲存上擴充haspMap.enteySet,加入了before、after欄位,把hashMap的元素用雙向鏈表串連了起來。這個雙向鏈表決定了它的遍曆順序。其順序通常是插入map中的順序,但是它有一個欄位accessOrder當為true時,遍曆順序將是LRU的效果。
研究它的有序性,我們可以從put方法、get方法和遍曆的方法入手,首先看get方法:
/** * 直接調用父類的getEntry方法。關鍵在於 * e.recordAccess(this) 這句代碼 */ public V get(Object key) { Entry<K,V> e = (Entry<K,V>)getEntry(key); if (e == null) return null; e.recordAccess(this); return e.value; } /** * Entry.recordAccess 方法 * * 如果是訪問順序(accessOrder=true),那麼就把它放到頭結點的下一個位置 * 否則什麼也不做, * 這樣就可以根據初始 accessOrder 屬性,來決定遍曆的順序。 */ void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } }
Put方法:
/** * put方法調用此方法,覆蓋了父類中的實現, */ void addEntry(int hash,K key, V value, int bucketIndex) { createEntry(hash, key, value, bucketIndex); // Remove eldest entry if instructed, else grow capacity if appropriate Entry<K,V> eldest = header.after; //回調。如果有必要移除在老的元素,最新的元素在鏈表尾部。 if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } else { if (size >= threshold) resize(2 * table.length); } } /** * */ void createEntry(int hash,K key, V value, int bucketIndex) { HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<K,V>(hash, key, value, old); table[bucketIndex] = e; //本質是插入雙向鏈表的末尾 e.addBefore(header); size++; } /** * 插入到 existingEntry的前面,因為是雙向鏈表。當existingEntry是 header時, * 相當於插入到鏈表最後。 * */ private void addBefore(Entry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; }
遍曆迭代直接使用雙向鏈表進行迭代介面,這裡不贅述,可以看源碼很容易理解。注意的是實現上市覆蓋了父類中相關的產生迭代器的方法。
TreeMap和CurrentHashMap都可以單獨開一篇文章來分析了。這裡簡單說下。TreeMap是基於b樹map,根據key排序。CurrentHashMap是並發包中的一個強大的類,適合多線程高並發時資料讀寫。
java之Map源碼淺析