Hello,你們好,前面給你們講了HashMap,LinkedList,知道了HashMap爲數組+單向鏈表,LinkedList爲雙向鏈表實現的。今天給你們介紹一個(HashMap+"LinkedList")的集合,LinkedHashMap,其中HashMap用於存儲數據,"LinkedList"用於存儲數據順序。OK,廢話少說,老套路,文章結構:java
大多數狀況下,只要不涉及線程安全問題,Map基本均可以使用HashMap,不過HashMap有一個問題,就是迭代HashMap的順序並非HashMap放置的順序,也就是無序。HashMap的這一缺點每每會帶來困擾,由於有些場景,咱們期待一個有序的Map.這就是咱們的LinkedHashMap,看個小Demo:數組
public static void main(String[] args) {
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("apple", "蘋果");
map.put("watermelon", "西瓜");
map.put("banana", "香蕉");
map.put("peach", "桃子");
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
複製代碼
輸出爲:
apple=蘋果
watermelon=西瓜
banana=香蕉
peach=桃子
複製代碼
能夠看到,在使用上,LinkedHashMap和HashMap的區別就是LinkedHashMap是有序的。 上面這個例子是根據插入順序排序,此外,LinkedHashMap還有一個參數決定是否在此基礎上再根據訪問順序(get,put)排序,記住,是在插入順序的基礎上再排序,後面看了源碼就知道爲何了。看下例子:緩存
public static void main(String[] args) {
Map<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true);
map.put("apple", "蘋果");
map.put("watermelon", "西瓜");
map.put("banana", "香蕉");
map.put("peach", "桃子");
map.get("banana");
map.get("apple");
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
複製代碼
輸出爲:
watermelon=西瓜
peach=桃子
banana=香蕉
apple=蘋果
複製代碼
能夠看到香蕉和蘋果在原來排序的基礎上又排後了。安全
我先說結論,而後再慢慢跟代碼。bash
好了,你們確定會以爲很神奇,如圖所示,原本HashMap的數據是0-7這樣的無須的,而LinkedHashMap卻把它變成了如圖所示的1.6.5.3.。。2這樣的有順序了。究竟是如何作到的了?其實說白了,就一句話,鉤子技術,在put和get的時候維護好了這個雙向鏈表,遍歷的時候就有序了。好了,一步一步的跟。 先看一下LinkedHashMap中的Entry(也就是每一個元素):app
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);
}
...
}
複製代碼
能夠看到繼承自HashMap的Entry,而且多了兩個指針before和after,這兩個指針說白了,就是爲了維護雙向鏈表新加的兩個指針。 列一下新Entry的全部成員變量吧:this
其中前面四個,是從HashMap.Entry中繼承過來的;後面兩個,是是LinkedHashMap獨有的。不要搞錯了next和before、After,next是用於維護HashMap指定table位置上鍊接的Entry的順序的,before、After是用於維護Entry插入的前後順序的(爲了維護雙向鏈表)。spa
1 public LinkedHashMap() {
2 super();
3 accessOrder = false;
4 }
複製代碼
1 public HashMap() {
2 this.loadFactor = DEFAULT_LOAD_FACTOR;
3 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
4 table = new Entry[DEFAULT_INITIAL_CAPACITY];
5 init();
6 }
複製代碼
1 void init() {
2 header = new Entry<K,V>(-1, null, null, null);
3 header.before = header.after = header;
4 }
複製代碼
這裏出現了第一個鉤子技術,儘管init()方法定義在HashMap中,可是因爲LinkedHashMap重寫了init方法,因此根據多態的語法,會調用LinkedHashMap的init方法,該方法初始化了一個Header,這個Header就是雙向鏈表的鏈表頭..線程
HashMap中的put方法:指針
1 public V put(K key, V value) {
2 if (key == null)
3 return putForNullKey(value);
4 int hash = hash(key.hashCode());
5 int i = indexFor(hash, table.length);
6 for (Entry<K,V> e = table[i]; e != null; e = e.next) {
7 Object k;
8 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
9 V oldValue = e.value;
10 e.value = value;
11 e.recordAccess(this);
12 return oldValue;
13 }
14 }
15
16 modCount++;
17 addEntry(hash, key, value, i);
18 return null;
19 }
複製代碼
LinkedHashMap中的addEntry(又是一個鉤子技術):
1 void addEntry(int hash, K key, V value, int bucketIndex) {
2 createEntry(hash, key, value, bucketIndex);
3
4 // Remove eldest entry if instructed, else grow capacity if appropriate
5 Entry<K,V> eldest = header.after;
6 if (removeEldestEntry(eldest)) {
7 removeEntryForKey(eldest.key);
8 } else {
9 if (size >= threshold)
10 resize(2 * table.length);
11 }
12 }
複製代碼
1 void createEntry(int hash, K key, V value, int bucketIndex) {
2 HashMap.Entry<K,V> old = table[bucketIndex];
3 Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
4 table[bucketIndex] = e;
5 e.addBefore(header);
6 size++;
7 }
複製代碼
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
複製代碼
好了,addEntry先把數據加到HashMap中的結構中(數組+單向鏈表),而後調用addBefore,這個我就不和你們畫圖了,其實就是挪動本身和Header的Before與After成員變量指針把本身加到雙向鏈表的尾巴上。 一樣的,不管put多少次,都會把當前元素加到隊列尾巴上。這下你們知道怎麼維護這個雙向隊列的了吧。
上面說了LinkedHashMap在新增數據的時候自動維護了雙向列表,這要還要提一下的是LinkedHashMap的另一個屬性,根據查詢順序排序,說白了,就是在get的時候或者put(更新時)把元素丟到雙向隊列的尾巴上。這樣不就排序了嗎?這裏涉及到LinkedHashMap的另一個構造方法:
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
複製代碼
第三個參數,accessOrder爲是否開啓查詢排序功能的開關,默認爲False。若是想開啓那麼必須調用這個構造方法。 而後看下get和put(更新操做)時是如何維護這個隊列的。
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;
}
複製代碼
此外,在put的時候,代碼11行(見上面的代碼),也是調用了e.recordAccess(this);咱們來看下這個方法:
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
複製代碼
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;
}
複製代碼
看到每次recordAccess的時候作了兩件事情:
固然,這一切都是基於accessOrder=true的狀況下。 假設如今咱們開啓了accessOrder,而後調用get("111");看下是如何操做的:
LRU即Least Recently Used,最近最少使用,也就是說,當緩存滿了,會優先淘汰那些最近最不常訪問的數據。咱們的LinkedHashMap正好知足這個特性,爲何呢?當咱們開啓accessOrder爲true時,最新訪問(get或者put(更新操做))的數據會被丟到隊列的尾巴處,那麼雙向隊列的頭就是最不常用的數據了。好比:
若是有1 2 3這3個Entry,那麼訪問了1,就把1移到尾部去,即2 3 1。每次訪問都把訪問的那個數據移到雙向隊列的尾部去,那麼每次要淘汰數據的時候,雙向隊列最頭的那個數據不就是最不常訪問的那個數據了嗎?換句話說,雙向鏈表最頭的那個數據就是要淘汰的數據。
此外,LinkedHashMap還提供了一個方法,這個方法就是爲了咱們實現LRU緩存而提供的,removeEldestEntry(Map.Entry<K,V> eldest) 方法。該方法能夠提供在每次添加新條目時移除最舊條目的實現程序,默認返回 false。
來,給你們一個簡陋的LRU緩存:
public class LRUCache extends LinkedHashMap {
public LRUCache(int maxSize) {
super(maxSize, 0.75F, true);
maxElements = maxSize;
}
protected boolean removeEldestEntry(java.util.Map.Entry eldest) {
//邏輯很簡單,當大小超出了Map的容量,就移除掉雙向隊列頭部的元素,給其餘元素騰出點地來。
return size() > maxElements;
}
private static final long serialVersionUID = 1L;
protected int maxElements;
}
複製代碼
是否是很簡單。。
其實 LinkedHashMap 幾乎和 HashMap 同樣:從技術上來講,不一樣的是它定義了一個 Entry<K,V> header,這個 header 不是放在 Table 裏,它是額外獨立出來的。LinkedHashMap 經過繼承 hashMap 中的 Entry<K,V>,並添加兩個屬性 Entry<K,V> before,after,和 header 結合起來組成一個雙向鏈表,來實現按插入順序或訪問順序排序。如何維護這個雙向鏈表了,就是在get和put的時候用了鉤子技術(多態)調用LinkedHashMap重寫的方法來維護這個雙向鏈表,而後迭代的時候直接迭代這個雙向鏈表便可,好了LinkedHashMap算是給你們分享完了,Over,Have a good day .