java基礎解析系列(四)---LinkedHashMap的原理及LRU算法的實現
- java基礎解析系列(一)---String、StringBuffer、StringBuilder
- java基礎解析系列(二)---Integer
- java基礎解析系列(三)---HashMap
- 這是我的博客目錄,歡迎閱讀
實驗
遍歷HashMap
public static void main(String[] args)
{
Map<String, String> map=new HashMap<String,String>();
map.put("white", "小白");
map.put("black", "小黑");
map.put("red", "小紅");
map.put("yellow", "小黃");
map.get("yellow");
map.get("red");
map.get("black");
map.get("white");
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
- 結果發現,hashmap遍歷出來的是無序的
換成LinkedHashMap
Map<String, String> map=new LinkedHashMap<String,String>();
- 結果發現,遍歷出來的順序是按找插入的順序
改變LinkedHashmap的參數
Map<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true);
- 結果發現,遍歷出來的順序是按照訪問的順序的來的
分析
與HashMap的關系
- LinkedHashMap可以看做是LinkedList+hashmap的組合
- LinkedHashMap是hashmap的子類
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
- LinkedHashMap中的Entry有before和after兩個域,這是用來實現雙向鏈表的
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);
}
構造方法
LinkedHashMap
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder)構造一個帶指定初始容量、加載因子和排序模式的空 LinkedHashMap 實例。
參數:
initialCapacity - 初始容量
loadFactor - 加載因子
accessOrder - 排序模式 - 對於訪問順序,為 true;對於插入順序,則為 false
拋出:
IllegalArgumentException - 如果初始容量為負或者加載因子為非正
- 從api可以看出,有一個參數accessOrder,默認情況下該參數為false,為false的時候,順序為插入順序,如果為true的話,順序為訪問順序
public LinkedHashMap() {
super();
accessOrder = false;
}
void init() {
header = new Entry<K,V>(-1, null, null, null);
header.before = header.after = header;
}
- 從這里看出,還未put的時候就創建了一個header
put方法
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
//如果table數組該下標的內容不為空,遍歷鏈表
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;
}
}
//如果沒有該key
modCount++;
addEntry(hash, key, value, i);
return null;
}
- put 方法繼承自hashmap的,不同的是addEntry被重寫了,如果put的鍵值對中是新的,那么執行addEntry方法
addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
- addEntry方法先調用createEntry方法
createEntry方法
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
- 這里將Entry放到table之后,還執行了一個addBefore方法
addBefore方法
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
- 這個方法用於維護雙向鏈表,可以自己畫一個圖看一下
回到addEntry方法,執行完createEntry方法后,下面的部分代碼
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
- 第一句注釋的意思是如果指示那么刪除最年長的,否則擴容
- 如果removeEldestEntry返回true,那么刪除老的鍵值對,否則的話如果超過閾值,進行擴容
removeEldestEnrey方法
protected boolean More removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
- removeEldestEntry方法,也就是是否刪除最年長的Entry。這里始終返回的是false,我們可以對他進行重寫
recordAccess方法
- 當put的是新的鍵值對,鍵是之前沒有的話,那么會創建一個新的Entry,而如果是之前有的話,那么會覆蓋value並執行revordAccess,中文意思為記錄訪問
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;
}
- 這個方法會調用remove方法,而這個方法實際上是改變鏈表的指針,執行后,相當於之前沒有這個這個Entry(可以自己動手畫一下指針指向),然后再將這個Entry重新加入,這個時候以前添加的Entry,由於最近被使用,就被移到到鏈表的后面了
- 同樣使用get的時候也會調用此方法,也就是會由於被訪問被移動到前面
總結
- 初始化一個LinkedHash,會創建一個header
- put的時候,將Entry放入數組中后,會和header連在一起
- 再put的一個的時候,同樣加入數組后,會繼續和前面一個相連,此時header,小黑,小紅三個連在一起,
- 此時get小黑,小黑會從鏈表中移除,此時相當於只添加了小紅,然后再將小黑添加進去,這樣的話就保證了,最新訪問的在后面
- 如果put的時候該鍵是之前已有的,那么會覆蓋value,然后重新調整該Entry在鏈表中的位置
LRU實現
- 所謂LRU也就是Least Recently Used,最近最少使用,當緩存滿了,刪除最少使用的
- 那么LinkedHashMap適合於解決這個問題,因為他使用了雙向鏈表,將最新訪問的移到了后面,那么這樣的話,前面的就是最少使用,這時候就可以將最少使用的刪除
- removeEldestEntry,前面說過這方法,這個方法就是刪除最少使用的,默認下不會刪除,因為該方法返回false,所以當空間滿了的話會擴容,我們可以重寫這個方法,那么這樣就不需要擴容,滿了的時候刪除最少使用的就可以
- 重寫removeEldestEntry,當大小大於容量的時候,會刪除最年長的鍵值對
public class LRUCache {
private int capacity;
private Map<Integer, Integer> cache;
public LRUCache(int capacity) {
this.capacity = capacity;
this.cache = new LinkedHashMap<Integer, Integer> (capacity, 0.75f, true) {
// 定義put后的移除規則,大於容量就刪除eldest
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
};
}
public int get(int key) {
if (cache.containsKey(key)) {
return cache.get(key);
} else
return -1;
}
public void set(int key, int value) {
cache.put(key, value);
}
public static void main(String[] args) {
LRUCache cache=new LRUCache(2);
cache.set(1,1);
cache.set(2, 2);
//此時超出容量,put3的時候刪除cache
cache.set(3, 3);
Set<Map.Entry<Integer,Integer>> set = cache.cache.entrySet();
Iterator<Map.Entry<Integer,Integer>> iterator = set.iterator();
while (iterator.hasNext())
{
System.out.print(iterator.next() + "\t");
}
System.out.println();
//輸出2=2 3=3
}
}
我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)
作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。