【Java集合系列六】LinkedHashMap解析
2017-08-14 16:30:10
1、简介
LinkedHashMap继承自HashMap,能保证迭代顺序,支持其他Map可选的操作。采用双向链表存储元素,默认的迭代序是插入序。重复插入一个已经存在的key不影响此顺序。如果accessOrder参数被使用且置为true,迭代序使用访问序,访问序受put、get、putAll等方法的影响,但不受集合视图操作的影响(其实HashMap中好像并没有什么视图操作,不像List有subList方法)。LinkedHashMap不是线程安全的。
2、与HashMap相比特殊点
之前提到了,HashMap不保持顺序,但是LinkedHashMap能保证迭代序。同时还支持两种遍历顺序:插入序和访问序。之所以能实现这些功能和效果,是因为LinkedHashMap重载了LinkedEntry,实现了双向链表,所以插入时,同时插入到table数组和双向链表header,如果是访问序,则在get、putAll等方法操作时,会将操作过的元素链接到表尾,保证链表尾部永远是最近使用过元素。
总结就是:LinkedHashMap有两套元素存储机制:数组table和header,所有区别于HashMap的特点都是通过双向链表header实现的。
3、LinkedEntry
LinkedEntry继承自HashMapEntry,新增了2个元素:nxt和prv来实现双向链表,代码如下:
/**
* LinkedEntry adds nxt/prv double-links to plain HashMapEntry.
*/
static class LinkedEntry<K, V> extends HashMapEntry<K, V> {
LinkedEntry<K, V> nxt;
LinkedEntry<K, V> prv; /** Create the header entry */
LinkedEntry() {
super(null, null, 0, null);
nxt = prv = this;
} /** Create a normal entry */
LinkedEntry(K key, V value, int hash, HashMapEntry<K, V> next,
LinkedEntry<K, V> nxt, LinkedEntry<K, V> prv) {
super(key, value, hash, next);
this.nxt = nxt;
this.prv = prv;
}
}
4、put操作
LinkedHashMap没有重写put方法,而是重写了put方法调用的addNewEntry方法,该方法执行真正插入一个元素的操作。插入元素时,同时插入到table数组和header双向链表,代码如下:
@Override void addNewEntry(K key, V value, int hash, int index) {
LinkedEntry<K, V> header = this.header;
// 移除最久没使用过的元素,removeEldestEntry方法默认返回false,适合子类重写
LinkedEntry<K, V> eldest = header.nxt;
if (eldest != header && removeEldestEntry(eldest)) {
remove(eldest.key);
}
// Create new entry, link it on to list, and put it into table
// 1、将元素查到链表尾部;
// 2、将元素插入到table数组中;
LinkedEntry<K, V> oldTail = header.prv;
LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
key, value, hash, table[index], header, oldTail);//newTail.prv = oldTail, newTail.nxt = header,其实就是在Tail元素和Header元素之间插入
table[index] = oldTail.nxt = header.prv = newTail; //1、前一行代码只处理了部分双向链表插入操作,这里继续处理,oldTail.nxt = newTail, header.prv = newTail;
//2、插入table[index];
}
@Override void addNewEntryForNullKey(V value) {
LinkedEntry<K, V> header = this.header;
// 移除最久没使用过的元素,removeEldestEntry方法默认返回false,适合子类重写
LinkedEntry<K, V> eldest = header.nxt;
if (eldest != header && removeEldestEntry(eldest)) {
remove(eldest.key);
}
//与addNewEntry方法类似,只是没有插入数组的操作
LinkedEntry<K, V> oldTail = header.prv;
LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
null, value, 0, null, header, oldTail);
entryForNullKey = oldTail.nxt = header.prv = newTail;
}
5、get操作
/**
* Relinks the given entry to the tail of the list. Under access ordering,
* this method is invoked whenever the value of a pre-existing entry is
* read by Map.get or modified by Map.put.
*/
private void makeTail(LinkedEntry<K, V> e) {
// 将元素e从当前位置移除
// Unlink e
e.prv.nxt = e.nxt;
e.nxt.prv = e.prv; // 连接到链表尾部
// Relink e as tail
LinkedEntry<K, V> header = this.header;
LinkedEntry<K, V> oldTail = header.prv;
e.nxt = header;
e.prv = oldTail;
oldTail.nxt = header.prv = e;
modCount++;
} @Override public V get(Object key) {
/*
* This method is overridden to eliminate the need for a polymorphic
* invocation in superclass at the expense of code duplication.
*/
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
if (e == null)
return null;
if (accessOrder) //如果是访问序,将当前元素移到链表尾部(保证最近使用的元素在尾部)
makeTail((LinkedEntry<K, V>) e);
return e.value;
} // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
// 这里的遍历操作与HashMap的类似,唯一的区别是:如果是访问序,则将该元素移到链表尾部
int hash = secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
return e.value;
}
}
return null;
}
6、preModify操作
在HashMap的put、putValueForNullKey方法用到了preModify方法,作用就是保证访问序,代码如下:
@Override void preModify(HashMapEntry<K, V> e) {
if (accessOrder) {
makeTail((LinkedEntry<K, V>) e);
}
}
7、remove操作
LinkedHashMap方法没有重写remove方法,但是重写了postRemove方法,该方法在HashMap的remove方法中有调用,如下:
//作用:从双向链表中移除元素e
@Override void postRemove(HashMapEntry<K, V> e) {
LinkedEntry<K, V> le = (LinkedEntry<K, V>) e;
le.prv.nxt = le.nxt;
le.nxt.prv = le.prv;
le.nxt = le.prv = null; // Help the GC (for performance)
}
8、Iterator体系
LinkedHashMap保证迭代顺序,支持插入序和访问序,那么它的Iterator是怎么实现的?体系与HashMap类似,实现了最顶层的LinkedHashIterator,如下:
private abstract class LinkedHashIterator<T> implements Iterator<T> {
// 1、前面提到过,header只是一个虚拟元素,真正的表头元素是header.nxt,表尾元素是header.prv,所以遍历的时候从表头开始;
// 2、nextEntry很简单,直接取nxt即可;
LinkedEntry<K, V> next = header.nxt;
LinkedEntry<K, V> lastReturned = null;
int expectedModCount = modCount;
public final boolean hasNext() {
return next != header;
}
final LinkedEntry<K, V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
LinkedEntry<K, V> e = next;
if (e == header)
throw new NoSuchElementException();
next = e.nxt;
return lastReturned = e;
}
// remove操作使用HashMap的remove方法,LinkedHashMap重写了postRemove方法
public final void remove() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (lastReturned == null)
throw new IllegalStateException();
LinkedHashMap.this.remove(lastReturned.key);
lastReturned = null;
expectedModCount = modCount;
}
}
至于其他的,就不用细说了,和HashMap类似。
【Java集合系列六】LinkedHashMap解析的更多相关文章
- Java集合系列[4]----LinkedHashMap源码分析
这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...
- 【Java集合系列】目录
2017-07-29 13:49:40 一.Collection的全局继承关系 二.系列文章 [Java集合系列一]ArrayList解析 备注: 1.ArrayList本质上就是一个数组,所有对外提 ...
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
概要 前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...
- Java 集合系列07之 Stack详细介绍(源码解析)和使用示例
概要 学完Vector了之后,接下来我们开始学习Stack.Stack很简单,它继承于Vector.学习方式还是和之前一样,先对Stack有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它. ...
- Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例
概要 这一章,我们对HashMap进行学习.我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap.内容包括:第1部分 HashMap介绍第2部分 HashMa ...
- Java 集合系列11之 Hashtable详细介绍(源码解析)和使用示例
概要 前一章,我们学习了HashMap.这一章,我们对Hashtable进行学习.我们先对Hashtable有个整体认识,然后再学习它的源码,最后再通过实例来学会使用Hashtable.第1部分 Ha ...
- Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 10 Hashtable详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 06 Stack详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
随机推荐
- HDU 3948 The Number of Palindromes(Manacher+后缀数组)
题意 求一个字符串中本质不同的回文子串的个数. $ 1\leq |string| \leq 100000$ 思路 好像是回文自动机的裸题,但是可以用 \(\text{Manacher}\) (马拉车) ...
- 【ActiveReports 大数据分析报告】用数据分析的手段告诉你,复联4有多火爆?
消失的人们会回来吗,奇异博士所说的1400万分之一可能性究竟是什么,还会有谁逝去? 4月24日零时,随着万众期待的<复仇者联盟4>(以下简称<复联4>)正式上映,一切谜底都将揭 ...
- 区块链 编译android geth 填坑记录 ubuntu
下载geth 源码 直接 make android 下载android ndk sdk 配置环境变量cd 安装golang 设置环境变量 发现没有gomobile命令 按照wiki方法 执行安装g ...
- node 之 Buffer 笔记
1. Buffer 相关 js 最初是被设计来处理 html的,因此它不善于处理二进制数据,只有数值和字符串类型.而 node 是基于 js 的,因为 node 需要实现一些譬如数据库通信.操作图像及 ...
- 【笔记】Nginx热更新相关知识
(以下学习笔记内容均摘自参考链接,仅供个人查阅) 1.inotify文件系统监控特性 Inotify 是一个 Linux 内核特性,它监控文件系统,并且及时向专门的应用程序发出相关的事件警告,比如 ...
- 自定义Cordova插件(基础篇)
cordova自定义插件 注意:存放自定义cordova插件目录不能有空格可能会报错 cordova的安装 下载node.js,安装完成后你可以在命令行中使用node和npm. 安装cordova使用 ...
- 在action中进行文件下载,下载时运行不报错,可是也不下载
在写前端下载页面时,使用ajax方式调用action中的方法,然后就将下载内容返回js中了,所以没有下载,之后改为使用Windows.location进行下载,就没有问题了. action中代码: i ...
- python 2.7 读写 opc数据
运行环境 python2.7+window server2008+keep server 1.安装OpenOPC a> 下载 OpenOPC-1.3.1.win32-py2.7 (1).exe ...
- c#格林治时间实现
C#时间戳的简单实现 Introduction: 在项目开发中,我们都经常会用到时间戳来进行时间的存储和传递,最常用的Unix时间戳(TimeStamp)是指格林尼治时间1970年1月1日0时(北 ...
- 按照分层设计理念,完成《XXX需求征集系统》的概念结构设计
按照分层设计理念,完成<XXX需求征集系统>的概念结构设计. 1.概要架构-初步设计 有关<XXX需求征集系统>的鲁棒图如下: 2.概要架构之高层分割 切系统为系统: 高层功能 ...