LRU在 Java LRU 的LinkedHashMap 工作原理及實現

1. 概述

LRU 緩存介紹

LRU 是 Least Recently Used 的縮寫,翻譯過來就是「最近最少使用」,也就是說,LRU 緩存把最近最少使用的數據移除,讓給最新讀取的數據。而往往最常讀取的,也是讀取次數最多的,所以,利用 LRU 緩存,我們能夠提高系統的性能。

實現

要實現 LRU 緩存,我們首先要用到一個類 LinkedHashMap。

用這個類有兩大好處:一是它本身已經實現了按照訪問順序的存儲,也就是說,最近讀取的會放在最前面,最最不常讀取的會放在最後(當然,它也可以實現按照插入順序存儲)。第二,LinkedHashMap 本身有一個方法用於判斷是否需要移除最不常讀取的數,但是,原始方法默認不需要移除(這是,LinkedHashMap 相當於一個linkedlist),所以,我們需要 override 這樣一個方法,使得當緩存裏存放的數據個數超過規定個數後,就把最不常用的移除掉。

在介紹的HashMap後,我們來學習LinkedHashMap的工作原理及實現。首先還是類似的,我們寫一個簡單的LinkedHashMap的程序:

1
2
3
4
5
6
7
8
9
10
11
12
LinkedHashMap<String, Integer> lmap = new LinkedHashMap<String, Integer>();
lmap.put( "語文" , 1 );
lmap.put( "數學" , 2 );
lmap.put( "英語" , 3 );
lmap.put( "歷史" , 4 );
lmap.put( "政治" , 5 );
lmap.put( "地理" , 6 );
lmap.put( "生物" , 7 );
lmap.put( "化學" , 8 );
for (Entry<String, Integer> entry : lmap.entrySet()) {
     System.out.println(entry.getKey() + ": " + entry.getValue());
}

運行結果是:

1
2
3
4
5
6
7
8
語文: 1
數學: 2
英語: 3
歷史: 4
政治: 5
地理: 6
生物: 7
化學: 8

我們可以觀察到,和HashMap的運行結果不同,LinkedHashMap的迭代輸出的結果保持了插入順序。是什麼樣的結構使得LinkedHashMap具有如此特性呢?我們還是一樣的看看LinkedHashMap的內部結構,對它有一個感性的認識。

沒錯,正如官方文檔所說:

Hash table and linked list implementation of the Map interface, with predictable iteration order. This implementation differs from HashMap in that it maintains a doubly-linked listrunning through all of its entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order).

LinkedHashMap是Hash表和鏈表的實現,並且依靠着雙向鏈表保證了迭代順序是插入的順序。

2. 三個重點實現的函數

在HashMap中提到了下面的定義:

1
2
3
4
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion( boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

LinkedHashMap繼承於HashMap,因此也重新實現了這3個函數,顧名思義這三個函數的作用分別是:節點訪問後、節點插入後、節點移除後做一些事情。

afterNodeAccess函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void afterNodeAccess(Node<K,V> e) { // move node to last
     LinkedHashMap.Entry<K,V> last;
     // 如果定義了accessOrder,那麼就保證最近訪問節點放到最後
     if (accessOrder && (last = tail) != e) {
         LinkedHashMap.Entry<K,V> p =
             (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
         p.after = null ;
         if (b == null )
             head = a;
         else
             b.after = a;
         if (a != null )
             a.before = b;
         else
             last = b;
         if (last == null )
             head = p;
         else {
             p.before = last;
             last.after = p;
         }
         tail = p;
         ++modCount;
     }
}

就是說在進行put之後就算是對節點的訪問了,那麼這個時候就會更新鏈表,把最近訪問的放到最後,保證鏈表。

afterNodeInsertion函數

1
2
3
4
5
6
7
8
void afterNodeInsertion( boolean evict) { // possibly remove eldest
     LinkedHashMap.Entry<K,V> first;
     // 如果定義了移除�ight:auto!important; top:auto!important; vertical-align:baseline!important; width:auto!important; min-height:auto!important; color:black!important; background:none!important"> afterNodeInsertion( boolean evict) { // possibly remove eldest
     LinkedHashMap.Entry<K,V> first;
     // 如果定義了移除規則,則執行相應的溢出
     if (evict && (first = head) != null && removeEldestEntry(first)) {
相關文章
相關標籤/搜索