【Java集合类】HashMap(二)- 设计要点

news2024/11/30 13:38:12

本章将开始探讨JDK中的HashMap,包括HashMap如何避免和解决上一章所说的散列冲突问题,以及Java 8对HashMap的改进

避免散列冲突- 散列函数设计

String.hashcode()

Object.hashCode()方法用于返回当前对象的散列值。Object类中也约定了,重写了equals也要重写hashcode相同对象必须有相同哈希码。

以常见的String类的hashcode()为例,会有一个固定值31,在for循环中会结合每一位字符的ASCII码计算出最终的hashcode

char val[] = value;
for (int i = 0; i < value.length; i++) {
      h = 31 * h + val[i];
}

这里String用的哈希函数是一个叫做DJBX33A算法的变种

这里的乘子用一个固定值31的好处:

  1. 之所以使用 31, 是因为他是一个奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算(低位补0)
  2. 31 有个很好的性能,即用移位和减法来代替乘法,可以得到更好的性能: 31 * i == (i << 5)- i, 现代的 VM 可以自动完成这种优化。这个公式可以很简单的推导出来。
  3. 另外,一些实验证明,31是个不大不小的质数,碰撞概率很小

因此,String类hashcode的实现兼顾了散列函数的随机性和性能

扰动函数HashMap.hash()

假如用String作为hashmap的key,计算出hashcode后,并没有直接根据数组长度取模,而是先将key传入扰动函数hash()

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

hashmap为什么使用扰动函数对hashcode的上半区和下半区异或:

  • 首先提一下,hashMap的长度必须是2的幂,这样hash%length就会转换为hash&(length-1),计算更加高效,
  • 而Java假设length一般很少超过2^16(65536),因此计算索引:hash&(length-1),hash值参与运算的部分一般只有低16位,导致高16位信息丢失,为了提高最终索引值随机性,让高16位也参与运算,扰动的办法就是hashcode高16位和低16位进行运算
  • 为什么用异或呢,因为用&或者|都会让结果趋向于0或1,反而不平均了
  1. **怎么验证随机性呢??**用实验的方式,比如准备10万个单词,分别用加扰动和不加扰动的方式,插入到一个长度为128(2次幂)的hashmap中,看看每个索引下对应的单词数量。如果所有单词。可以画出折线图看看,如果所有单词在每个索引下分配的比较平均,就说明随机性强。随机性强也是为了尽量减少hash碰撞。

避免散列冲突-负载因子

static final float DEFAULT_LOAD_FACTOR = 0.75f;

在HashMap中,负载因子决定了数据量多少了以后进行扩容。这里要提到上面做的HashMap例子,我们准备了7个元素,但是最后还有3个位置空余,2个位置存放了2个元素*。* 所以可能即使你数据比数组容量大时也是不一定能正正好好的把数组占满的,而是在某些小标位置出现了大量的碰撞,只能在同一个位置用链表存放,那么这样就失去了Map数组的性能

要选择一个合理的大小下进行扩容,默认值0.75就是说当阀值容量占了3/4时赶紧扩容,减少Hash碰撞

解决散列冲突 - 链表 + 红黑树

HashMap解决散列冲突的方法是上一章中提到的拉链法(链表法)

在Java 8之前,HashMap只使用了链表来解决哈希冲突。

但是,由于链表长度增长过长会导致查找效率变低,因此在Java 8中添加了红黑树的支持来解决这个问题。当链表长度超过8时,HashMap将使用红黑树代替链表,这样能够提高HashMap的性能。

如果红黑树中节点的数量降到一定数量以下(Java 8中为6个),它将退化为链表。这是因为红黑树节点包含额外的指针和颜色标记,当节点数较少时,这些额外开销可能会变得更加显著,而链表占用的空间更少,也更容易遍历。

这种自动调整的特性使得HashMap能够在不同负载下保持良好的性能和空间使用率。

Java8对HashMap的修改

这部分将尽可能解释每种修改的背后原因

数据结构的改变

前面也提到过:Java8之前的数据结构数组+链表,而Java8之后是数组+链表/红黑树,显然是对于性能的考虑

更简单的hash()方法

前面我们提到过扰动函数能够让最终的hash值更加随机,但实际上Java8之前的扰动函数hash()更加复杂:

static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);

关于这一点改变我目前也没有在权威资料中查到原因,不过这篇回答中对此进行了一个合理猜测:

https://stackoverflow.com/questions/24673567/change-to-hashmap-hash-function-in-java-8

简单来说就是个tradeoff:更复杂的hash()方法、更随机的散列值 vs 更高效的hash()方法、更好的性能

