Map的基本實現,包括:HashMap、TreeMap、LinkedHashMap、WeekHashMap、ConcurrentHashMap、IdentityHashMap。它們都有同樣的基本接口Map,但是行為特性各不相同,這表現在效率,鍵值對的保存及呈現次序、對象的保存周期、映射表如何在多線程程序中工作和判定“鍵”等價的策略等方面。
java集合
HashMap
Map基于散列表的實現(它取代了Hashtable)。插入和查詢“鍵值對”的開銷是固定的。可以通過構造器設置容量和負載因子,以調整容器的性能。
LinkedHashMap
類似于HashMap,但是迭代遍歷它時,取得“鍵值對”的順序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一點;而在迭代訪問時反而更快,因為它使用鏈表維護內部次序。
TreeMap
基于紅黑樹的實現。查看“鍵”或“鍵值對”時,它們會被排序(次序由Comparable或Comparator決定)。TreeMap的特點在于,所得到的結果是經過排序的,TreeMap是唯一帶有subMap方法的Map,它可以返回一個子樹。
WeekHashMap
弱鍵(week key)映射,允許釋放映射所指向的對象;這是為解決某些類特殊問題而設計的。如果映射之外沒有引用指向某個“鍵”,則此“鍵”可以被垃圾收集器回收。
ConcurrentHashMap
一種線程安全的Map,它不涉及同步加鎖。
IdentityHashMap
使用==代替equals對“鍵”進行比較的散列映射,專為解決特殊問題而設計。
Map接口中主要的方法
containsKey(Object key):如果此映射包含指定鍵的映射關系,則返回 true;
containsValue(Object value):如果此映射將一個或多個鍵映射到指定值,則返回 true;
entrySet():返回此映射中包含的映射關系的 Set 視圖;
get(Object key):返回指定鍵所映射的值;如果此映射不包含該鍵的映射關系,則返回 null;
keySet():返回此映射中包含的鍵的 Set 視圖;
put(K key, V value):將指定的值與此映射中的指定鍵關聯(可選操作)。
AbstractMap提供 Map 接口的骨干實現,以最大限度地減少實現Map接口所需的工作。要實現不可修改的映射,編程人員只需擴展此類并提供 entrySet 方法的實現即可,該方法將返回映射的映射關系Set視圖。通常,返回的 set 將依次在AbstractSet上實現。此 set 不支持add或remove方法,其迭代器也不支持 remove 方法。要實現可修改的映射,編程人員必須另外重寫此類的 put 方法(否則將拋出 UnsupportedOperationException),entrySet().iterator() 返回的迭代器也必須另外實現其remove方法。
LinkedHashMap返回的結果是其插入次序
LinkedHashMap繼承自HashMap,所以它比HashMap的性能略差,但是可以維護元素間的插入順序(使用一個雙向鏈表來保存順序):
private transient Entry<K,V> header;
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
…….//省略
}
當要調用put方法插入元素時,會調用HashMap的put方法,這個方法會調用addEntry()方法,這個方法在LinkedHashMap中被重定義了:
//LinkedHashMap的addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);//調用HashMap中的addEntry方法,會創建結點,同時會維護新創建的結點到雙向鏈表中
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
//HashMap中的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 = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
//LinkedHashMap中的createEntry,覆蓋HashMap中的createEntry
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
從以上代碼中我們可以看到LinkedHashMap的put方法的過程,首先LinkedHashMap中沒有put方法,所以會調用HashMap中的put方法,這個put方法會檢查數據是否在Map中,如果不在就會調用addEntry方法,由于LinkedHashMap覆蓋了父類的addEntry方法,所以會直接調用LinkedHashMap的addEntry方法,這個方法中又調用了HashMap的addEntry方法,addEntry又調用了createEntry方法,這個方法也是LinkedHashMap覆蓋了HashMap的,它會創建結點到table中,同時會維護Entry(繼承自HashMap.Entry的LinkedHashMap.Entry)的前后元素。
//HashMap中的createEntry方法,對比以上LinkedHashMap中的createEntry方法發現,除了將Entry放入桶中之外,LinkedHashMap還維護了Entry指向之前元素和之后元素的指針
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++;
}
簡單來講,LinkedHashMap中的Entry是帶有指向在它自己插入Map之前和之后的元素引用的對象,在put元素時,首先檢查數據是否已經在Map中,如果不在就創建這個Entry,同時還要把這個Entry記錄插入到之前元素構成的鏈表中(并沒有真的簡單的創建了個鏈表結點,而是這個鏈表本身就是這些Entry元素構成的)。這些Entry本身不但是Map中table的元素,還是鏈表元素。
在進行遍歷時,它使用的是KeyIterator,而KeyIterator繼承自LinkedHashIterator,在LinkedHashIterator內部有鏈表的頭指針指向的下一個元素:
Entry<K,V> nextEntry = header.after;
由于這些Entry本身是鏈表元素,也是table中元素,故直接找到其后繼就可以得到所有元素。剩下的遍歷過程就是對一個鏈表的遍歷了,每遍歷到一個Entry就可以獲得它的key和value。
此外,LinkedHashMap還能維護一個最近最少訪問的序列,其本質還是維護Entry指針,每次使用get訪問元素時,都會將這個元素插入Map尾部,這樣鏈表頭部就是最近訪問次數最少的元素了,整個鏈表就是從近期訪問最少到近期訪問最多的順序。
其實現方式是,在get中找到要get的元素后調用元素的recordAccess方法,這個方法就把這個Entry的前后指針進行了調整。
//LinkedHashMap的get方法
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方法,參數m就是一個LinkedHashMap
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {//是否按照最近最少訪問排列
lm.modCount++;
remove();//從當前鏈中刪除自己
addBefore(lm.header);//加入到鏈表尾部
}
}
總的來說,對于所有的集合類來說,對于List,如果隨機存取多于修改首尾元素的可能,則應該選擇ArrayList,如果要實現類似隊列或者棧的功能或者首尾添加的功能較多,則應該選擇LinkedList;對于Set,HashSet是常用的Set,畢竟通常對Set操作都是插入和查詢,但是如果希望產生帶有排序的Set則可以使用TreeSet,希望記錄插入順序則要使用LinkedHashSet;而Map和Set類似,如果需要快速的查詢和添加,則可以用HashMap,如果需要Map中的元素按照一定的規則排序,則可以用TreeMap,如果需要記錄數據加入Map的順序,或者需要使用最近最少使用的規則,則可以用LinkedHashMap。