LinkedHashMap 底层分析
众所周知 HashMap 是一个无序的 Map
,因为每次根据 key
的 hashcode
映射到 Entry
数组上,所以遍历出来的顺序并不是写入的顺序。
因此 JDK 推出一个基于 HashMap
但具有顺序的 LinkedHashMap
来解决有排序需求的场景。
它的底层是继承于 HashMap
实现的,由一个双向链表所构成。
LinkedHashMap
的排序方式有两种:
- 根据写入顺序排序。
- 根据访问顺序排序。
其中根据访问顺序排序时,每次 get
都会将访问的值移动到链表末尾,这样重复操作就能的到一个按照访问顺序排序的链表。
数据结构
@Test
public void test(){
Map<String, Integer> map = new LinkedHashMap<String, Integer>();
map.put("1",1) ;
map.put("2",2) ;
map.put("3",3) ;
map.put("4",4) ;
map.put("5",5) ;
System.out.println(map.toString());
}
调试可以看到 map
的组成:
打开源码可以看到:
/**
* The head of the doubly linked list.
*/
private transient Entry<K,V> header;
/**
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
*
* @serial
*/
private final boolean accessOrder;
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
}
其中 Entry
继承于 HashMap
的 Entry
,并新增了上下节点的指针,也就形成了双向链表。
还有一个 header
的成员变量,是这个双向链表的头结点。
上边的 demo 总结成一张图如下:
第一个类似于 HashMap
的结构,利用 Entry
中的 next
指针进行关联。
下边则是 LinkedHashMap
如何达到有序的关键。
就是利用了头节点和其余的各个节点之间通过 Entry
中的 after
和 before
指针进行关联。
其中还有一个 accessOrder
成员变量,默认是 false
,默认按照插入顺序排序,为 true
时按照访问顺序排序,也可以调用:
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
这个构造方法可以显示的传入 accessOrder
。
构造方法
LinkedHashMap
的构造方法:
public LinkedHashMap() {
super();
accessOrder = false;
}
其实就是调用的 HashMap
的构造方法:
HashMap
实现:
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
//HashMap 只是定义了改方法,具体实现交给了 LinkedHashMap
init();
}
可以看到里面有一个空的 init()
,具体是由 LinkedHashMap
来实现的:
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
其实也就是对 header
进行了初始化。
put 方法
看 LinkedHashMap
的 put()
方法之前先看看 HashMap
的 put
方法:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
//空实现,交给 LinkedHashMap 自己实现
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// LinkedHashMap 对其重写
addEntry(hash, key, value, i);
return null;
}
// LinkedHashMap 对其重写
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
// LinkedHashMap 对其重写
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
主体的实现都是借助于 HashMap
来完成的,只是对其中的 recordAccess(), addEntry(), createEntry()
进行了重写。
LinkedHashMap
的实现:
//就是判断是否是根据访问顺序排序,如果是则需要将当前这个 Entry 移动到链表的末尾
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
//调用了 HashMap 的实现,并判断是否需要删除最少使用的 Entry(默认不删除)
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
//就多了这一步,将新增的 Entry 加入到 header 双向链表中
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
//写入到双向链表中
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
get 方法
LinkedHashMap 的 get()
方法也重写了:
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
//多了一个判断是否是按照访问顺序排序,是则将当前的 Entry 移动到链表头部。
e.recordAccess(this);
return e.value;
}
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
//删除
remove();
//添加到头部
addBefore(lm.header);
}
}
clear()
清空就要比较简单了:
//只需要把指针都指向自己即可,原本那些 Entry 没有引用之后就会被 JVM 自动回收。
public void clear() {
super.clear();
header.before = header.after = header;
}
总结
总的来说 LinkedHashMap
其实就是对 HashMap
进行了拓展,使用了双向链表来保证了顺序性。
因为是继承与 HashMap
的,所以一些 HashMap
存在的问题 LinkedHashMap
也会存在,比如不支持并发等。
号外
最近在总结一些 Java 相关的知识点,感兴趣的朋友可以一起维护。
LinkedHashMap 底层分析的更多相关文章
- HDFS下载数据机制的底层分析
HDFS下载数据机制的底层分析 Hadoop中的RPC(Remote Procedure Call)框架 hadoop中结点间的通信采用的是RPC. RPC框架的实现机制图解: 从hdfs下载数据的源 ...
- HashMap底层分析
以下基于 JDK1.7 分析. 如图所示,HashMap 底层是基于数组和链表实现的.其中有两个重要的参数: 容量 负载因子 容量的默认大小是 16,负载因子是 0.75,当 HashMap 的 si ...
- Block详解二(底层分析)
Block专辑: Block讲解一 MRC-block与ARC-block Block详解一(底层分析) 今天讲述Block的最后一篇,后两篇仅仅是加深1,2篇的理解,废话少说,开始讲解! __blo ...
- JDK1.8源码逐字逐句带你理解LinkedHashMap底层
注意 我希望看这篇的文章的小伙伴如果没有了解过HashMap那么可以先看看我这篇文章:http://blog.csdn.net/u012403290/article/details/65442646, ...
- hashMap 底层原理+LinkedHashMap 底层原理+常见面试题
1.源码 java1.7 hashMap 底层实现是数组+链表 java1.8 对上面进行优化 数组+链表+红黑树 2.hashmap 是怎么保存数据的. 在hashmap 中有这样一个结构 ...
- HashMap 底层分析
以下基于 JDK1.7 分析 如图所示,HashMap底层是基于数组和链表实现的,其中有两个重要的参数: ---容量 ---负载因子 容量的默认大小是16,负载因子是0.75,当HashMap的siz ...
- 5.4 集合的排序(Java学习笔记)(Collections.sort(),及Arrays.sort()底层分析)
1.Comparable接口 这个接口顾名思义就是用于排序的,如果要对某些对象进行排序,那么该对象所在的类必须实现 Comparabld接口.Comparable接口只有一个方法CompareTo() ...
- Swift--struct与class的区别(汇编角度底层分析)
概述 相对Objective-C, Swift使用结构体Struct的比例大大增加了,其中Int, Bool,以及String,Array等底层全部使用Struct来定义!在Swift中结构体不仅可以 ...
- Java类集框架详细汇总-底层分析
前言: Java的类集框架比较多,也十分重要,在这里给出图解,可以理解为相应的继承关系,也可以当作重要知识点回顾: Collection集合接口 继承自:Iterable public interfa ...
随机推荐
- 洛谷p1106 删数问题 题解
传送门 # 4.24一个重要的日子.我人生中第一道7个测试点下载了5个的题目被我发现了,第一次用光下载数据点机会,真心坑点重重. 这题是一道很经典的贪心题目,可能是因为我太蒻了,导致我一直以为最少普及 ...
- input autocomplete属性设计输入框自动联想(php实现)
因为客户临时要求加一个输入框自动联想,就开始了解这块.结合网上总结最难的一点就是找好对应的js版本以及相应的jQuery-ui: 以下是我用的版本,以及连接地址: jQuery的js文件: <s ...
- Git是什么、Git的功能、为什么versioncontrol用Git、Git的常用命令、Git的优缺点
Git是什么 git是目前世界上最先进的分布式版本控制系统(没有之一). Git是用于 Linux内核开发的版本控制工具.与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版 ...
- [POJ1961]Period (KMP)
题意 求字符串s的最小循环元长度和循环次数 思路 s[1~i]满足循环元要len能整除i并且s[len+1~i]=s[1~i-len] 代码 #include<cstdio> #inclu ...
- react 的进阶
一 react 中table报错 validateDOMNesting(...): <tr> cannot appear as a child of <table>. See ...
- Egret的容器--删除对象,遮罩
class P91F extends egret.Sprite { public constructor() { super(); this.addEventListener(egret.Event. ...
- spring创建bean的三种方式
spring创建bean的三种方式: 1通过构造方法创建bean(最常用) 1.1 spring默认会通过无参构造方法来创建bean,如果xml文件是这样配置,则实体类中必须要有无参构造方法,无参构造 ...
- docker pull 镜像报错
[root@localhost ~]# docker pull ningx Using default tag: latest Trying to pull repository docker.io/ ...
- IaaS,PaaS和SaaS
云计算的三种服务模式:IaaS,PaaS和SaaS IaaS: Infrastructure-as-a-Service(基础设施即服务)是第一层. PaaS: Platform-as-a-Servic ...
- centos7.5安装python3.7
系统状态 CentOS Linux release 7.5.1804 (Core) mini版安装系统 Python-3.7.0.tgz 官方下载源码包 安装系统依赖包 # 编译必备 yum inst ...