LinkedHashMap類簡介(jdk 1.7.0_67)
LinkedHashMap類繼承了HashMap類,也就是LinkedHashMap類的功能幾乎和HashMap一樣。而LinkedHashMap類就是擴展了一個雙向鏈表,使得可以按照“鍵-值”對插入的順序遍歷,這個是在HashMap類中遍歷是沒有順序的。LinkedHashMap類可以插入null的key值和value值,以及這個類也是線程不安全的。重點是要了解它是如何實現按“鍵-值”對插入的順序遍歷的。
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
LinkedHashMap類中的Entry<K,V>類繼承自HashMap.Entry<K,V>類源碼如下:
//這裏就只是多了兩個屬性
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;//這兩個屬性用於構成雙鏈表遍歷
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
//後面源代碼省略
}
先看如下一段代碼:
public static void main(String[] args){
Map<String, String> linkedMap = new LinkedHashMap<String, String>();
linkedMap.put("k1", "v1");
linkedMap.put("k2", "v2");
linkedMap.put("k3", "v3");
}
執行第2行代碼的時候,先調用父類HashMap類的HashMap()無參構造函數初始化加載因子和閥值,源碼代碼如下://調用無參構造函數
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
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();//LinkedHashMap類中重寫了這個方法,所以調用的是LinkedHashMap類中的init()方法
}
//LinkedHashMap類中的init()方法
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
執行方法init()的時候生成了一個Entry對象,就是鏈表的頭部,爲了方便理解這裏假設header變量指向的引用地址是:0X000,那麼執行完這個方法就生成了一個結構類似如下圖的對象:
接着執行代碼:linkedMap.put("k1", "v1"),LinkedHashMap類中並沒有重寫這個方法,它調用的是父類HashMap類中的put(K k, V v)方法,源碼如下:
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);//此處調用的是LinkedHashMap類中的該方法,因爲重寫了該方法(體現了多態性)
return null;
}
put方法中要做的事就是初始化table數組的容量,計算哈希值,根據哈希值算出在table數組中的索引,然後在是保存“鍵-值”對。關鍵是看看LinkedHashMap類中方法addEntry()的源碼:void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed//下面的代碼是爲了實現LRU算法的緩存用的,這裏不用去關心
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
這行代碼:super.addEntry(hash, key, value, bucketIndex)它是調用了父類HashMap中的方法,源代碼如下: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類中的該方法,因爲重寫了該方法(體現了多態性)
}
//LinkedHashMap中的方法
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry old = table[bucketIndex];
Entry e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
//LinkedHashMap中的方法
private void addBefore(Entry existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
從上面的源碼可以看出,父類中HashMap維護了“鍵-值”對的基本存儲結構,也就是說”鍵—值“對存儲在HashMap類中的結構一樣。而重點我們就來分析LinkedHashMap類中它是如何建立雙向鏈表的。通過上面main方法中的put代碼和方法addBefore(Entry existingEntry)方法來分析。
代碼:linkedMap.put("k1", "v1");假設生成Entry<>對象的地址是:0X001,根據key="k1"計算出的hash值是3214,next爲null。分析忽略了具體在table數組中的位置,只分析如何建立雙向鏈表的過程,當執行到e.addBefore(header),它的內存指向圖如下所示:1.after = existingEntry,existingEntry它在這裏始終是鏈表的頭header,after變量指向的地址是header的地址0X000;
2.before = existingEntry.before,before變量指向的地址是:0X000;
3.before.after = this,before指向的是0X000也就是指向header對象的地址,那麼before.after也就是等價於header.after指向地址:0X001;
4.依據上面3可得 after.before指向的地址是:0X001,最後的結果成了一個雙向的循環鏈表。
代碼linkedMap.put("k2", "v2")(假設生成的Entry<>對象爲e2,內存地址爲0X002,hash值爲1243,next=null)執行的結果,組成的雙向鏈表如下圖:
每次put進來一個新的”鍵—值“對就生成一個Entry<>對象插入到鏈表header的前面組成一個前後雙向的鏈表,這個就是LinkedHashMap類的關鍵點,這樣我們就可以按照插入的順序遍歷”鍵—值“對元素了。這裏用到了數據結構鏈表的知識,當我們對未知感到恐懼,是因爲我們無知。