有可能是官方发现后者带来的好处更大,所以放弃了更为复杂的hash()方法

其实回答里也提到了:假如key的hashcode()方法已经能生成高质量的散列值(例如String),那hash()方法再进行复杂运算其实是在浪费时间

新增节点的插入位置

新增一个节点时,Java8之前采用的是头插法,之后采用的是尾插法

采用头插法最大的问题就是在并发时,扩容可能产生环形链表,导致get()方法产生死循环,采用尾插法可以解决

其实头插法有一个好处:根据局部性原理越晚插入的元素越有可能被访问,因此头部插入有助于后续访问效率的提升
但是,如果是一个容量很大,并且长期存在于硬盘上的数据结构,这种局部性原理的效率提升更为明显。内存中短期使用的小容量数据结构,反而不太能借助局部性原理提升效率。

扩容方法

扩容时最直接影响效率的问题,就是需要把元素迁移到新的桶位中。

首先注意,表中每个节点都会保存一个hash值,这个值是经过扰动函数处理之后、取模之前的值,扩容时会用到这个值重新计算下标

拆分元素的过程中,原jdk1.7中会需要重新计算哈希值:**hash&(length-1),但是到jdk1.8中已经进行优化,不再直接取模计算,提升了拆分的性能,设计的还是非常巧妙的
请添加图片描述

对31取模保留低5位,对15取模保留低4位,两者的差异就在于第5位是否为1,是的话则需要加上增量,为0的话则不需要改变

参考资料

面经手册 · 第3篇《HashMap核心知识,扰动函数、负载因子、扩容链表拆分,深度学习》 | bugstack 虫洞栈

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

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

相关文章

【消费战略方法论】认识消费者的恒常原理(一):消费者稳态平衡原理

“消费战略”是塔望咨询基于大量的战略与营销实践经验结合心理学、经济学、传播学等相关专业学科的知识应用进行提炼与创造形成的战略方法体系。消费战略强调以消费者为导向&#xff0c;进行企业、品牌战略、品牌营销的制订和落地&#xff0c;企业经营的每个环节和输出的每个动…

提取括号中的内容

正则能解决不嵌套的括号内容提取问题遇到一个问题&#xff0c;就是需要提取字符串中每一个中括号里的内容&#xff0c;在网上搜了一下&#xff0c;发现用正则表达式(\[[^\]]*\])可以提取中括号中的内容&#xff0c;以下面文本为匹配对象&#xff1a;PerformanceManager[第1个中…

【算法基础】一维差分 + 二维差分

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;【C/C】算法 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵 希望大佬指点一二 如果文章对你有…

#笨鸟先飞 猴博士电路笔记 第一篇 电路基础

第零课 基础知识串联与并联电源电势与电位差第一课 电阻电路的等效变换电压源串联电流源并联电压源和电流源串联电压源和电流源并联电压源转化为电流源电流源转化为电压源Δ-Y等效变换第二课 基尔霍夫定律基尔霍夫电流定律任一结点上流出电流之和等于流入电流之和。受控电流源&…

Java 集合 --- 如何遍历Map

