Java基础之LinkedHashMap原理分析
我们平时用LinkedHashMap的时候,都会写下面这段
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("student", "333");
map.put("goods", "222");
map.put("product", "222");
然后我们通常都会去看 put 方法,但是我们点到LinkedHashMap内部后,发现没有put方法,这是为什么呢?
其实这个不难,因为LinkedHashMap继承子HashMap
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
}
这就好理解了。因为put是集成自HashMap,那么LinkedHashMap的数据也是 数组+链表 的形式存储的吗?我们慢慢往下看
在HashMap中,put一个数据的时候,会调用一个newNode方法来创建节点,而LinkedHashMap重写了该方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMapEntry<K,V> p =
new LinkedHashMapEntry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
在每次创建节点的时候,都调用了一次linkNodeLast方法,来拼接链表。
tail代表链表尾巴,head代表链表脑袋
entry.before代表前驱
entry.after代表后置
private void linkNodeLast(LinkedHashMapEntry<K,V> p) {
LinkedHashMapEntry<K,V> last = tail;
tail = p;
//判断尾部是否是空的,为空就认为链表没创建,拼接在头上
if (last == null)
head = p;
else {
//在最后一个节点的before上放前一个节点
p.before = last;
//在after上放置当前节点
last.after = p;
}
}
通过这个方法,我们就对LinkedHashMap有了一个初步的了解了。before和after分别指向前驱和后置,这是典型的双向链表的结构,稍等,我去画个赋予灵魂的配图。

有了这个图就好理解多了~
当我们 put 数据的时候,除了创建节点之外,还有一个操作,就是HashMap会回调一个 afterNodeInsertion方法,我们看一下LinkedHashMap的实现
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMapEntry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
其实这个方法是移出最老的节点,但这段代码在jdk1.8里就不在被执行了,除非你自己集成LinkedHashMap重写removeEldestEntry方法。因为removeEldestEntry=false,OK,当我们在put数据的时候,整个双链表就建立起来了,接下来我们看下get有什么操作吧
final boolean accessOrder;
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
//顺序访问模式
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
LinkedHashMap的get操作首先会从 HashMap 维护的数据中通过hash获取Node,然后判断accessOrder属性,如果等于true就调用afterNodeAccess方法
那么accessOrder是个什么呢?有什么用呢?
其实accessOrder是个标记位,用来标记数据是否按照访问顺序处理,如果设置为true,那么我们每次访问数据,这个数据都会被移动到链表尾部,就会导致链表尾部的访问频次是最高的(年老的变量),链表头部是访问频次最低的(年轻的变量),这个特性正好适合做LRU缓存。如果设置为false,也就是默认的模式,那么就是按照存储顺序存储数据,访问也不会触发置尾操作。我们接下来看一下它是怎么做到的置尾吧。
首先通过这个构造方法,把accessOrder初始化成true,默认是false
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
然后我们试一下效果
Map<String, String> map = new LinkedHashMap<>(
1 << 4, 0.75f, true
);
map.put("node1", "node1");
map.put("node2", "node1");
map.put("node3", "node1");
map.put("node4", "node1");
map.get("node1");
System.out.println(map);
{node2=node1, node3=node1, node4=node1, node1=node1}
和预期一样,访问了一次node1,它就把node1放在链表的尾巴上了,这个操作主要是在afterNodeAccess内,我们接下来看下是怎么实现的吧
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;
//如果我们get的数据是表头的数据,那么表头就需要更新为表头的后置
//比如node1->node2->node3,我们获取node1的时候,node1要跑到队尾
//所以node2就是老大
if (b == null)
head = a;
else
//否则的话,把get的数据的前置节点和get的数据的后置节点连接
//比如,get node2,node2的before正是node1
//因为node2要去队尾了,所以node1就不能在绑定after为node2了,要改成node3
b.after = a;
//a等于空说明p是队尾。因为只有队尾的后置节点是空的
if (a != null)
//把操作数据的后置节点连接上操作数据的前置节点
//比如,get node2,node的after便是node3
//node3的before在没改变的时候是node2,结果node2要去队尾,所以要连接都node1去
a.before = b;
else
//a等于空说明什么?说明p的后置节点是空的。说明p可能是队尾
last = b;
//假设last等于b的时候。结果b是空的,按照规则,before为空就要成为头
if (last == null)
head = p;
else {
//把操作数据的前置节点设置成队尾,准备去队尾了。。。
p.before = last;
//把刚才队尾的后置节点,设置成刚刚操作的node2,实锤了,真的都队尾了
last.after = p;
}
//执行队尾赋值
tail = p;
++modCount;
}
}
这个方法我在啰嗦总结一下吧
1.先把操作数据的前置和后置找处理
2.然后把它前置和它后置做链接
3.把它的前置链接到之前的队尾上,再把之前的队尾的后置链接到它身上
4.最后把队尾改成操作的数据即可
最后再让我这个灵魂画手配张图吧~

