LinkedHashMap源码分析及实现LRU
概述
从名字上看LinkedHashMap相比于HashMap,显然多了链表的实现。从功能上看,LinkedHashMap有序,HashMap无序。这里的顺序指的是添加顺序或者访问顺序。
基本使用
@Test
public void test1(){
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
map.put(1,1);
map.put(3,3);
map.put(4,4);
map.put(2,2);
for (Map.Entry entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
map.get(1);
map.get(2);
System.out.println();
for (Map.Entry entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
我们通过构造函数的第三个参数accessOrder设为true(默认为false),该参数为true,则根据访问顺序排序。如下,当我们调用get()方法的时候,再次遍历发现数据被放到了最后面。
1:1
3:3
4:4
2:2
3:3
4:4
1:1
2:2
源码分析
首先LinkedHashMap继承了HashMap,因此其基本使用与HashMap几乎完全一致。
可以看到内部实现了双向链表 head指向最久访问,tail指向最新访问。
/**
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> tail;
/**
* HashMap.Node subclass for normal LinkedHashMap entries.
*/
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // Entry类维护了双线链表所需的前后节点指向
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
既然有了双向链表的基本定义,那么他是怎么实现双向链表的呢?找了这个类,发现并没有重写put方法,说明直接使用父类的put。那么直接看HashMap的putVal()方法。HashMap的源码就不再详细讲了,感兴趣的朋友可以看看我的另一篇文章。
观察HashMap的putVal的时候我们发现创建节点会频繁的使用到newNode()方法。
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); //添加节点的时候使用newNode
else {
......省略
我们观察LinkedHashMap,果然,在这里重写了,将每个节点添加到双向链表中。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
前面已经看到了put操作,LinkedHashMap在put方法执行的时候维护了一个双向链表。接下来我们看一下get操作。
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null) // 调用父类的getNode
return null;
if (accessOrder) // accessOrder为true则调整双向链表顺序
afterNodeAccess(e);
return e.value;
}
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) { // accessOrder为true且 尾节点不为当前元素
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;
}
}
当我们将accessOrder设为true的时候,LinkedHashMap会将当前元素调整到双向链表末尾。
LRU缓存的实现
LRU(Least Recently Used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
根据定义,为了实现LRU,LinkedHashMap我们需要将accessOrder设为true。以保证最新使用的可以调整到链表的末尾。那么另一个比较重要的就是当缓存满的时候,需要淘汰最久没有使用的数据,及链表的头。
因此,该操作应该是存在put方法执行的过程中,由于LinkedHashMap并没有实现put方法,那么继续看HashMap里面的putVal(); 在方法快结束的时候发现了一个有趣的类,从方法名就感觉可以从这里入手。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true); //最后一个参数即下面的evict为true
}
afterNodeInsertion(evict); // HashMap并没有对这个类进行实现。
查看LinkedHashMap的afterNodeInsertion
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) { // 我们只需要在这里重写移除最久没有使用的节点的判断条件,就可以移除节点了
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
我们假设LRU里最大容量为16,只需要将构造方法里的accessOrder设为true,并且重写removeEldestEntry的判断条件就可以了。
public class LruLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private Integer capacity;
public LruLinkedHashMap(int capacity) {
super(capacity, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
public static void main(String[] args) {
LruLinkedHashMap<Integer, Integer> lru = new LruLinkedHashMap(16);
for (int i = 0; i < 16; i++) {
lru.put(i, i);
}
System.out.println("当前lru缓存的元素为");
for (Map.Entry entry : lru.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
lru.get(0);
lru.get(1);
System.out.println("当前lru缓存的元素为");
for (Map.Entry entry : lru.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
lru.put(16, 16);
lru.put(17, 17);
System.out.println("当前lru缓存的元素为");
for (Map.Entry entry : lru.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
}
测试结果如下,最左边的为最近最久未使用。
当前lru缓存的元素为
0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9 10:10 11:11 12:12 13:13 14:14 15:15
当前lru缓存的元素为
2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9 10:10 11:11 12:12 13:13 14:14 15:15 0:0 1:1
当前lru缓存的元素为
4:4 5:5 6:6 7:7 8:8 9:9 10:10 11:11 12:12 13:13 14:14 15:15 0:0 1:1 16:16 17:17
LinkedHashMap源码分析及实现LRU的更多相关文章
- Java集合系列[4]----LinkedHashMap源码分析
这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...
- LinkedHashMap源码分析
hashMap源码分析:hashMap源码分析 版本说明:jdk1.7LinkedHashMap继承于HashMap,是一个有序的Map接口的实现.有序指的是元素可以按照一定的顺序排列,比如元素的插入 ...
- Java 容器 LinkedHashMap源码分析1
同 HashMap 一样,LinkedHashMap 也是对 Map 接口的一种基于链表和哈希表的实现.实际上, LinkedHashMap 是 HashMap 的子类,其扩展了 HashMap 增加 ...
- 死磕 java集合之LinkedHashMap源码分析
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 LinkedHashMap内部维护了一个双向链表,能保证元素按插入的顺序访问,也能以访问 ...
- java Linkedhashmap源码分析
LinkedHashMap类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是插入次序,或者是最近最少使用(LRU)的次序.只比HashMap慢一点:而在迭代访问时反而更快,因为它使用链表维 ...
- Java8集合框架——LinkedHashMap源码分析
本文的结构如下: 一.LinkedHashMap 的 Javadoc 文档注释和简要说明 二.LinkedHashMap 的内部实现:一些扩展属性和构造函数 三.LinkedHashMap 的 put ...
- Java集合之LinkedHashMap源码分析
概述 HashMap是无序的, 即put的顺序与遍历顺序不保证一样. LinkedHashMap是HashMap的一个子类, 它通过重写父类的相关方法, 实现自己的功能. 它保留插入的顺序. 如果需要 ...
- LinkedHashMap 源码分析
LinkedHashMap LinkedHashMap 能解决什么问题?什么时候使用 LinkedHashMap? 1)LinkedHashMap 按照键值对的插入顺序进行遍历,LinkedHashM ...
- HashMap LinkedHashMap源码分析笔记
MapClassDiagram
随机推荐
- Windows Management Instrumentation 服务卸载并重新创建
SC delete Winmgmt sc create Winmgmt binPath= "C:\Windows\System32\svchost.exe -k netsvcs" ...
- PM意识升级2.0
上一篇<质量:“PM,你怎么可以放弃我?!”>,我们匡正了PM的一个意识:质量是PM的底线和目标,也算是PM意识升级的1.0.同时提出,只有“正确的做正确的事”才是保证质量,且成本相对最低 ...
- 关于Java 中跳出多重循环
前言 环境:window10 JDK 1.8 应用场景:在多个for循环或while循环中,直接跳到最外层的循环外面,而不是需要层层退出来. 使用: 使用一个标签label(也可以是其他单词,不能是关 ...
- 根据Webservice地址,动态传入参数(Webservice代理类)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Sunlib; ...
- SqlServer 操作 JSON
SqlServer 操作 JSON Intro Sql Server 从 2016 开始支持了一些 json 操作,最近的项目里也是好多地方直接用字段直接存成了 json ,需要了解一下怎么在 Sql ...
- LeetCode算法题-Find Smallest Letter Greater Than Target(Java实现)
这是悦乐书的第306次更新,第326篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第175题(顺位题号是744).给定一个仅包含小写字母的有序字符数组,并给定目标字母目标 ...
- python3 购物车 增改查终极版~
还是先来条NLP再说,快没了,以后想抄还没有... 十一,没有挫败,只有回应讯息 “挫败”只是指出过去的做法得不到预期的效果,是给我们需要改变的信号. “挫败”只是在事情画上句号时才能用上,欲想事情解 ...
- Java jar包启动脚本
#!/bin/bash APP_HOME=/wdcloud/app/rps/rps-module-admin APP_JAR=rps-module-admin-*.jar APP_PIDS=$(ps ...
- 【js】js中判断对数是否为空
1.将json对象转化为json字符串,再判断该字符串是否为"{}" var data = {}; var b = (JSON.stringify(data) == "{ ...
- JQuery:怎么动态切换一个元素的显示、隐藏呢?原来隐藏就显示,原来显示就隐藏
使用toggle() 方法:<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"& ...