LinkedHashMap如何保证有序问题

目录
  • LinkedHashMap如何保证有序
    • 属性信息
    • AccessOrder
    • newNode()
    • 按访问顺序有序
  • LinkedHashMap实现有序的原理

LinkedHashMap如何保证有序

我们常说linkedHashMap是有序的,这个有序也是分为两种的,分别是:插入顺序和访问顺序,我们可以通俗的认为:linkedHashMap = hashmap + 双向链表

以下的学习是基于jdk8

根据linkedHashMap的结构来看,是依赖于hashmap的,通过查看源码,我们也会发现,linkedHashMap只是维护了一个链表,并没有put、remove方法的具体实现,

因为linkedHashMap的设计思想是:对数据的操作,是依赖于hashmap中的方法,只是在其中的一些细节方法,linkedHashMap会进行扩展,接下来我们先说属性有哪些

属性信息

/**
     * The head (eldest) of the doubly linked list.
     * 链表的head节点
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * The tail (youngest) of the doubly linked list.
     * 链表的尾结点
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * The iteration ordering method for this linked hash map: <tt>true</tt>
     * for access-order, <tt>false</tt> for insertion-order.
     * false:表示插入顺序;true表示读取顺序
     * @serial
     */
    final boolean accessOrder;

AccessOrder

该属性是来区分当前linkedHashMap是按照访问顺序排序,还是按照put顺序有序,当该参数为true的时候,表示按照读取顺序有序;默认为false,按照put顺序有序

newNode()

在调用put方法的时候,会调用父类hashmap的put方法,那linkedHashMap如何维护节点之间的顺序呢?

在put方法中,如果得到当前key要存储的位置,会调用newNode()方法,初始化一个node节点,

linkedHashMap对该方法,进行了覆写,

/**
     * 在向map中put元素的时候,是会初始化一个node节点,如果是linkedHashMap,调用的是该方法
     * 在该方法中,可以看到,会初始化一个LinkedHashMap.Entry节点,然后将该节点插入到linkedHashMap的双向链表中
     * 默认是加到链表尾部
     * @param hash
     * @param key
     * @param value
     * @param e
     * @return
     */
    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;
    }

这里可以看到,除了初始化一个节点之外,还会调用linkNodeLast方法,在linkedNodeLast方法中,会将p节点添加到自己维护的双向链表的尾部

/**
     * 这是加入到链表尾部的方法
     * 如果当前是第一个put的元素,那p就是head,否则,就把节点放到tail的后面
     * @param p
     */
    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;
        }
    }

按访问顺序有序

假如在初始化linkedHashMap对象的时候,指定accessOrder为true,那表示按照访问顺序有序,在get方法中,会对访问到的元素进行处理