最后聊一下resize吧。既然是集成自HashMap,那么肯定也是到达了扩容阀值就要扩容的
我们去找LinkedhashMap内部,发现没有重写resize,那就说明它的扩容是由父类HashMap完成的。具体的扩容过程,可以看我另一篇讲解HashMap的文章
Java基础之LinkedHashMap原理分析的更多相关文章
- Java基础之HashMap原理分析(put、get、resize)
在分析HashMap之前,先看下图,理解一下HashMap的结构 我手画了一个图,简单描述一下HashMap的结构,数组+链表构成一个HashMap,当我们调用put方法的时候增加一个新的 key-v ...
- 原子类java.util.concurrent.atomic.*原理分析
原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...
- JAVA常用数据结构及原理分析
JAVA常用数据结构及原理分析 http://www.2cto.com/kf/201506/412305.html 前不久面试官让我说一下怎么理解java数据结构框架,之前也看过部分源码,balaba ...
- Java NIO使用及原理分析 (四)
在上一篇文章中介绍了关于缓冲区的一些细节内容,现在终于可以进入NIO中最有意思的部分非阻塞I/O.通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据.同样,写入调用将会阻塞直至 ...
- (6)Java数据结构-- 转:JAVA常用数据结构及原理分析
JAVA常用数据结构及原理分析 http://www.2cto.com/kf/201506/412305.html 前不久面试官让我说一下怎么理解java数据结构框架,之前也看过部分源码,balab ...
- Java NIO使用及原理分析 (四)(转)
在上一篇文章中介绍了关于缓冲区的一些细节内容,现在终于可以进入NIO中最有意思的部分非阻塞I/O.通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据.同样,写入调用将会阻塞直至 ...
- 支付宝app支付java后台流程、原理分析(含nei wang chuan tou)
java版支付宝app支付流程及原理分析 本实例是基于springmvc框架编写 一.流程步骤 1.执行流程 当手机端app(就是你公司开发的app)在支付 ...
- Java NIO使用及原理分析(1-4)(转)
转载的原文章也找不到!从以下博客中找到http://blog.csdn.net/wuxianglong/article/details/6604817 转载自:李会军•宁静致远 最近由于工作关系要做一 ...
- Java NIO使用及原理分析(二)
在第一篇中,我们介绍了NIO中的两个核心对象:缓冲区和通道,在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况,如 ...
随机推荐
- Oracle 根据单元格内容不同来判断不同条件
select xxx,xxx from xxx where decode(AAA,0,BBB,1,CCC)='12345' 如果AAA字段等于0则AAA=''12345' 否则如果BBB字段等于1则B ...
- JAVA使用urlrewrite实现伪静态化
什么是伪静态? 伪静态字面理解就是假的静态,说的官方点就是“地址重写,用户得到的全部地址都是经过处理后的URL地址”. 为什么要伪静态呢? 提高安全性,可以有效的避免一些参数名.ID等完全暴露在用户面 ...
- Fiddler显示指定host请求,以及过滤无用的css,js
第一步 右侧窗口点击filters 第二步 点击Use Fiters 第三步 第一个选项不动 no zone filter ,第二个选项选择 show only following hosts 第四步 ...
- 牛客网PAT练兵场-科学计数法
题目地址:https://www.nowcoder.com/pat/6/problem/4050 题解:模拟题 /** * Copyright(c) * All rights reserved. * ...
- ZK的watch机制
1.watcher原理框架 由图看出,zk的watcher由客户端,客户端WatchManager,zk服务器组成.整个过程涉及了消息通信及数据存储. zk客户端向zk服务器注册watcher的同时, ...
- caoz:数据分析这点事
http://www.wocaoseo.com/thread-53-1-1.html 先声明一下,按照传统的定义,我还真不是数据分析高手,各种关联算法,只会最简单的一种(话说不少场合还算管用):各种挖 ...
- MyEclipse2017 安装MAVEN插件办法
笔者辛苦所写,如要留用,请标明出处,谢谢 —————————————————————————————————————————————————————— 笔者由于用到的项目使用到MAVEN,为了以后搭建 ...
- 前端ES6 一些面试题
1.ES5.ES6和ES2015有什么区别? ES2015特指在2015年发布的新一代JS语言标准,ES6泛指下一代JS语言标准,包含ES2015.ES2016.ES2017.ES2018等.现阶段在 ...
- Android Weekly Notes Issue #428
Android Weekly Issue #428 Kotlin Flow Retry Operator with Exponential Backoff Delay 这是讲协程Flow系列文章中的一 ...
- 超全Python IDE武器库大总结,优缺点一目了然!
本文介绍了多个 Python IDE,并评价其优缺点.读者可以参考此文列举的 Python IDE 列表,选择适合自己的编辑器. 写 Python 代码最好的方式莫过于使用集成开发环境(IDE)了.它 ...