一、LinkedHashMap与HashMap的结构区别
HashMap
LinkedHashMap
结构区别:LinkedHashMap的元素Entry中多两个用于维护双向链表的指针before、after,并且在LinkedHashMap中有两个head、tail指针用于记录双向链表的头结点和尾结点。
二、LinkedHashMap顺序迭代原理
LinkedHashMap可保证通过keySet()、entrySet()、forEach()等方法获取的元素具有顺序性,顺序分为两种:
- 添加顺序:获取元素的顺序与其被添加的顺序一致。
- 访问顺序:按元素被访问的时间进行排序,越近被访问的排在越后面,put和get等方法都表示访问。
在LinkedHashMap通过成员变量accessOrder来决定按哪种顺序排序,默认为false添加顺序,只有通过下面这个构造方法在创建LinkedHashMap时将accessOrder设置为true,才表示访问顺序。
1. 添加顺序实现原理
添加顺序实现很简单,就是在按照HashMap规则添加元素后,还会执行一个方法将其添加到双向链表的尾部,然后更新尾结点tail。因此迭代时从双向链表head迭代到tail即为访问顺序。
2. 访问顺序实现原理
put添加元素后依然会将其添加到双向链表的尾部,表示是最近被访问到的。但是如果accessOrder为true,那么在下面这些访问方法中都会调用afterNodeAccess()将被访问元素移动到双向链表的尾部。因此在双向链表中就实现了元素的访问顺序。
三、LinkedHashMap实现LRU算法
在LinkedHashMap中还有一个钩子方法removeEldestEntry(),在插入元素后调用的afterNodeInsertion()方法中会调用这个方法,如果它返回true那么就会移除Map中最老的元素即head,在添加顺序中head就是最早添加的元素,在访问顺序中head就是最长时间没被访问的元素。
因此我们可以重写钩子方法removeEldestEntry(),来利用LinkedHashMap实现LRU算法(LeetCode链接):
public class LRUCache {
private final LinkedHashMap<Integer, Integer> map;
private final int capacity;
public LRUCache(int capacity) {
this.capacity = capacity;
//1、选用可设置accessOrder的构造函数
//2、重写removeEldestEntry()方法,在插入元素后若size大于容量,则返回true,移除最长时间没被访问的元素
map = new LinkedHashMap<Integer, Integer>(capacity+1, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > capacity;
}
};
}
public int get(int key) {
try {
return map.get(key);
} catch (Exception e) {
return -1;
}
}
public void put(int key, int value) {
map.put(key, value);
}
}