LinkedHashMap就这么简单【源码剖析】

前言

声明,本文用得是jdk1.8

前面已经讲了Collection的总览和剖析List集合以及散列表、Map集合、红黑树还有HashMap基础了:

本篇主要讲解LinkedHashMap~

看这篇文章之前最好是有点数据结构的基础:

当然了,如果讲得有错的地方还请大家多多包涵并不吝在评论去指正~

一、LinkedHashMap剖析

LinkedHashMap数据结构图:

ps:图片来源网络,侵删~

首先我们来看看类继承图:

我简单翻译了一下顶部的注释(我英文水平渣,如果有错的地方请多多包涵~欢迎在评论区下指正)

从顶部翻译我们就可以归纳总结出HashMap几点:

  • 底层是散列表和双向链表
  • 允许为null,不同步
  • 插入的顺序是有序的(底层链表致使有序)
  • 装载因子和初始容量对LinkedHashMap影响是很大的~

同时也给我带了几个疑问:

  • access-ordered和insertion-ordered具体的使用和意思
  • 为什么说初始容量对遍历没有影响?

希望可以在看源码的过程中可以解决掉我这两个疑问~那接下来就开始吧~

1.1LinkedHashMap的域

1.2LinkedHashMap重写的方法

下面我列举就这两个比较重要的:

这就印证了我们的LinkedHashMap底层确确实实是散列表和双向链表~

  • 在构建新节点时,构建的是LinkedHashMap.Entry 不再是Node.

1.3构造方法

可以发现,LinkedHashMap有5个构造方法

下面我们来看看构造方法的定义是怎么样的:

从构造方法上我们可以知道的是:LinkedHashMap默认使用的是插入顺序

1.4put方法

原本我是想要找put方法,看看是怎么实现的,后来没找着,就奇了个怪~

再顿了一下,原来LinkedHashMap和HashMap的put方法是一样的!LinkedHashMap继承着HashMap,LinkedHashMap没有重写HashMap的put方法

所以,LinkedHashMap的put方法和HashMap是一样的。

如果没看过HashMap就是这么简单【源码剖析】的同学,可进去看看~

当然了,在创建节点的时候,调用的是LinkedHashMap重写的方法~

1.5get方法

get方法也是多了:判断是否为访问顺序~~~

讲到了这里,感觉我们可以简单测试一波了:

首先我们来看看已插入顺序来进行插入和遍历:

    public static void insertOrder() {

        // 默认是插入顺序
        LinkedHashMap<Integer,String>  insertOrder = new LinkedHashMap();

        String value = "关注公众号Java3y";
        int i = 0;

        insertOrder.put(i++, value);
        insertOrder.put(i++, value);
        insertOrder.put(i++, value);
        insertOrder.put(i++, value);
        insertOrder.put(i++, value);

        //遍历
        Set<Integer> set = insertOrder.keySet();
        for (Integer s : set) {
            String mapValue = insertOrder.get(s);
            System.out.println(s + "---" + mapValue);
        }
    }

测试一波:

接着,我们来测试一下以访问顺序来进行插入和遍历:

    public static void accessOrder() {
        // 设置为访问顺序的方式
        LinkedHashMap<Integer,String> accessOrder = new LinkedHashMap(16, 0.75f, true);

        String value = "关注公众号Java3y";
        int i = 0;
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);


        // 遍历
        Set<Integer> sets = accessOrder.keySet();
        for (Integer key : sets) {
            String mapValue = accessOrder.get(key);
            System.out.println(key + "---" + mapValue);
        }

    }

代码看似是没有问题,但是运行会出错的!

前面在看源码注释的时候我们就发现了:在AccessOrder的情况下,使用get方法也是结构性的修改

为了简单看出他俩的区别,下面我就直接用key来进行看了~

以下是访问顺序的测试

    public static void accessOrder() {
        // 设置为访问顺序的方式
        LinkedHashMap<Integer,String> accessOrder = new LinkedHashMap(16, 0.75f, true);

        String value = "关注公众号Java3y";
        int i = 0;
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);


        // 访问一下key为3的元素再进行遍历
        accessOrder.get(3);

        // 遍历
        Set<Integer> sets = accessOrder.keySet();
        for (Integer key : sets) {

            System.out.println(key );
        }

    }

测试结果:

以下是插入顺序的测试(代码就不贴了,和上面几乎一样):

我们可以这样理解:最常用的将其放在链表的最后,不常用的放在链表的最前~

这个知识点以我的理解而言,它这个访问顺序在LinkedHashMap如果不重写用处并不大~它是用来给别的实现进行扩展

  • 因为最常被使用的元素再遍历的时候却放在了最后边,在LinkedHashMap中我也没找到对应的方法来进行调用~
  • 一个removeEldestEntry(Map.Entry<K,V> eldest)方法,重写它可以删除最久未被使用的元素!!
  • 还有一个是afterNodeInsertion(boolean evict)方法,新增时判断是否需要删除最久未被使用的元素!!