public V get(Object key) {
        Node<K,V> e;
        // 这里的getNode调用的是hashmap中的方法,获取到当前key所对应的value
        if ((e = getNode(hash(key), key)) == null)
            return null;
        /**
         * 如果是按照访问顺序有序,会把获取到的e节点插入到链表的尾部,然后返回e.value
         */
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

这里可以看到,如果是按照访问顺序有序的话,会额外调用afterNodeAccess()方法,如果为false,会直接返回获取到的节点value值,afterNodeAccess方法简单而言,就是将e节点移到链表的尾部,所以,我们可以认为,最近访问的在元素在链表的最后面

所以:对于按照put顺序有序的设置,在put元素的时候,本身就会把最新插入的元素放入到链表的尾部,如果是按照访问顺序有序的话,在get的时候,会把访问的元素移到链表的尾部,根据该特点,也可以实现一个简单的LRU算法

对于hashmap和linkedHashMap来说,最大的区别就是:hashmap是无序的,linkedHashMap自己维护了节点的顺序

LinkedHashMap实现有序的原理

LinkedHashMap采用的hash算法和HashMap相同,但是它重新定义了数组中保存的元素Entry,该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。

这样就能按照插入的顺序遍历原本无序的HashMap了,是不是很方便?

看源代码:

/**
 * 双向链表的表头元素。
 */
private transient Entry<K,V> header;  

/**
 * LinkedHashMap的Entry元素。
 * 继承HashMap的Entry元素,又保存了其上一个元素before和下一个元素after的引用。
 */
private static class Entry<K,V> extends HashMap.Entry<K,V> {
    Entry<K,V> before, after;
    ……
}  

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java中HashMap 中的一个坑

    目录 前言 问题展示 原因分析 解决方案 LinkedHashMap 的魔力 总结 前言 最近公司的系统要增加一个新的列表展示功能,功能本身难度并不大,但遇到了一个很“奇怪”的问题.小伙伴在执行查询列表时,明明已经使用了 order by 进行排序了,但最终查询出来的数据却还是乱的. 预期中的(正确)结果:   现实中的(非预期)结果:  那到底是哪里出现了问题呢? 问题展示 为了方便展示,我把复杂的业务程序简化成了以下代码: import java.util.HashMap; public c

  • Java使用LinkedHashMap进行分数排序

    分数排序的特殊问题 在java中实现排序远比C/C++简单,我们只要让集合中元素对应的类实现Comparable接口,然后调用Collections.sort();方法即可. 这种方法对于排序存在许多相同元素的情况有些浪费,明显即使值相等,两个元素之间也要比较一下,这在现实中是没有意义的. 典型例子就是学生成绩统计的问题,例如高考中,满分是150,成千上万的学生成绩都在0-150之间,平均一个分数的人数成百上千,这时如果排序还用传统方法明显就浪费了. 进一步思考 成绩既然有固定的分数等级,我们可

  • java HashMap,TreeMap与LinkedHashMap的详解

     java HashMap,TreeMap与LinkedHashMap的详解 今天上午面试的时候 问到了Java,Map相关的事情,我记错了HashMap和TreeMap相关的内容,回来赶紧尝试了几个demo理解下 package Map; import java.util.*; public class HashMaps { public static void main(String[] args) { Map map = new HashMap(); map.put("a", &

  • java Map接口子类HashMap遍历与LinkedHashMap详解

    目录 一.概述 二.Map常用子类 三.Map接口中的常用方法 四.Map集合遍历键找值方式 五.Entry键值对对象 六.Map集合遍历键值对方式 七.HashMap存储自定义类型键值 八.LinkedHashMap 九.Map集合练习 十.JDK9对集合添加的优化 一.概述 现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射.Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map接口

  • 详解Java LinkedHashMap与HashMap的使用

    目录 HashMap存储自定义类型键值 LinkedHashMap Map集合练习 JDK9对集合添加的优化 HashMap存储自定义类型键值 练习:每位学生(姓名,年龄)都有自己的家庭住址.那么,既然有对应关系,则将学生对象和家庭住址存储到map集合中.学生作为键, 家庭住址作为值. 注意,学生姓名相同并且年龄相同视为同一名学生. 编写学生类: public class Student { private String name; private int age; public Student

  • SpringCloud实战小贴士之Zuul的路径匹配

    不论是使用传统路由的配置方式还是服务路由的配置方式,我们都需要为每个路由规则定义匹配表达式,也就是上面所说的 path 参数.在Zuul中,路由匹配的路径表达式采用了Ant风格定义. Ant风格的路径表达式使用起来非常简单,它一共有下面这三种通配符: 通配符 说明 ? 匹配任意的单个字符 * 匹配任意数量的字符 ** 匹配任意数量的字符,支持多级目录 我们可以通过下表的示例来进一步理解这三个通配符的含义并参考着来使用: URL路径 说明 /user-service/? 它可以匹配 /user-s

  • SpringBoot整合Shiro的代码详解

    shiro是一个权限框架,具体的使用可以查看其官网 http://shiro.apache.org/  它提供了很方便的权限认证和登录的功能. 而springboot作为一个开源框架,必然提供了和shiro整合的功能!接下来就用springboot结合springmvc,mybatis,整合shiro完成对于用户登录的判定和权限的验证. 1.准备数据库表结构 这里主要涉及到五张表:用户表,角色表(用户所拥有的角色),权限表(角色所涉及到的权限),用户-角色表(用户和角色是多对多的),角色-权限表

  • spring boot整合Shiro实现单点登录的示例代码

    Shiro是什么 Shiro是一个Java平台的开源权限框架,用于认证和访问授权.具体来说,满足对如下元素的支持: 用户,角色,权限(仅仅是操作权限,数据权限必须与业务需求紧密结合),资源(url). 用户分配角色,角色定义权限. 访问授权时支持角色或者权限,并且支持多级的权限定义. Q:对组的支持? A:shiro默认不支持对组设置权限. Q:是否可以满足对组进行角色分配的需求? A:扩展Realm,可以支持对组进行分配角色,其实就是给该组下的所有用户分配权限. Q:对数据权限的支持? 在业务

  • SpringBoot集成Shiro进行权限控制和管理的示例

    shiro apache shiro 是一个轻量级的身份验证与授权框架,与spring security 相比较,简单易用,灵活性高,springboot本身是提供了对security的支持,毕竟是自家的东西.springboot暂时没有集成shiro,这得自己配. 1 . 添加依赖 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId

  • 基于java查找并打印输出字符串中字符出现次数

    今天在面试时遇到一道算法的题: 给定一个字符串,输出每次字符出现的次数:要求按照顺序输出: 自己的思路开始是: 1.把String转换char数组 2.直接去遍历数组,获取每个字符出现次数,遇到不同时候重新记录 3.把结果用StringBuffer拼接后输出 public class Record { public static void main(String[] args) { System.out.println("直接遍历数组的方法:"+compressStrArray(&qu

  • SpringBoot配置shiro安全框架的实现

    首先引入pom <!--SpringBoot 2.1.0--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.0.RELEASE</version> </parent> <!--shiro--> &l

  • springboot使用shiro-整合redis作为缓存的操作

    说在前面 本来的整合过程是顺着博客的顺序来的,越往下,集成的越多,由于之前是使用ehcache缓存,现在改为redis,限制登录人数 以及 限制登录次数等 都需要改动,本篇为了简单,目前先将这两个功能下线,配置暂时是注销的,原类保存,在下篇博客中改. 还有之前是使用SessionListener监听session创建来统计在线人数,在本篇中也将改为统计redis中的key数目. 如果是单机,使用ehcache是最快的,项目一般都不是单节点,为了方便之后使用sso单点登录,以及多节点部署,所以使用

  • Java中详细解析Map接口

    目录 Map详解: Map基本操作: hashMap原理: Put方法: Get方法: Map的遍历: TreeMap LinkedHashMap: 对比下Hashmap.Hashtable和ConcurrentHashmap: 总结 Map详解: 先看图,便于宏观了解Map的地位. Map接口中键和值一一映射. 可以通过键来获取值. 给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值. 当访问的值不存在的时候,方法就会抛出一个NoSuchElementEx

随机推荐