众所周知 HashMap 是一个无序的 Map,因为每次根据 keyhashcode 映射到 Entry 数组上,所以遍历出来的顺序并不是写入的顺序。

因此 JDK 推出一个基于 HashMap 但具有顺序的 LinkedHashMap 来解决有排序需求的场景。

它的底层是继承于 HashMap 实现的,由一个双向链表所构成。

LinkedHashMap 的排序方式有两种:

  • 根据写入顺序排序。
  • 根据访问顺序排序。

其中根据访问顺序排序时,每次 get 都会将访问的值移动到链表末尾,这样重复操作就能的到一个按照访问顺序排序的链表。

数据结构

    @Testpublic 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 只是定义了改方法,具体实现交给了 LinkedHashMapinit();}

可以看到里面有一个空的 init(),具体是由 LinkedHashMap 来实现的:

    @Overridevoid 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 instructedEntry<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

转载于:https://www.cnblogs.com/crossoverJie/p/9321480.html

LinkedHashMap 底层分析相关推荐

  1. Android底层隐私数据,Android Intent传递数据底层分析详细介绍_Android_脚本之家

    Android  Intent传递数据底层分析详细介绍 我们知道在Activity切换时,如果需要向下一个ActivityB传递数据,可以借助Intent对象的putExtra方法. 但是不知各位有没 ...

  2. 【Python基础避坑】函数内存底层分析,全局变量/局部变量,参数传递,浅拷贝/深拷贝

    老高说,基本功不扎实会在工作中遇到很多的坑,非常同意- 函数定义示例 1.含有返回值 # -*-coding:utf-8-*- def add(a, b):'''两数相加'''sum = a + br ...

  3. C++11 std::shared_ptr的std::move()移动语义底层分析

    std::shared_ptr的std::move()移动语义底层分析 执行std::move()之前: 执行std::move()之后: 结论:一个浅拷贝 sizeof(std::shared_pt ...

  4. android中intent放数据类型,Android Intent传递数据底层分析详细介绍

    Android  Intent传递数据底层分析详细介绍 我们知道在Activity切换时,如果需要向下一个ActivityB传递数据,可以借助Intent对象的putExtra方法. 但是不知各位有没 ...

  5. String的底层分析 (学习笔记)

    StringTable底层分析 String的基本特性 StringPool String的内存分配 字符串的拼接操作 拼接效率的对比 intern()的理解 new String("&qu ...

  6. android 4.4 电池电量管理底层分析(C\C++层)

    参考文献:http://blog.csdn.net/wlwl0071986/article/details/38778897 简介: Linux电池驱动用于和PMIC交互.负责监听电池产生的相关事件, ...

  7. ①、iOS-RAC的开发用法-底层分析以及总结

    iOS RAC系列 ①.iOS-RAC的开发用法-底层分析以及总结 ②.iOS-RAC-核心类分析-RACPassthroughSubscriber订阅者-RACScheduler调度者-RACDis ...

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

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

  9. 从底层分析c和类c语言

    转自:http://blog.csdn.net/jggyyhh/article/details/50429886?locationNum=5&fps=1 要想深入理解C语言就不得不要知道几个知 ...

  10. 音视频开发—抖音GsyVideoPlayer视频底层分析使用

    theme: channing-cyan 一.初始化分析 1.初始化主要是构造了一个StandardGSYVideoPlayer 2.设置全屏布局的容器 代码如下: listVideoUtil = n ...

最新文章

  1. Coursera Machine Learning 作业提交问题
  2. Eclipse 代码风格配置
  3. 前端学习(693):for循环案例之求出偶数奇数之和
  4. 文章用图的修改和排版
  5. SSDTShadow Hook的实现,完整代码。可编译
  6. 从零基础到精通的前端学习路线
  7. 一道把递归、链表、引用、双指针都结合的题——回文链表
  8. 题目13 在O(1)时间删除链表节点
  9. ncl 添加点shp文件_NCL绘制中国地图
  10. t480 拆触摸板_ThinkPad T480 如何关闭触摸板?
  11. easysysprep4封装教程,自己封装系统
  12. 上海宝付谈谈故宫瘫痪,程序员怎么办
  13. 计算机组装有哪些,教你组装电脑主要配置有哪些
  14. win10连不上网,几种尝试
  15. 微内核相对于单内核优势之我见
  16. Cadence Allegro打阵列过孔方法图文教程及视频演示
  17. elasticsearch2.4.2安装(2) --插件marvel
  18. SSO单点登录-基于cookie的单点登录
  19. intouch中DA server的配置文件
  20. nslookup blog.csdn.net Can't resolve blog.csdn.net

热门文章

  1. Android图片控件,跟随列表(recyclerView)的上下滚动而同步平移。
  2. 蓝鳍鱼乐岿彦演讲实录:VR面临的机遇和挑战
  3. 《JavaScript开发框架权威指南》——2.4 处理任务
  4. 《Linux菜鸟入门2》访问网络文件系统
  5. 2016数据库考试题
  6. 两个框架之间的数据类型转换,需要桥接技术
  7. jquery取复制函数注意点
  8. Centos6.3下rsync+sersync安装配置
  9. Java集合详解2:LinkedList和Queue
  10. 创建数据库和表的SQL语句