众所周知 HashMap 是一个无序的 Map,因为每次根据 keyhashcode 映射到 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 继承于 HashMapEntry,并新增了上下节点的指针,也就形成了双向链表。

还有一个 header 的成员变量,是这个双向链表的头结点。

上边的 demo 总结成一张图如下:

第一个类似于 HashMap 的结构,利用 Entry 中的 next 指针进行关联。

下边则是 LinkedHashMap 如何达到有序的关键。

就是利用了头节点和其余的各个节点之间通过 Entry 中的 afterbefore 指针进行关联。

其中还有一个 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 方法

LinkedHashMapput() 方法之前先看看 HashMapput 方法:

    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 相关的知识点,感兴趣的朋友可以一起维护。

地址: https://github.com/crossoverJie/Java-Interview

LinkedHashMap 底层分析的更多相关文章

  1. HDFS下载数据机制的底层分析

    HDFS下载数据机制的底层分析 Hadoop中的RPC(Remote Procedure Call)框架 hadoop中结点间的通信采用的是RPC. RPC框架的实现机制图解: 从hdfs下载数据的源 ...

  2. HashMap底层分析

    以下基于 JDK1.7 分析. 如图所示,HashMap 底层是基于数组和链表实现的.其中有两个重要的参数: 容量 负载因子 容量的默认大小是 16,负载因子是 0.75,当 HashMap 的 si ...

  3. Block详解二(底层分析)

    Block专辑: Block讲解一 MRC-block与ARC-block Block详解一(底层分析) 今天讲述Block的最后一篇,后两篇仅仅是加深1,2篇的理解,废话少说,开始讲解! __blo ...

  4. JDK1.8源码逐字逐句带你理解LinkedHashMap底层

    注意 我希望看这篇的文章的小伙伴如果没有了解过HashMap那么可以先看看我这篇文章:http://blog.csdn.net/u012403290/article/details/65442646, ...

  5. hashMap 底层原理+LinkedHashMap 底层原理+常见面试题

    1.源码 java1.7    hashMap 底层实现是数组+链表 java1.8 对上面进行优化  数组+链表+红黑树 2.hashmap  是怎么保存数据的. 在hashmap 中有这样一个结构 ...

  6. HashMap 底层分析

    以下基于 JDK1.7 分析 如图所示,HashMap底层是基于数组和链表实现的,其中有两个重要的参数: ---容量 ---负载因子 容量的默认大小是16,负载因子是0.75,当HashMap的siz ...

  7. 5.4 集合的排序(Java学习笔记)(Collections.sort(),及Arrays.sort()底层分析)

    1.Comparable接口 这个接口顾名思义就是用于排序的,如果要对某些对象进行排序,那么该对象所在的类必须实现 Comparabld接口.Comparable接口只有一个方法CompareTo() ...

  8. Swift--struct与class的区别(汇编角度底层分析)

    概述 相对Objective-C, Swift使用结构体Struct的比例大大增加了,其中Int, Bool,以及String,Array等底层全部使用Struct来定义!在Swift中结构体不仅可以 ...

  9. Java类集框架详细汇总-底层分析

    前言: Java的类集框架比较多,也十分重要,在这里给出图解,可以理解为相应的继承关系,也可以当作重要知识点回顾: Collection集合接口 继承自:Iterable public interfa ...

随机推荐

  1. 洛谷p1106 删数问题 题解

    传送门 # 4.24一个重要的日子.我人生中第一道7个测试点下载了5个的题目被我发现了,第一次用光下载数据点机会,真心坑点重重. 这题是一道很经典的贪心题目,可能是因为我太蒻了,导致我一直以为最少普及 ...

  2. input autocomplete属性设计输入框自动联想(php实现)

    因为客户临时要求加一个输入框自动联想,就开始了解这块.结合网上总结最难的一点就是找好对应的js版本以及相应的jQuery-ui: 以下是我用的版本,以及连接地址: jQuery的js文件: <s ...

  3. Git是什么、Git的功能、为什么versioncontrol用Git、Git的常用命令、Git的优缺点

    Git是什么 git是目前世界上最先进的分布式版本控制系统(没有之一). Git是用于 Linux内核开发的版本控制工具.与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版 ...

  4. [POJ1961]Period (KMP)

    题意 求字符串s的最小循环元长度和循环次数 思路 s[1~i]满足循环元要len能整除i并且s[len+1~i]=s[1~i-len] 代码 #include<cstdio> #inclu ...

  5. react 的进阶

    一 react 中table报错 validateDOMNesting(...): <tr> cannot appear as a child of <table>. See ...

  6. Egret的容器--删除对象,遮罩

    class P91F extends egret.Sprite { public constructor() { super(); this.addEventListener(egret.Event. ...

  7. spring创建bean的三种方式

    spring创建bean的三种方式: 1通过构造方法创建bean(最常用) 1.1 spring默认会通过无参构造方法来创建bean,如果xml文件是这样配置,则实体类中必须要有无参构造方法,无参构造 ...

  8. docker pull 镜像报错

    [root@localhost ~]# docker pull ningx Using default tag: latest Trying to pull repository docker.io/ ...

  9. IaaS,PaaS和SaaS

    云计算的三种服务模式:IaaS,PaaS和SaaS IaaS: Infrastructure-as-a-Service(基础设施即服务)是第一层. PaaS: Platform-as-a-Servic ...

  10. centos7.5安装python3.7

    系统状态 CentOS Linux release 7.5.1804 (Core) mini版安装系统 Python-3.7.0.tgz 官方下载源码包 安装系统依赖包 # 编译必备 yum inst ...