有序Map集合:LinkedHashMap和TreeMap该如何选用

news2024/9/22 3:50:32

文章目录

  • 前言
  • 一、为什么HashMap是无序的
  • 二、LinkedHashMap如何保证有序性
  • 三、TreeMap的底层原理
  • 四、LinkedHashMap和TreeMap比较
  • 总结


前言

为什么HashMap是无序的?有序的Map集合有哪些?LinkedHashMap和TreeMap都是有序的Map集合,他们有什么区别,该如何选用?LinkedHashMap和TreeMap的底层原理是什么?


一、为什么HashMap是无序的

要搞清楚HashMap为什么是无序的,首先我们要搞清楚HashMap的底层数据结构。很多人可能会说简单,HashMap的底层不就是链表散列结构,那什么是链表散列结构呢?
用更直白的话说就是数组+链表+红黑树(java8)

HashMap中存放的元素是根据key和hash(key)存放在数组、链表和红黑树中,而整个HashMap结构中并没有对每个元素的存放顺序做相关的记录,所以不能保证元素遍历顺序和存放顺序的一致性
可能有人会说,无论是数组、链表和红黑树都是有序的结构,为什么遍历顺序就不能和存放顺序保持一致。
其核心点是:HashMap会根据hash(key)和数组长进行取模获取bucketIndex确定元素存放的槽位,而取模算法的除数table.length会随着HashMap中元素个数的变化而进行的扩容而产生变化,这就导致根据hash(key)取模分配得到的槽位并不是固定的,从而导致了HashMap是遍历顺序和存放顺序的不一致

注意⚠️:
HashMap虽然不能保证元素遍历的顺序和存放顺序的一致性,但是多次遍历HashMap ,顺序不变。
在这里插入图片描述
简单说下向HashMap中存放一个新元素的步骤:
1、首先对key值进行hash计算
2、对hash(key)和数组长进行取模运算,得到bucketIndex。即根据key的hash值取模确认数据存放在哪个数组位上。
3、判断对应的数组槽位上是否有数据,如果没有数据则直接存放新数据,如果有数据则判断已有数据和新加入数据的key值是否相等。
4、数组对应槽位上的数据key值相等的话,直接更新value值。
5、数组对应槽位上的数据key值不相等的话,槽位上的是树节点则向树结构中添加新节点,槽位上的是非树节点则向链表中添加新节点。

相关源码说明:

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //1、通过位运算(n - 1) & hash取模,获取tab数值的index ,等价于hash%table.length
        if ((p = tab[i = (n - 1) & hash]) == null)
            //2、数组tab的index位为null,则直接存放新数据
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //3、数组tab的index位不为空,key值相等,则更新旧值的value值
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //4、数组tab的index位元素的key值不相等,且元素为树节点,则在树结构中添加新节点  
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            //5、数组tab的index位元素的key值不相等,且元素非树节点,则在链表中添加新节点
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

二、LinkedHashMap如何保证有序性

通过上面的分析我们知道HashMap为什么不能保证遍历顺序和存放顺序的一致性。核心原因是HashMap底层是通过数组+链表+红黑树的混合结构来保存元素,并没有引入额外的属性保证元素的存放顺序。
那么,我们是否可以引入一些有序的数据结构来保存HashMap元素的存放顺序呢?答案是可以的。
LinkedHashMap正是通过引入了链表结构来保存元素的存放顺序。

事实上LinkedHashMap是HashMap的直接子类,二者唯一的区别是LinkedHashMap在HashMap的基础上,采用双向链表(doubly-linked list)的形式将所有entry连接起来,这样是为保证元素的迭代顺序跟插入顺序相同。上图给出了LinkedHashMap的结构图,主体部分跟HashMap完全一样,多了header指向双向链表的头部(是一个哑元),该双向链表的迭代顺序就是entry的插入顺序。

除了可以保迭代历顺序,这种结构还有一个好处 : 迭代LinkedHashMap时不需要像HashMap那样遍历整个table,而只需要直接遍历header指向的双向链表即可,也就是说LinkedHashMap的迭代时间就只跟entry的个数相关,而跟table的大小无关。
在这里插入图片描述

LinkedHashMap中的节点说明:

 //继承了HashMap.Node
 static class Entry<K,V> extends HashMap.Node<K,V> {
        //当前节点的前一个节点before、后一个节点after
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
        ……  
 }         

三、TreeMap的底层原理

很多人喜欢将TreeMap和LinkedHashMap放在一起比较,误认为两种都能保证数据的有序性,这其实是错误的。
LinkedHashMap可以保证遍历顺序和存放顺序的一致性。
TreeMap由于元素插入的时候会根据key进行排序,所以并不能保证遍历顺序和存放顺序的一致性。

Java TreeMap实现了SortedMap接口,也就是说会按照key的大小顺序对Map中的元素进行排序,key大小的评判可以通过其本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator)

