集合总结四(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 list running 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). Note that insertion order is not affected if a key is re-inserted into the map. (A key k is reinserted into a map m if m.put(k, v) is invoked when m.containsKey(k) would return true immediately prior to the invocation.)
从注释中,我们可以先了解到LinkedHashMap是通过哈希表和链表实现的,它通过维护一个链表来保证对哈希表迭代时的有序性,而这个有序是指键值对插入的顺序。另外,当向哈希表中重复插入某个键的时候,不会影响到原来的有序性。也就是说,假设你插入的键的顺序为1、2、3、4,后来再次插入2,迭代时的顺序还是1、2、3、4,而不会因为后来插入的2变成1、3、4、2。(但其实我们可以改变它的规则,使它变成1、3、4、2)
LinkedHashMap的实现主要分两部分,一部分是哈希表,另外一部分是链表。哈希表部分继承了HashMap,拥有了HashMap那一套高效的操作,所以我们要看的就是LinkedHashMap中链表的部分,了解它是如何来维护有序性的。
LinkedHashMap 的大致实现如下图所示,当然链表和哈希表中相同的键值对都是指向同一个对象,这里把它们分开来画只是为了呈现出比较清晰的结构。
二、属性
在看属性之前,我们先来看一下 LinkedHashMap 的声明:
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
从上面的声明中,我们可以看见 LinkedHashMap 是继承自 HashMap 的,所以它已经从 HashMap 那里继承了与哈希表相关的操作了,那么在 LinkedHashMap 中,它可以专注于链表实现的那部分,所以与链表实现相关的属性如下。
//LinkedHashMap的链表节点继承了HashMap的节点,而且每个节点都包含了前指针和后指针,所以这里可以看出它是一个双向链表
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
//头指针
transient LinkedHashMap.Entry<K,V> head;
//尾指针
transient LinkedHashMap.Entry<K,V> tail;
//默认为false。当为true时,表示链表中键值对的顺序与每个键的插入顺序一致,也就是说重复插入键,也会更新顺序
//简单来说,为false时,就是上面所指的1、2、3、4的情况;为true时,就是1、3、4、2的情况
final boolean accessOrder;
三、方法
如果你有仔细看过HashMap源码的话,你会发现HashMap中有如下三个方法:
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
如果你没有注意到注释的解释的话,你可能会很奇怪为什么会有三个空方法,而且有不少地方还调用过它们。其实这三个方法表示的是在访问、插入、删除某个节点之后,进行一些处理,它们在LinkedHashMap都有各自的实现。LinkedHashMap正是通过重写这三个方法来保证链表的插入、删除的有序性。
1、afterNodeAccess方法
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//当accessOrder的值为true,且e不是尾节点
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;
}
}
这段代码的意思简洁明了,就是把当前节点e移至链表的尾部。因为使用的是双向链表,所以在尾部插入可以以O(1)的时间复杂度来完成。并且只有当accessOrder设置为true时,才会执行这个操作。在HashMap的putVal方法中,就调用了这个方法。
2、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);
}
}
afterNodeInsertion方法是在哈希表中插入了一个新节点时调用的,它会把链表的头节点删除掉,删除的方式是通过调用HashMap的removeNode方法。想一想,通过afterNodeInsertion方法和afterNodeAccess方法,是不是就可以简单的实现一个基于最近最少使用(LRU)的淘汰策略了?当然,我们还要重写removeEldestEntry方法,因为它默认返回的是false。
3、afterNodeRemoval方法
void afterNodeRemoval(Node<K,V> e) { // unlink
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
这个方法是当HashMap删除一个键值对时调用的,它会把在HashMap中删除的那个键值对一并从链表中删除,保证了哈希表和链表的一致性。
4、get方法
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的getNode方法来获取结果的。并且,如果你把accessOrder设置为true,那么在获取到值之后,还会调用afterNodeAccess方法。这样是不是就能保证一个LRU的算法了?
5、put方法和remove方法
我在LinkedHashMap的源码中没有找到put方法,这就说明了它并没有重写put方法,所以我们调用的put方法其实是HashMap的put方法。因为HashMap的put方法中调用了afterNodeAccess方法和afterNodeInsertion方法,已经足够保证链表的有序性了,所以它也就没有重写put方法了。remove方法也是如此。
集合总结四(LinkedHashMap的实现原理)的更多相关文章
- java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现
java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析 ...
- Java中的集合(十四) Map的实现类LinkedHashMap
Java中的集合(十四) Map的实现类LinkedHashMap 一.LinkedHashMap的简介 LinkedHashMap是Map接口的实现类,继承了HashMap,它通过重写父类相关的方法 ...
- RocketMQ详解(四)核心设计原理
专题目录 RocketMQ详解(一)原理概览 RocketMQ详解(二)安装使用详解 RocketMQ详解(三)启动运行原理 RocketMQ详解(四)核心设计原理 RocketMQ详解(五)总结提高 ...
- linux基础-第十四单元 Linux网络原理及基础设置
第十四单元 Linux网络原理及基础设置 三种网卡模式图 使用ifconfig命令来维护网络 ifconfig命令的功能 ifconfig命令的用法举例 使用ifup和ifdown命令启动和停止网卡 ...
- TCP协议—三次握手四次挥手的原理<转>
三次握手四次挥手的原理 TCP是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接.在TCP/IP协议中,TCP 协议提供可靠的连接服务,连接是通过三次握手进行初始化的.三 ...
- Java List集合 遍历 四种方式(包含 Lambda 表达式遍历)
示例代码如下: package com.miracle.luna.lambda; import java.util.ArrayList; import java.util.List; /** * @A ...
- Java中的集合(四)PriorityQueue常用方法
Java中的集合(四)PriorityQueue常用方法 PriorityQueue的基本概念等都在上一篇已说明,感兴趣的可以点击 Java中的集合(三)继承Collection的Queue接口 查看 ...
- HashSet集合存储数据的结构和HashSet集合存储元素不重复的原理
HashSet集合存储数据的结构 HashSet集合存储元素不重复的原理 //创建HashSet集合对象 Hashset<String> set = new HashSet<> ...
- Java集合系列(四):HashMap、Hashtable、LinkedHashMap、TreeMap的使用方法及区别
本篇博客主要讲解Map接口的4个实现类HashMap.Hashtable.LinkedHashMap.TreeMap的使用方法以及三者之间的区别. 注意:本文中代码使用的JDK版本为1.8.0_191 ...
随机推荐
- [python](windows)分布式进程问题:pickle模块不能序列化lambda函数
运行错误:_pickle.PicklingError: Can't pickle <function <lambda> at 0x000002BAAEF12F28>: attr ...
- VS 2017常用快捷键
VS 2017常用快捷键 1.查找和替换 1)查找:使用组合键“Ctrl+F”: 2)替换:使用组合键“Ctrl+H”. (批量更改函数名的神器!) 2.复制/剪切/删除整行代码 1)如果你想复制一整 ...
- Andorid Studio中运行模拟器--夜神模拟器
这样可以直接在夜神模拟器上运行app然后在androidstudio上查看log…. 1.下载夜神模拟器 2.修改配置 点击右上角的设置图标,对夜神模拟器的分辨率进行选择,手机版的480×800的就差 ...
- js获取当前时间:封装js的日期时间
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- Electron入门之ipcMain,ipcRenderer
ipcMain 模块是类EventEmitter的实例.当在主进程中使用它的时候,它控制着由渲染进程(web page)发送过来的异步或同步消息.从渲染进程发送过来的消息将触发事件. [发消息] 发送 ...
- 菜鸟脱壳之脱壳的基础知识(四)——利用ESP定律来寻找OEP
.上节说的是单步跟踪法,这节讲的是利用堆栈平衡(ESP定律)来进行脱壳!想必大家都听说过ESP定律这个大名吧!ESP定律运用的就是堆栈平衡原理!一般的加壳软件在执行时,首先要初始化,保存环境(保存各个 ...
- 1.3 第一个Go程序
1.3.1 Hello Go // hello.go package main import ( "fmt" ) func main() { fmt.Println("H ...
- 实践:搭建基于Load Balancer的MySql Cluster
服务器规划: 整套系统全部在rhel5u1 server 64位版本下,由基于xen的虚拟机搭建,其中集群管理节点*2.SQL节点*2.数据节点*4.Web服务节点*2组成,其中数据节点做成2个组,每 ...
- js中通过Object.prototype.toString方法----精确判断对象的类型
判断是否为函数 function isFunction(it) { return Object.prototype.toString.call(it) === '[object Func ...
- flashfxp 数据socket错误 连接已超时 filezilla
最近windows server 开启了防火墙后发现flashfxp连不上,报超时. 1,服务端的动态端口从指定的范围内取, 2,防火墙开启范围内端口. 参考:http://jingyan.baidu ...