Map 家族数量众多,其中 HashMap 和 ConcurrentHashMap 用的最多,而 LinkedHashMap 似乎则是不怎么用的,但是他却有着顺序。两种,一种是添加顺序,一种是访问顺序。

LinkedHashMap 继承了 HashMap。那么如果是你,你怎么实现这两个顺序呢?

如果实现添加顺序的话,我们可以在该类中,增加一个链表,每个节点对应 hash 表中的桶。这样,循环遍历的时候,就可以按照链表遍历了。只是会增大内存消耗。

如果实现访问顺序的话,同样也可以使用链表,但每次读取数据时,都需要更新一下链表,将最近一次读取的放到链尾。这样也就能够实现。此时也可以跟进这个特性实现 LRU(Least Recently Used) 缓存。

如何使用?

下面是个小 demo

LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
for (int i = 0; i < 10; i++) {map.put(i, i);
}for (Map.Entry entry : map.entrySet()) {System.out.println(entry.getKey() + ":" + entry.getValue());
}
map.get(3);
System.out.println();
for (Map.Entry entry : map.entrySet()) {System.out.println(entry.getKey() + ":" + entry.getValue());
}

打印结果:

0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:8
9:90:0
1:1
2:2
4:4
5:5
6:6
7:7
8:8
9:9
3:3

首先构造方法是有意思的,比 HashMap 多了一个 accessOrder boolean 参数。表示,按照访问顺序来排序。最新访问的放在链表尾部。

如果是默认的,则是按照添加顺序,即 accessOrder 默认是 false。

源码实现

如果看 LinkedHashMap 内部源码,会发现,内部确实维护了一个链表:

/*** 双向链表的头,最久访问的*/
transient LinkedHashMap.Entry<K,V> head;/*** 双向链表的尾,最新访问的*/
transient LinkedHashMap.Entry<K,V> tail;

而这个 LinkedHashMap.Entry 内部也维护了双向链表必须的元素,before,after:

/*** HashMap.Node subclass for normal LinkedHashMap entries.*/
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);}
}

在添加元素的时候,会追加到尾部。

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {LinkedHashMap.Entry<K,V> p =new LinkedHashMap.Entry<K,V>(hash, key, value, e);linkNodeLast(p);return p;
}// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {LinkedHashMap.Entry<K,V> last = tail;tail = p;if (last == null)head = p;else {p.before = last;last.after = p;}
}

在 get 的时候,会根据 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;
}void afterNodeAccess(Node<K,V> e) { // move node to lastLinkedHashMap.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;if (b == null)head = a;elseb.after = a;if (a != null)a.before = b;elselast = b;if (last == null)head = p;else {p.before = last;last.after = p;}tail = p;++modCount;}
}

同时注意:这里修改了 modCount,即使是读操作,并发也是不安全的。

如何实现 LRU 缓存?

LRU 缓存:LRU(Least Recently Used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

LinkedHashMap 并没有帮我我们实现具体,需要我们自己实现 。具体实现方法是 removeEldestEntry 方法。

一起来看看原理。

首先,HashMap 在 putVal 方法最后,会调用 afterNodeInsertion 方法,其实就是留给 LinkedHashMap 的。而 LinkedHashMap 的具体实现则是根据一些条件,判断是否需要删除 head 节点。

源码如下:

void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K,V> first;if (evict && (first = head) != null && removeEldestEntry(first)) {K key = first.key;removeNode(hash(key), key, null, false, true);}
}

evict 参数表示是否需要删除某个元素,而这个 if 判断需要满足的条件如上:head 不能是 null,调用 removeEldestEntry 方法,返回 true 的话,就删除这个 head。而这个方法默认是返回 false 的,等待着你来重写。

所以,removeEldestEntry 方法的实现通常是这样:

public boolean removeEldestEntry(Map.Entry<K, V> eldest){return size() > capacity;
}

如果长度大于容量了,那么就需要清除不经常访问的缓存了。afterNodeInsertion 会调用 removeNode 方法,删除掉 head 节点 —— 如果 accessOrder 是 true 的话,这个节点就是最不经常访问的节点。

拾遗

LinkedHashMap 重写了一些 HashMap 的方法,例如 containsValue 方法,这个方法大家猜一猜,怎么重写比较合理?

HashMap 使用了双重循环,先循环外层的 hash 表,再循环内层的 entry 链表。性能可想而知。

但 LinkedHashMap 内部有个元素链表,直接遍历链表就行。相对而言而高很多。

public boolean containsValue(Object value) {for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {V v = e.value;if (v == value || (value != null && value.equals(v)))return true;}return false;
}

这也算一种空间换时间的策略吧。

get 方法当然也是要重写的。因为需要根据 accessOrder 更新链表。

总结

雪薇的总结的一下:

LinkedHashMap 内部包含一个双向链表维护顺序,支持两种顺序——添加顺序,访问顺序。

默认就是按照添加顺序来的,如果要改成访问顺序的话,构造方法中的 accessOrder 需要设置成 true。这样,每次调用 get 方法,就会将刚刚访问的元素更新到链表尾部。

关于 LRU,在accessOrder 为 true 的模式下,你可以重写 removeEldestEntry 方法,返回 size() > capacity,这样,就可以删除最不常访问的元素。

作者:莫那一鲁道

https://www.jianshu.com/p/06a0fd962e0b