Java 集合 --- 如何遍历MapMap的基本操作如何遍历MapType of HashMapMap没有继承Collection接口AbstractMap和AbstractCollection是平级关系 Map的基本操作 package map; import java.util.*; /*** This program demonstrates the use of a map with key type String and val…

case的使用

1.x和z值 1.1.定义 x&#xff1a;表示不定值 z&#xff1a;表示高阻态&#xff0c;还有一种表达方式“&#xff1f;” 一个x/z可以用来定义十六进制&#xff08;h&#xff09;数的4位二进制的状态&#xff0c;八进制&#xff08;o&#xff09;数的3位&#xff0c;二进制&#x…

「我有一剑可开天门」大厂面试真题,这边建议是直接开冲

前言 说一下&#xff0c;最新在重温雪中悍刀行这本小说&#xff0c;故此有了这么一个沙雕标题&#xff08;小声bb。这本书是真的好看&#xff09;&#xff0c;这套面试题是一个粉丝总结完发给我的&#xff0c;本意是想让我分享出来帮助到更多的人&#xff0c;我整理了一下&…

失眠时还在吃它?有风险,你了解过吗

失眠&#xff0c;是当代人的通病。所以解决失眠也成了刚需&#xff0c;市面上开始出现各种助眠产品。有商业机构调查发现&#xff0c;62%的90后消费者曾买过助眠产品&#xff0c;其中人气选手就是褪黑素。褪黑素本身就是人体天然存在的&#xff0c;与睡眠有关的物质&#xff0c…

第10天-商品服务 - 分层领域模型及规格参数编码实现

1.分层领域模型规约 DO&#xff08; Data Object&#xff09;&#xff1a; 此对象与数据库表结构一一对应&#xff0c;通过 DAO 层向上传输数据源对象。DTO&#xff08; Data Transfer Object&#xff09;&#xff1a;数据传输对象&#xff0c; Service 或 Manager 向外传输的…

VSCode远程连接服务器

工作使用服务器的jupyter&#xff0c;直到有一天服务器挂了&#xff0c;然而&#xff0c;代码还没有来得及备份。o(╥﹏╥)o VScode远程连接服务器&#xff0c;使用服务器的资源&#xff0c;代码可以存在本地&#xff0c;可以解决上述困境。 1.官网下载VSCode.网址https://cod…

JAVA并发集合之ConcurrentHashMap

ConcurrentHashMap是一个支持高并发更新与查询的哈希表(基于HashMap)。Hashmap在多线程并发的情况下&#xff0c;是线程不安全的&#xff0c;容易出现死循环、死锁等问题&#xff0c;JDK8后不会出现死锁问题&#xff0c;但依然存在多线程的系列问题&#xff0c;如&#xff1a;数…

数据结构_ 堆结构与堆排序(c++ 实现 + 完整代码 )

堆结构与堆排序 文章目录堆结构与堆排序引入堆堆结构所满足的数学特性准备代码----------- 往堆中插入元素----------- 删除堆顶堆排序构建完整代码及测试动态分配版本非动态版本引入堆 二叉树 具有左孩子与右孩子的最普通的二叉树。 满二叉树 特殊的二叉树&#xff1a;每个节…

sql join、left join、full join的区别总结,注意事项

1. 结论图 见 https://www.runoob.com/sql/sql-join.html 2. 测试 2.1. 造数据 数据表 mysql脚本 DROP TABLE IF EXISTS class; CREATE TABLE class (c_id INTEGER NOT NULL COMMENT 班级ID,c_name VARCHAR(100) NOT NULL COMMENT 班级名,PRIMARY KEY (c_id) ) CO…

JavaEE简单示例——动态SQL的复杂查询操作<foreach>

简单介绍&#xff1a; 在我们之前学习MySQL的时候&#xff0c;我们曾经有一个操作叫做查询区间&#xff0c;比如我们使用in关键字查询id为3到6之间的值&#xff0c;或者查询id小于100的值&#xff0c;这时候如果将SQL语句一条一条的查询出来进行筛选效率就太慢了&#xff0c;所…

【05-JVM面试专题-运行时数据区的结构都有哪些?哪些是共享的呢?哪些是非共享的呢?详细的介绍一下运行时数据区结构各部分的作用?】

运行时数据区的结构都有哪些&#xff1f;哪些是共享的呢&#xff1f;哪些是非共享的呢&#xff1f;详细的介绍一下运行时数据区结构各部分的作用&#xff1f; 运行时数据区的结构都有哪些&#xff1f;哪些是共享的呢&#xff1f;哪些是非共享的呢&#xff1f;详细的介绍一下运行…

带您了解TiDB MySQL数据库中关于日期、时间的坑

带您了解TiDB & MySQL数据库中关于日期、时间的坑时间的基础知识什么是时间计算时间的几种方法世界时&#xff08;UT&#xff09;协调世界时&#xff08;UTC&#xff09;国际原子时&#xff08;TAI&#xff09;时区的概念中国所在的时区操作系统的时区datetimedatectl数据库…

Spring代理模式——静态代理和动态代理

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

python打包exe实用工具auto-py-to-exe的操作方法

auto-py-to-exe操作方法auto-py-to-exe 是一个用于打包 python 程序的程序。本文就是主要介绍如何使用 auto-py-to-exe 完成 python 程序打包。本文主要分为两节&#xff0c;第一节主要对 auto-py-to-exe 做一些介绍&#xff0c;第二节则是演示 auto-py-to-exe 的打包过程。一、…

pygraphviz安装教程

0x01. 背景 最近在做casual inference&#xff0c;做实验时候想因果图可视化&#xff0c;遂需要安装pygraphviz&#xff0c;整了一下午&#xff0c;终于捣鼓好了&#xff0c;真头大。 环境&#xff1a; win10操作系统python3.9环境 0x02. 安装Graphviz 传送门&#xff1a;…

linux:本地套接字通信客户和服务器代码

客户端代码 #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <arpa/inet.h> #include <sys/un.h> int main(int argc, const cha…