TreeMap底层通过红黑树(Red-Black tree)实现,也就意味着containsKey(), get(), put(), remove()都有着log(n)的时间复杂度。其具体算法实现参照了《算法导论》。

在这里插入图片描述

四、LinkedHashMap和TreeMap比较

  • 底层结构:LinkedHashMap底层还是沿用了HashMap的存储结构,核心区别在于节点属性中通过额外添加前后元素的属性,采用链表方式保存了元素存放的顺序。而TreeMap底层采用的是红黑树结构,存放的元素会根据key的大小顺序。
  • 有序性:LinkedHashMap能够保证遍历顺序和存放顺序的一致性,而TreeMap由于在存放元素的时候会根据Key值进行排序,所以不能保证遍历顺序和存放顺序的一致性。但两者都能保证多次遍历的顺序一致性。
  • 性能比较:LinkedHashMap无论是在新增、修改、删除还是根据根据Key值进行等值查找时,都有比TreeMap更好的性能,但是在根据Key值进行区间检索、范围查询时TreeMap更有优势。
  • 使用场景:HashMap是Key-Value数据结构的首选类型,性能最高,能满足绝大部分的场景,如果还需要保证集合的遍历和存放顺序的一致性可以采用LinkedHashMap,如果需要根据Key值顺序对集合内存放的元素进行排序则推荐采用TreeMap。

总结

本文主要是结合HashMap、LinkedHashMap和TreeMap的底层原理对3者的有序性进行了说明,并比较了3者在使用上的差异,大家可以根据项目中的实际业务场景来灵活选用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/61553.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能优化算法期末复习(更新ing)

目录 一、GA遗传算法 二、ACO蚁群算法 三、PSO粒子群算法 四、SA模拟退火算法 五、ABC人工蜂群算法 六、综合 一、GA遗传算法 1.运算流程 2.遗传算法适应值分配策略&#xff08;基于目标函数的直接分配、基于排名的分配&#xff09; 3.遗传算法在二进制问题&#xff08;如0…

Windows OpenGL ES 图像绿幕抠图

目录 一.OpenGL ES 图像绿幕抠图 1.原始图片2.效果演示 二.OpenGL ES 图像绿幕抠图源码下载三.猜你喜欢 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 基础 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 特效 零基础…

Java开发必须掌握的运维知识 (十)-- Docker集群自动化部署管理:Kubernetes快速入门

一、什么是Kubernetes Kubernetes(K8S)是Google在2014年发布的一个开源项目&#xff0c;用于自动化容器化应用程序的部署、扩展和管理。 Kubernetes通常结合docker容器工作&#xff0c;并且整合多个运行着docker容器的主机集群。 Kubernetes官网地址 二、Kubernetes相关特性 …

JavaWeb_第3章_HTTPTomcatServlet

JavaWeb_第3章_HTTP&Tomcat&Servlet 文章目录JavaWeb_第3章_HTTP&Tomcat&Servlet1&#xff0c;Web概述1.1 Web和JavaWeb的概念1.2 JavaWeb技术栈1.2.1 B/S架构1.2.2 静态资源1.2.3 动态资源1.2.4 数据库1.2.5 HTTP协议1.2.6 Web服务器1.3 Web核心课程安排2, HT…

vue中,页面布局之使用vue-splitpane实现窗格的拆分和调节,类似于flex布局

vue中&#xff0c;页面布局之使用vue-splitpane实现窗格的拆分和调节&#xff0c;类似于flex布局 1、基本介绍 npm地址&#xff1a;https://www.npmjs.com/package/vue-splitpane 安包 npm install vue-splitpane注册 main.js import splitPane from vue-splitpane // 注…

土豆清洗去皮机设计

目 录 摘 要 I Abstract II 1绪论 1 1.1选题背景及意义 1 1.2国内外研究现状 1 1.2.1国内外现状 2 1.2.2国外研究现状 2 1.3 发展趋势 3 1.4研究内容及方法 4 2毛刷式土豆清洗去皮机总体设计 5 2.1毛刷式土豆清洗去皮机的构造及工作原理 5 2.2 土豆擦皮机的相关计算 6 2.2.1设计…

动手学深度学习(1)—— 基础知识

文章目录一、基本概念1.1 关键组件数据模型目标函数优化算法1.2 各种机器学习问题监督学习无监督学习强化学习1.3 神经网络的特点二、预备知识2.1 数据操作入门运算符广播机制索引和切片节省内存转换为其他python 对象2.2 数据预处理读取数据集处理缺失的数据2.3 线性代数标量向…

WPS通过“文档部件”的“域”设置图、表和公式的自动序列号

写文档时&#xff0c;当有多张图片、多个表格和多个公式需要编号时&#xff0c;可以通过设置自动序列号&#xff0c;实现一定程度的自动编号和任意位置插入后随时更新序号。具体操作如下 ​ 1. 图的设置 在WPS中&#xff0c;首先设置好一张图片的格式后&#xff0c;对于需要设…

【并发】J.U.C线程池

