###LinkedHashMap 源碼分析java
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
上面是 LinkedHashMap 的繼承結構。而後看下構造方法:算法
private transient Entry<K,V> header;//內部鏈表的頭 private final boolean accessOrder;//是否按照訪問的順序排序,默認是 false public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } public LinkedHashMap() { super(); accessOrder = false; } public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
構造方法比較簡單,主要是多個兩個成員變量,而且構造方法當中多了 accessOrder。數據結構
下面看下 put 方法,LinkedHashMap 並無從新實現 put 方法,可是重寫了 HashMap 當中的幾個 put 調用的方法:ide
下面是 HashMap 的 put 方法:源碼分析
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); return null; }
LinkedHashMap 當中重寫了 addEntry 方法和 createEntry 方法。下面看下:this
void addEntry(int hash, K key, V value, int bucketIndex) { super.addEntry(hash, key, value, bucketIndex); // 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); }
下面的 createEntry 方法:code
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 一組 key - value 的時候,咱們會在 HashMap 的結構當中添加一個元素,而且會在本身的特有的鏈表當中添加一個元素。對象
看下 LinkedHashMap 內部的 Entry 實現代碼:排序
private static class Entry<K,V> extends HashMap.Entry<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { super(hash, key, value, next); } private void remove() { before.after = after; after.before = before; } private void addBefore(Entry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; } void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } } void recordRemoval(HashMap<K,V> m) { remove(); } }
這個結構就是一個雙向循環列表,因此 LinkedHashMap 的內部數據結構就是以下圖: ###實現 LRU 算法 LRU 算法就是近期最少使用算法。當咱們要用 LinkedHashMap 來實現的時候,其實咱們就是用他內部的雙向鏈表,每次 put 的時候咱們把這個元素加入到鏈表尾部,而後 get 的時候也會把元素從新添加到尾部,這樣就簡單的描述了一個 LRU 算法。繼承
那麼實現代碼以下:
package com.rcx.test.cache; import java.util.LinkedHashMap; import java.util.Map; public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> { private static final long serialVersionUID = 6802031150943192407L; private int capacity; LRULinkedHashMap(int capacity) { super(16, 0.75f, true); this.capacity = capacity; } @Override public boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > capacity; } public static void main(String[] args) throws Exception { LRULinkedHashMap<String, String> map = new LRULinkedHashMap<String, String>(4); map.put("rcx1", "a1"); map.put("rcx2", "a2"); map.put("rcx3", "a3"); map.put("rcx4", "a4"); map.put("rcx5", "a5"); System.out.println(map.get("rcx1")); } }
看代碼實現的很簡單,只須要重寫一個 removeEldestEntry 就行,這個方法只須要判斷當前的 size 是否是大於最大容量,那麼咱們從新看下這是爲何:
首先咱們建立的 LinkedHashMap 第 3 個參數傳的是 true 。這樣 accessOrder 就是 addEntry 了,那麼就從新看下 put 方法。
void addEntry(int hash, K key, V value, int bucketIndex) { super.addEntry(hash, key, value, bucketIndex); Entry<K,V> eldest = header.after;//header 後面的元素是最不經常使用的元素 // removeEldestEntry 這個方法咱們重寫了,大於最大容量就進入 if 語句 if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } } // HashMap 當中的 removeEntryForKey final Entry<K,V> removeEntryForKey(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this);// 會調用 Entry 的這個方法,而且 LinkedHashMap.Entry 重寫了這個方法 return e; } prev = e; e = next; } return e; }
咱們看下 LinkedHashMap.Entry 的 recordRemoval 實現:
void recordRemoval(HashMap<K,V> m) { remove(); } private void remove() { before.after = after; after.before = before; }
代碼實現很簡單,想當於把鏈表的當前元素來刪掉。
既然上面在 put 的時候會根據最大容量來判斷是否須要移除最不經常使用的元素了,下面咱們就分析最經常使用的元素如何處理。內部原理就是當每次 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; }
代碼超級簡單, LinkedHashMap.Entry 重寫了 recordAccess 方法,以下:
void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } }
能夠看到先判斷 accessOrder 這個成員變量,咱們建立 LinkedHashMap 對象時候傳入的是 true。裏面的內部結構也很簡單,先把本身移除,而後在把本身添加進去。