推荐阅读 ↓↓↓

1.不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事

2.如何才能成为优秀的架构师?

3.从零开始搭建创业公司后台技术栈

4.“37岁,985毕业,年薪50万,被裁掉只用了10分钟”

5.37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...

6.副业&接私活必备的 10 个开源项目!

7.你知道哪10大算法统治着全球吗?

8.15张图看懂瞎忙和高效的区别!

一个人学习、工作很迷茫?

点击「阅读原文」加入我们的小圈子!

为什么说LinkedHashMap是Java中最大的数据结构? 了解一下?相关推荐

  1. java中常见的数据结构分类

    自己总结了下java中常见的数据结构和分类 在这里,我总结了list中数据结构对应我们所学的线性表,属于顺序存储还是链式存储,但没有总结set数据结构对应我们所学的哪一种(按理说应该是集合),是因为t ...

  2. Java基础-JAVA中常见的数据结构介绍

    Java基础-JAVA中常见的数据结构介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是数据结构 答:数据结构是指数据存储的组织方式.大致上分为线性表.栈(Stack) ...

  3. Java中的数组数据结构需要了解的要点

    首先,数组是Java中的对象.它们不是像int.short或long这样的基本类,也不是具有很多方法的全功能对象,但由于它们是对象,所以它们隐式地扩展了Object,这就是为什么可以使用数组引用(例如 ...

  4. java数据结构创建树_在java中创建树数据结构?

    我试图在 java中创建一个树数据结构,其中每个父节点只能有三个子节点,但在节点至少有一个子节点但少于3个子节点的情况下,我一直坚持在树上添加一个节点.我不确定是否应该使用迭代器来迭代我当前节点的节点 ...

  5. java中数组的数据结构_Java数据结构与算法(一)--数组

    目录 数组是应用最广泛的数据存储结构.它被植入大部分的编程语音.在Java中数组用来存放一种类型的集合. 1.数组的介绍 ①数组的声明 第一种方式: int[] arr = new int[10];/ ...

  6. java中json重复数据结构_JS实现去除数组中重复json的方法示例

    本文实例讲述了JS实现去除数组中重复json的方法.分享给大家供大家参考,具体如下: var array = [{"name":"123"},{"na ...

  7. java中json重复数据结构_怎么将有JSON中有相同值放在一组?

    处理的json是文章的列表,其中有:日期.文章标题.连接.发布时间戳等对象. 做android app打算把同一天的文章都在viewpager的一页里,那应该怎么返回相应的数据结构呢? 我想过将日期做 ...

  8. java中json重复数据结构_JAVA把各种数据结构转换为JSON格式

    Java代码 import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import ja ...

  9. java中的数据结构总结

    Java的类库实在是很多,以至于很多人都不太了解,结果总是自己造轮子. 下面汇总了Java中的一些数据结构,加上一些实现的分析,同时备忘. 至于时间复杂度,个人觉得写出来的用处不大.如果明白它是怎么实 ...

  10. java中是否支持多重继承_java支持多重继承吗 JAVA特性面试题:

    1.简要介绍java程序的健壮性. 答:JAVA程序会在编译和运行的时候自动的检测可能出现的错误,而且它是一种强类型语言,对于类型的检查很严格,而且它的垃圾回收机制也有效的避免了内存的泄漏. 2.为什 ...

最新文章

  1. mSystems: 中科院动物所王德华组揭示低温下野生动物肠道菌群提高存活率的机制...
  2. BigDecimal divide方法结果为无限小数问题
  3. 酷派android是什么系统版本,酷派大神X7的手机系统是什么?能升级安卓5.0吗?
  4. nginx管理面板_吸塑包装自建网站上线,阿里云ecs+bt面板+WordPress
  5. java长时间_什么导致Java中长时间的旋转和同步时间?
  6. 不均衡数据集采样2——BorderlineSMOTE算法(过采样)
  7. Java并发(四)——synchronized、volatile
  8. Hibernate中的HQL语言
  9. FreeImage使用方法
  10. 华为机试 - HJ10 字符个数统计
  11. 降低网站跳出率9种方法
  12. 使用Eclipse编译运行MapReduce程序 Hadoop2.6.0_Ubuntu/CentOS
  13. 一篇很哇塞的MyBatis入门到精通
  14. LeetCode-Hot100-无重复字符的最长子串
  15. 拉里·埃里森和历史上最牛的演讲【转】
  16. 2021年煤矿井下爆破考试内容及煤矿井下爆破考试资料
  17. 中创向心力:践行《国家职业教育改革实施方案》,积极推进职业教育改革
  18. 大型活动大规模人群的识别和疏散:从公交2.0到公交3.0
  19. Wolfram Mathematica学习笔记2
  20. php中平方代码_php 做出平方代码,用类来实现的接口,初学者请大侠们出手啊。...

热门文章

  1. linux 一个读写锁的使用异常导致的故障
  2. the database profile could not loaded. Check log for details
  3. 数据中心局部高热处理方案
  4. Jsp基本指令和动作
  5. Http client to POST using multipart/form-data
  6. 「leetcode」763.划分字母区间【贪心算法】详细图解
  7. 苹果mac文本处理软件:FSNotes
  8. indesign教程,了解图层
  9. 区块链开发(一)搭建基于以太坊的私有链环境
  10. SyncBird Pro的PhoneCare功能如何使用