线程池 经历了Java内存模型、JUC基础之AQS、CAS、Lock、并发工具类、并发容器、阻塞队列、atomic类后&#xff0c;我们开始JUC的最后一部分&#xff1a;线程池。 线程池的优势 为什么多线程会带来性能问题 多线程的性能问题&#xff0c;分为两类&#xff0c;一类是线程本身…

从今天起真正释放创造力——亚马逊云科技re:Invent

对于开发者而言&#xff0c;成就感来自于每一次敲下代码后可实现的创造力&#xff0c;而不是把时间和精力消耗在写千篇一律又无法复用的“胶水”代码&#xff0c;或是在越来越复杂的软件栈面前&#xff0c;疲惫地写业务流程并尽量减少Bug。更加不堪的是&#xff0c;有时仅仅是因…

2022.12.4 学习周报

文章目录摘要文献阅读1.题目2.摘要3.介绍4.方法5.实验5.1 数据集5.2 网络模型5.3 实验表现6.展望深度学习1.LSTM原理1.1 什么是LSTM&#xff1f;1.2 遗忘门&#xff08;forget gate&#xff09;1.3 输入门&#xff08;input gate&#xff09;1.4 输出门&#xff08;output gate…

Python声明式统计可视化库 altair-GitHub鉴赏官

推荐理由&#xff1a;Vega-Altair是 Python 的声明式统计可视化库。借助 Vega-Altair&#xff0c;您可以花更多时间来理解数据及其含义。Vega-Altair 的 API 简单、友好且一致&#xff0c;建立在强大的 Vega-Lite JSON 规范之上。这种优雅的简单性可以用最少的代码产生漂亮而有…

FT2004(D2000)开发实战之在线开发GPIO LED程序

一 在线开发GPIO LED程序 分析GPIO LED原理图 从飞腾FT2004原理图可知,开发板将GPIO1_A3和GPIO1_A4连接到LED上,具体如下: 开发板将GPIO1_A3和GPIO1_A4连接到LED上,因此代码初始化时需要注意以下几点: => 打开/dev/gpiochip1设备 => gpio偏移量为3和4创建led.c roo…

OpenShift 4 - 从 FreeIPA/RHIdM 向 RHSSO 同步用户和组

《OpenShift / RHEL / DevSecOps / Ansible 汇总目录》 说明&#xff1a;本文已经在 OpenShift 4.11 环境中验证 文章目录安装 FreeIPA/RHIdM在 FreeIPA/RHIdM 中添加用户和组从 FreeIPA/RHIdM 向 RHSSO 同步用户和组参考红帽 RHIdM 基于开源项目 FreeIPA&#xff0c;其内部提供…

Windows Access Token

Windows Access Token Windows Token其实叫Access Token(访问令牌)&#xff0c;它是一个描 述进程或者线程安全上下文的一个对象。不同的用户登录计算机后&#xff0c; 都会生成一个Access Token&#xff0c;这个Token在用户创建进程或者线程 时会被使用&#xff0c;不断的拷贝…

如何选择和使用腾讯云服务器的方法新手教程

本文将介绍如何选择和使用腾讯云服务器的方法新手教程。云服务器能帮助快速构建更稳定、安全的应用&#xff0c;降低开发运维的难度和整体IT成本。腾讯云CVM云服务器提供多种类型的实例、操作系统和软件包。各实例中的 CPU、内存、硬盘和带宽可以灵活调整&#xff0c;以满足应用…

C++11中可变参数模板使用

在看同事编写的代码&#xff0c;发现有如下的代码&#xff0c;因为没用过&#xff0c;所以查了一下这是什么语法&#xff0c;通过查询资料知道了这是C11中增加的可变参数模板。 template<class T, class ...Args> bool GetValue(T &value, Args &&...args) c…

12.2-12.4总结

12.2 明明跟着Vue3的视频学的&#xff0c;结果今天发现我学的都是vue2的写法&#xff0c;导致网上查阅资料调用组件有很多东西看不懂。设置langts也用不了&#xff0c;所以去查文档&#xff0c;增加配置了。 -安装vue-cli手脚架 解决lang"ts"报错http://t.csdn.cn/…

【力扣周总结】

题目汇总 判断括号的合法 #20 有效的括号&#xff08;栈问题 stack &#xff09;#921. 使括号有效的最少添加&#xff08;纯逻辑题&#xff09;#1541. 平衡括号字符串的最少插入次数&#xff08;纯逻辑题&#xff09; 单调(递减)栈 - Next Greater Element 题型 496. 下一个…

计算机毕业设计源码——基于Android的真人社交游戏辅助应用开发

编号 本科生毕业设计&#xff08;论文&#xff09; 题目&#xff1a; 真人社交游戏辅助应用开发 专业 学 号 学生姓名 指导教师 摘 要 时至今日&#xff0c;社交已成为人们生活中必不可少的一部分&#xff0c;社交网络的盛行已经成为一种必然趋势&#xff0e;与此同时&a…