总体介绍

1
2
3
4
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V> {
}

LinkedHashMap实现了Map接口,即允许放入key为null的元素,也允许插入value为null的元素。从名字上可以看出该容器是linked list和HashMap的混合体,也就是说它同时满足HashMap和linked list的某些特性。可将LinkedHashMap看作采用linked list增强的HashMap

事实上LinkedHashMap是HashMap的直接子类,二者唯一的区别是LinkedHashMap在HashMap的基础上,采用双向链表(doubly-linked list)的形式将所有entry连接起来,这样是为保证元素的迭代顺序跟插入顺序相同

方法剖析

get()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
// afterNodeAccess 按读取顺序排序
afterNodeAccess(e);
return e.value;
}


public V getOrDefault(Object key, V defaultValue) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return defaultValue;
if (accessOrder)
// afterNodeAccess 按读取顺序排序
afterNodeAccess(e);
return e.value;
}

Callbacks

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) { }

afterNodeAccess(Node<K,V> e)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
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;
}
}

LinkedHashMap经典用法

LinkedHashMap除了可以保证迭代顺序外,还有一个非常有用的用法: 可以轻松实现一个采用了FIFO替换策略的缓存。
具体说来,LinkedHashMap有一个子类方法protected boolean removeEldestEntry(Map.Entry<K,V> eldest),该方法的作用是告诉Map是否要删除“最老”的Entry,所谓最老就是当前Map中最早插入的Entry,如果该方法返回true,最老的那个元素就会被删除。
在每次插入新元素的之后LinkedHashMap会自动询问**removeEldestEntry()是否要删除最老的元素。这样只需要在子类中重载该方法,当元素个数超过一定数量时让removeEldestEntry()**返回true,就能够实现一个固定大小的FIFO策略的缓存。

示例代码如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
* 实现 LRUCache 类:
* 1. LRUCache(int capacity) 以 正整数 作为容量capacity 初始化 LRU 缓存
* 2. int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
* 3. void put(int key, int value)如果关键字key 已经存在,则变更其数据值value ;如果不存在,则向缓存中插入该组key-value 。如果插入操作导致关键字数量超过capacity ,则应该 逐出 最久未使用的关键字。
* 函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
* <p>
* <p>
* 输入
* ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
* [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
* 输出
* [null, null, null, 1, null, -1, null, -1, 3, 4]
* <p>
* 解释
* LRUCache lRUCache = new LRUCache(2);
* lRUCache.put(1, 1); // 缓存是 {1=1}
* lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
* lRUCache.get(1); // 返回 1
* lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
* lRUCache.get(2); // 返回 -1 (未找到)
* lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
* lRUCache.get(1); // 返回 -1 (未找到)
* lRUCache.get(3); // 返回 3
* lRUCache.get(4); // 返回 4
*/
public class LRUCache_LinkedHashMap {

private int capacity;
private LinkedHashMap<Integer, Integer> cache;

public LRUCache_LinkedHashMap(int capacity) {
this.capacity = capacity;

/**
* LinkedHashMap
* 这里的 accessOrder 默认是为false,如果要按读取顺序排序需要将其设为 true
* initialCapacity 代表 map 的 容量,loadFactor 代表加载因子 (默认即可)
* public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
* super(initialCapacity, loadFactor);
* this.accessOrder = accessOrder;
* }
*/
cache = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {
// 当Entry个数超过cache的size时,删除最老的Entry
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return cache.size() > capacity;
}
};
}

public int get(int key) {
return cache.getOrDefault(key, -1);
}

public void put(int key, int value) {
cache.put(key, value);
}

public static void main(String[] args) {
LRUCache_LinkedHashMap lruCache = new LRUCache_LinkedHashMap(2);
lruCache.put(1, 1);
lruCache.put(2, 2);
System.out.println(lruCache.get(1));
lruCache.put(3, 3);
System.out.println(lruCache.get(2));
}
}