去网上搜了几篇资料,都是讲LRUMap的实现的(也就是对LinkedHashMap进行扩展),有兴趣的同学可参考下列链接:

1.6remove方法

对于remove方法,在LinkedHashMap中也没有重写,它调用的还是父类的HashMap的remove()方法,在LinkedHashMap中重写的是:afterNodeRemoval(Node<K,V> e)这个方法

当然了,在remove的时候会涉及到上面重写的方法:

1.7遍历的方法

Set<Map.Entry<K,V>> entrySet()是被重写的了

看到了这里,我们就知道为啥注释说:初始容量对遍历没有影响

因为它遍历的是LinkedHashMap内部维护的一个双向链表,而不是散列表(当然了,链表双向链表的元素都来源于散列表)

二、总结

LinkedHashMap比HashMap多了一个双向链表的维护,在数据结构而言它要复杂一些,阅读源码起来比较轻松一些,因为大多都由HashMap实现了..

阅读源码的时候我们会发现多态是无处不在的~子类用父类的方法,子类重写了父类的部分方法即可达到不一样的效果!

  • 比如:LinkedHashMap并没有重写put方法,而put方法内部的newNode()方法重写了。LinkedHashMap调用父类的put方法,里面回调的是重写后的newNode(),从而达到目的!

LinkedHashMap可以设置两种遍历顺序:

  • 访问顺序(access-ordered)
  • 插入顺序(insertion-ordered)
  • 默认是插入顺序的

对于访问顺序,它是LRU(最近最少使用)算法的实现,要使用它要么重写LinkedListMap的几个方法(removeEldestEntry(Map.Entry<K,V> eldest)afterNodeInsertion(boolean evict)),要么是扩展成LRUMap来使用,不然设置为访问顺序(access-ordered)的用处不大~

LinkedHashMap遍历的是内部维护的双向链表,所以说初始容量对LinkedHashMap遍历是不受影响的

参考资料:

  • 《Core Java》
  • https://blog.csdn.net/zxt0601/article/details/77429150
  • https://blog.csdn.net/panweiwei1994/article/details/76555359
  • https://zhuanlan.zhihu.com/p/28216267
  • https://blog.csdn.net/fan2012huan/article/details/51097331
  • https://www.cnblogs.com/chinajava/p/5808416.html

明天要是无意外的话,可能会写TreeMap,敬请期待哦~~~~

文章的目录导航:https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang

原文发布于微信公众号 - Java3y(java3y)

原文发表时间:2018-04-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏微信公众号:Java团长

Java知识点集锦

答:不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primi...

1041
来自专栏http://www.cnblogs.com

time&datetime模块详解

 一.time模块 1.时间格式转换图: ? 2.time模块中时间表现的格式主要有三种:   a、timestamp时间戳,时间戳表示的是从1970年1月1日...

3919
来自专栏余林丰

Effective Java通俗理解(上)

  这篇博客是Java经典书籍《Effective Java(第二版)》的读书笔记,此书共有78条关于编写高质量Java代码的建议,我会试着逐一对其进行更为通俗...

2567
来自专栏阿杜的世界

【转】Java知识点集锦(1~40)

答:不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primi...

992
来自专栏卡少编程之旅

Javascript一些优雅实现

35311
来自专栏JMCui

读书笔记 之《Thinking in Java》(对象、集合、异常)

一、前言:     本来想看完书再整理下自己的笔记的,可是书才看了一半发现笔记有点多,有点乱,就先整理一份吧,顺便复习下前面的知识,之后的再补上。     真的...

3658
来自专栏肖洒的博客

前端入门学习--JavaScript

大概了解了HTML和CSS,到了前端的精华JavaScript。 学习笔记,ALL FROM 廖雪峰的官方网站

992
来自专栏WeaponZhi

轻松初探Python(六)—函数

这是「AI 学习之路」的第 6 篇,「Python 学习」的第 6 篇 题外话 这周工作日 5 天,我并没有更新文章,但大家并不要以为小之懒惰了。正好相反,自从...

3537
来自专栏钟绍威的专栏

Properties+重温Map+本地计数器Map方法Properties的方法用Properties的好处

昨天想写一个记账本,发现并不能把项目名称与内容关联起来,于是乎我想到了map,可是又不知道map储存到文件中又怎么读出来,幸好今天遇到了properties ...

1967
来自专栏Java帮帮-微信公众号-技术文章全总结

Java面试系列6

清明即事 帝里重清明, 人心自愁思。 车声上路合, 柳色东城翠。 花落草齐生, 莺飞蝶双戏。 空堂坐相忆, 酌茗聊代醉。 1.String是最基本的数据类型吗...

2665

扫码关注云+社区