Java源碼集合類LinkedHashMap學習1

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類的關鍵點,這樣我們就可以按照插入的順序遍歷”鍵—值“對元素了。這裏用到了數據結構鏈表的知識,當我們對未知感到恐懼,是因爲我們無知。


發佈了43 篇原創文章 · 獲贊 4 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章