【JavaSE】深入HashMap
文章目录
- 1. HashMap概述
- 2. 哈希冲突
- 3. 树化与退化
- 3.1 树化的意义
- 3.2 树的退化
- 4. 二次哈希
- 5. put方法源码分析
- 6. key的设计
- 7. 并发问题
参考
- 如何防止因哈希碰撞引起的DoS攻击_hashmap dos攻击_双子孤狼的博客-CSDN博客
- 为什么 HashMap 要用 h^(h >>>16) 计算hash值?槽位数必须是 2^n?_一行Java的博客-CSDN博客
- HashMap面试,看这一篇就够了_苦味代码的博客-CSDN博客
1. HashMap概述
HashMap
是Java中最常用的集合框架。
在JDK1.7的时候,HashMap
的底层由数组和链表组成。数组是HashMap
的主体,而链表是为了解决哈希冲突而存在的。
在JDK1.8的时候,HashMap
的底层由数组、链表和红黑树组成。红黑树的出现是为了解决因哈希冲突导致的链表长度过长影响HashMap
性能的问题。红黑树搜索的空间复杂度为O(logn)
,而链表却是O(n)
。
也就是当链表的长度达到一定长度后,链表就会进行树化,当然这是一种笼统说法,具体细节待会深究。
2. 哈希冲突
哈希冲突是指对不同的关键字通过一个哈希函数进行计算得出相同的哈希值,这样使得它们存在的数组时候发生了冲突。
解决哈希冲突通常有以下四种方法。
- 开放定址法
开放定址法,也称为再散列地址法。基本思想就是,如果p=H(key)
出现冲突时,则以p
为基础,再次hash
,p1=H(p)
,如果p1
再次出现冲突,则以p1
为基础,以此类推,直到找到一个不冲突的哈希地址pi
。
就是说当发生哈希冲突的时候,对哈希值进行求哈希值,只要哈希表足够大,那么总能找到一个这样的空地址。
因此开放定址法所需要的hash表的长度要大于等于所需要存放的元素。
- 再哈希法
再哈希法,也称双重散列,多重散列。基本思想就是提供多个不同的哈希函数,当R1=H1(key1)
发生冲突时,再计算R2=H2(key1)
,直到没有冲突为止。这样做虽然不易产生堆集,但增加了计算的时间。
- 链地址法
链地址法,也称拉链法,将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。
- 建立公共溢出区
将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。
而HashMap
采用的就是链地址法。
并且JDK1.7和JDK1.8的链表插入节点时,采用的方式不一样
- JDK1.7采用的是头插法。
- JDK1.8采用的是尾插法。
3. 树化与退化
树化是指将链表转化为红黑树的过程。
在JDK1.8的HashMap
中,树化的规则是这样的:当链表长度超过树化阈值 8 时,先尝试扩容来减少链表长度,如果数组容量已经 >=64,才会进行树化
所以,HashMap
并不是一开始就进行树化的。
阈值设置为8主要是因为泊松分布,具体原因HashMap
作者在源码中也有解释
意思就是说,理想情况下使用随机的哈希码,容器中节点分布在 hash 桶中的频率遵循泊松分布,按照泊松分布的计算公式计算出了桶中元素个数和概率的对照表,可以看到链表中元素个数为 8 时的概率已经非常小,再多的就更少了,所以原作者在选择链表元素个数时选择了 8,是根据概率统计而选择的。
也就是说哈希 值如果足够随机,则在 哈希表内按泊松分布,在负载因子 0.75 的情况下,长度超过 8 的链表出现概率是 0.00000006,树化阈值选择 8 就是为了让树化几率足够小。
3.1 树化的意义
首先,树化成红黑树可以避免DOS攻击,防止链表超长时性能下降,树化应当是偶然情况,是保底策略。
DOS攻击是指恶意的攻击者通过使用以下精心构造的数据,使得所有数据经过哈希函数之后,都映射到了一个位置,导致了大量数据出现了哈希冲突,通过
HashMap
查询的效率从O(1)
变成了O(n)
。这样就有可能发生因为查询操作消耗大量CPU
或者线程资源,而导致系统无法响应其他请求的情况,从而达到拒绝服务攻击(DoS
)的目的。
其次,哈希表的查找,更新的时间复杂度是 O(1)
,而红黑树的查找,更新的时间复杂度是 O(log2n)
。
但是由于TreeNode
占用空间也比普通 Node
的大,如非必要,尽量还是使用链表。
3.2 树的退化
树的退化主要是发生在这两种情况下
HashMap
在扩容时,如果拆分树,树元素个数小于等于6,则会退化成链表remove
树节点时,若root
、root.left
、root.right
、root.left.left
有一个为 null ,也会退化为链表
4. 二次哈希
在JDK1.8的源码的put
方法中,会将key
进行二次哈希
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
通过源码可以了解到,首先会调用key
对象的hashCode
方法进行求哈希值,然后将该哈希值的低16位与高16位进行异或运算,这样做的目的是为了综合高位数据,让哈希分布更为均匀,减少哈希碰撞。
并且这么做可以在数组 table 的 length 比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。
操作 | 值 |
---|---|
hashCode() | 1,794,106,052 |
二进制 | 01101010 11101111 11100010 11000100 |
h >>> 16 | 00000000 00000000 01101010 11101111 |
5. put方法源码分析
查看put
方法源码
transient Node<K,V>[] table;public V put(K key, V value) {// 调用上文我们已经分析过的hash方法return putVal(hash(key), key, value, false, true);
}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)// 第一次put时,会调用resize进行桶数组初始化n = (tab = resize()).length;// 根据数组长度和哈希值相与来寻址,原理上文也分析过if ((p = tab[i = (n - 1) & hash]) == null)// 如果没有哈希碰撞,直接放到桶中tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))// 哈希碰撞,且节点已存在,直接替换e = p;else if (p instanceof TreeNode)// 哈希碰撞,树结构e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {// 哈希碰撞,链表结构for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// 链表过长,转换为树结构if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(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;
}
put
方法的大体思路如下
- 调用
key
的hashCode
方法计算哈希值,并据此计算出数组下标index - 如果发现当前的桶数组为
null
,则调用resize()
方法进行初始化 - 如果没有发生哈希碰撞,则直接放到对应的桶中
- 如果发生哈希碰撞,且节点已经存在,就替换掉相应的
value
- 如果发生哈希碰撞,且桶中存放的是树状结构,则挂载到树上
- 如果碰撞后为链表,添加到链表尾,如果链表超度超过
TREEIFY_THRESHOLD
默认是8,则将链表转换为树结构 - 数据put完成后,如果
HashMap
的总数超过threshold
就要resize
put
方法中比较重要的一个知识点莫过于计算索引了。
- 首先,先调用
hash
方法。- 在
hash
方法中,计算对象的hashCode
方法 - 再进行调用
HashMap
的hash()
方法进行二次哈希
- 在
- 接着,将
hash
方法返回的二次哈希值记为hash
,用(n - 1)
也就是数组长度减一对hash
进行与运算((n - 1) & hash
)
这里使用n-1
是因为以默认数组长度16为例子,那么数组下标为0-15,哈希值计算hash%(2^4)
,其本质就是和长度取余。也就等价于 (2^4 - 1) & hash
在JDK1.8和JDK1.7中,它们的put
方法实现有所不同
-
链表插入节点时,1.7 是头插法,1.8 是尾插法
-
1.7 是大于等于阈值且没有空位时才扩容,而 1.8 是大于阈值就扩容
-
1.8 在扩容计算 Node 索引时,会优化
6. key的设计
HashMap
的 key
可以为 null
,但 Map
的其他实现就不一定了
其key
应当符合下面的要求
- 作为
key
的对象,必须实现hashCode
和equals
,并且key
的内容不能修改(不可变) - key 的
hashCode
应该有良好的散列性
7. 并发问题
- 扩容死链(1.7 会存在)
void transfer(Entry[] newTable, boolean rehash) {int newCapacity = newTable.length;for (Entry<K,V> e : table) {while(null != e) {Entry<K,V> next = e.next;if (rehash) {e.hash = null == e.key ? 0 : hash(e.key);}int i = indexFor(e.hash, newCapacity);e.next = newTable[i];newTable[i] = e;e = next;}}
}
e
和next
都是局部变量,用来指向当前节点和下一个节点- 线程1(绿色)的临时变量
e
和next
刚引用了这俩节点,还未来得及移动节点,发生了线程切换,由线程2(蓝色)完成扩容和迁移
- 线程2 扩容完成,由于头插法,链表顺序颠倒。但线程1 的临时变量 e 和 next 还引用了这俩节点,还要再来一遍迁移
- 第一次循环
- 循环接着线程切换前运行,注意此时 e 指向的是节点 a,next 指向的是节点 b
- e 头插 a 节点,注意图中画了两份 a 节点,但事实上只有一个(为了不让箭头特别乱画了两份)
- 当循环结束是 e 会指向 next 也就是 b 节点
- 第二次循环
- next 指向了节点 a
- e 头插节点 b
- 当循环结束时,e 指向 next 也就是节点 a
- 第三次循环
- next 指向了 null
- e 头插节点 a,a 的 next 指向了 b(之前 a.next 一直是 null),b 的 next 指向 a,死链已成
- 当循环结束时,e 指向 next 也就是 null,因此第四次循环时会正常退出
- 数据错乱(1.7,1.8 都会存在)
假设现在有两个线程A和B,这两个线程都分别将数据C和D放进HashMap
中,并且C和D的二次哈希值都一样。
线程A和线程B同时执行到检查有无哈希冲突的那一段代码。A和B检查均无发现有哈希冲突。
假设线程A比较快,于是线程A将tab[i]
指向数据C。
这时候线程B将tab[i]
指向数据D。
最终tab[i]
指向数据D,导致了数据C丢失
相关文章:
【JavaSE】深入HashMap
文章目录1. HashMap概述2. 哈希冲突3. 树化与退化3.1 树化的意义3.2 树的退化4. 二次哈希5. put方法源码分析6. key的设计7. 并发问题参考 如何防止因哈希碰撞引起的DoS攻击_hashmap dos攻击_双子孤狼的博客-CSDN博客 为什么 HashMap 要用 h^(h >>&#…...
华为机试题:HJ62 查找输入整数二进制中1的个数(python)
文章目录博主精品专栏导航知识点详解1、input():获取控制台(任意形式)的输入。输出均为字符串类型。1.1、input() 与 list(input()) 的区别、及其相互转换方法2、print() :打印输出。1、整型int() :将指定进制…...
代码随想录训练营一刷总结|
分为几个大部分: 数组 最先接触的部分,虽然说感觉是最简单的,但是需要掌握好基础,特别是小心循环。这里面需要再仔细看的就是螺旋矩阵那一块,其他的在后续刷的时候能用一种方法一次a就行。 链表 需要注意链表的基础…...
CSS中的几种尺寸单位
一、尺寸单位 CSS 支持多种尺寸单位,包括: px:像素,固定大小单位em:相对于当前元素字体大小的单位rem:相对于根元素(HTML)字体大小的单位%:相对于父元素的百分比单位vh…...
运维必会:ansible剧本(piaybook)
playbooks 概述以及实例操作 Playbooks 组成部分: Inventory Modules Ad Hoc Commands Playbooks Tasks: 任务,即调用模块完成的某些操作 Variables: 变量 Templates: 模板 Handlers: 处理器,由某时间触发执行的操作 Roles: 角色 YAML 介绍…...
活动星投票午间修身自习室制作在线投票投票制作网页
“午间修身自习室”网络评选投票_免费小程序投票推广_小程序投票平台好处手机互联网给所有人都带来不同程度的便利,而微信已经成为国民的系统级别的应用。现在很多人都会在微信群或朋友圈里转发投票,对于运营及推广来说找一个合适的投票小程序能够提高工…...
C#泛型:高级静态语言的效率利器
文章目录引入类型约束子类泛型常用的泛型数据结构前文提要: 💎超快速成,零基础掌握C#开发中最重要的概念💎抽丝剥茧,C#面向对象快速上手💎Winform,最友好的桌面GUI框架💎懂了委托&a…...
澳大利亚访问学者申请流程总结
澳大利亚访问学者申请需要注意些什么?下面知识人网小编整理澳大利亚访问学者申请流程总结。1、取得wsk英语成绩,现在都是先买票再上车了。2、联系外导,申请意向接收函(email)。3、向留学基金委CSC提出申请。4、获批后,申请正式邀请…...
cookie和Session的作用和比较
目录 什么是cookie cookie的工作原理 什么是session Session的工作原理 为什么会有session和cookie cookie和session如何配合工作 cookie和Session作用 什么是会话 什么是cookie cookie是web服务器端向我们客户端发送的一块小文件,该文件是干嘛的呢…...
测试员都是背锅侠?测试人员避“锅”攻略,拿走不谢
最近发生了一起生产事故,究其根源,事故本身属于架构或者需求层面需要规避的问题,测试人员的责任其实是非常小的,但实际情况是:相关测试人员因此承担了很大的压力,成为质量问题的“背锅侠”。 实际上&#…...
C++: C++模板<template>
C template content😊前言😁模板💕1、泛型编程😍2、函数模板😒2.1:函数模板概念👌2.2:函数模板的格式😘2.3:函数模板原理😁2.4:函数模…...
chmod命令详解
用法:chmod [选项]… 模式[,模式]… 文件… 或:chmod [选项]… 八进制模式 文件… 或:chmod [选项]… --reference参考文件 文件… Change the mode of each FILE to MODE. With --reference, change the mode of each FILE to that of R…...
状态机设计中的关键技术
⭐本专栏针对FPGA进行入门学习,从数电中常见的逻辑代数讲起,结合Verilog HDL语言学习与仿真,主要对组合逻辑电路与时序逻辑电路进行分析与设计,对状态机FSM进行剖析与建模。 🔥文章和代码已归档至【Github仓库…...
单片机开发---ESP32S3移植NES模拟器(二)
书接上文 《单片机开发—ESP32-S3模块上手》 《单片机开发—ESP32S3移植lvgl触摸屏》 《单片机开发—ESP32S3移植NES模拟器(一)》 暖场视频,小时候称这个为—超级曲线射门!!!!!&am…...
微信小程序nodej‘s+vue警局便民服务管理系统
本文首先介绍了设计的背景与研究目的,其次介绍系统相关技术,重点叙述了系统功能分析以及详细设计,最后总结了系统的开发心得在Internet高速发展的今天,我们生活的各个领域都涉及到计算机的应用,其中包括“最多跑一次”微信小程序的网络应用,在外国小程序的使用已经是很普遍的方…...
第18章 MongoDB $type 操作符教程
MongoDB $type 操作符 描述 在本章节中,咱们将继续讨论MongoDB中条件操作符 $type。 $type操作符是基于BSON类型来检索集合中匹配的数据类型,并return 结果。 MongoDB 中可以使用的类型如下表所示: 类型数字备注Double1 String2 Object3…...
【MySQL主从复制】快速配置
本文配置环境Windows和Linux。 windows主 Linux 从 一、主库配置 首先保证Linux和防火墙开启3306端口或关闭防火墙。 登录Mysql管理员账户: GRANT REPLICATION slave,reload,super ON *.* TO root@从库ip地址 IDENtIFIED BY root; flush privileges; 本地的mysql可以被:…...
Typescript - interface 关键字(通俗易懂的详细教程)
前言 简单来说,Interface 就是一种描述对象或函数的东西。 您可以把 interface 理解为形状,真实开发情况下,一个对象需要有什么样的属性,函数需要什么参数或返回什么样的值,数组应该是什么样子的,一个类和继…...
【计组】内存和总线
课程链接:深入浅出计算机组成原理_组成原理_计算机基础-极客时间 一、虚拟内存和内存保护 日常使用的操作系统下,程序不能直接访问物理内存。内存需要被分成固定大小的页(Page),再通过虚拟内存地址(Virtu…...
CUDA中的数学方法
CUDA中的数学方法 文章目录CUDA中的数学方法1. Standard FunctionsSingle-Precision Floating-Point FunctionsDouble-Precision Floating-Point Functions2. Intrinsic FunctionsSingle-Precision Floating-Point FunctionsDouble-Precision Floating-Point Functions参考手册…...
Elasticsearch基本概念和索引原理
一、Elasticsearch是什么? Elasticsearch是一个基于文档的NoSQL数据库,是一个分布式、RESTful风格的搜索和数据分析引擎,同时也是Elastic Stack的核心,集中存储数据。Elasticsearch、Logstash、Kibana经常被用作日志分析系统&…...
《NFL橄榄球》:堪萨斯城酋长·橄榄1号位
堪萨斯城酋长队(Kansas City Chiefs)是位于密苏里州堪萨斯城的职业美式橄榄球队;目前在全国橄榄球联盟隶属于美国橄榄球联合会(AFC)西区;其夏季训练营在威斯康星大学河瀑校区举行。 酋长队的前身是达拉斯得州佬队,这支…...
python+django在线教学网上授课系统vue
随着科技的进步,互联网已经开始慢慢渗透到我们的生活和学习中,并且在各个领域占据着越来越重要的部分,很多传统的行业都将面临着巨大的挑战,包括学习也不例外。现在学习竞争越来越激烈,人才的需求量越来越大࿰…...
二叉搜索树之AVL树
AVL树的概念二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种解决上…...
全栈自动化测试技术笔记(二):准备工作的切入点
自动化测试技术笔记(二):准备工作的切入点 上篇整理的技术笔记,聊了自动化测试的前期调研工作如何开展,最后一部分也提到了工作的优先级区分。 这篇文章,接上篇文章的内容,来聊聊自动化测试前期的准备工作࿰…...
57 长短期记忆网络(LSTM)【动手学深度学习v2】
57 长短期记忆网络(LSTM)【动手学深度学习v2】 深度学习学习笔记 学习视频:https://www.bilibili.com/video/BV1JU4y1H7PC/?spm_id_fromautoNext&vd_source75dce036dc8244310435eaf03de4e330 长短期记忆网络(LSTM)…...
算法第十五期——动态规划(DP)之各种背包问题
目录 0、背包问题分类 1、 0/1背包简化版 【代码】 2、0/ 1背包的方案数 【思路】 【做法】 【代码】 空间优化1:交替滚动 空间优化2:自我滚动 3、完全背包 【思路】 【代码】 4、分组背包 核心代码 5、多重背包 多重背包解题思路1:转化…...
实现复选框全选和全不选的切换
今天,复看了一下JS的菜鸟教程,发现评论里面都是精华呀!! 看到函数这一节,发现就复选框的全选和全不选功能展开了讨论。我感觉挺有意思的,尝试实现了一下。 1. 全选、全不选,两个按钮ÿ…...
React hooks之useState用法(一)
系列文章目录 学习React已经有很长的一段时间了,今天决定重新回顾一下跟React相关的一些知识点 文章目录系列文章目录结构如下一、hooks是什么?useState可以能做什么二、如何使用useState()第一步:创建【函数组件&…...
spring的简单理解
目录 1 .ioc容器(控制反转) 2. Aop面向切面编程 3. 事务申明 4. 注解的方式启动 5. spring是什么与他的优势 6. 代理设计模式(比如aop) 7. springmvc中相应json数据 8. 使用lombok来进行对代码的简化 9. 使用logback记录…...
wordpress popuppress/产品推广软文
2019独角兽企业重金招聘Python工程师标准>>> 下面主要从队列、消息发送、消息接收方面了解消息传递过的一些可靠性处理。 1、队列 消费者是无法订阅或者获取不存在的MessageQueue中信息。消息被Exchange接受以后,如果没有匹配的Queue,则会被…...
wordpress模板+免费下载/网站改版公司哪家好
天,仔细阅读了园子里面的一个朋友写的《一缕阳光:DDD(领域驱动设计)应对具体业务场景,如何聚焦 Domain Model(领域模型)?》(http://www.cnblogs.com/xishuai/p/3800656.html)这篇博客…...
大城 网站建设/seo是什么化学名称
springcloud 总集:www.tapme.top/blog/detail… 前言 在第四篇和第五篇中提到一个叫关联 id的东西,用这个东西来将所有请求串起来,用来清晰的记录调用过程,以便以微服务的问题调试。 微服务虽然能够将单体软件系统分解为更小的、更…...
wordpress导航添加双语菜单/磁力兔子搜索引擎
步骤系列文章前言实现效果项目结构1.定义GSON实体类1.1gson包下建立一个Basic 类1.2gson包下建立一个AQI类1.3gson包下建立一个Now类1.4gson包下建立一个Suggestion类1.5gson包下建立一个Forecast类1.6gson包下建立一个Weather类2.编写天气界面2.1新建一个title.xml2.2新建一个…...
网站不支持m.域名/深圳发布最新通告
this的用法方法中全局环境中函数中事件中对象方法中显示函数绑定new运算符内部函数中上一篇关于 call()、apply() 和 bind() 方法的介绍中说明了:这三个方法都是重定义 this 的,所以在这里有必要总结一下 this ~this 就是一个指针,指向调用函…...
wordpress更改主机/优化公司排行榜
http://poj.org/problem?id3348 题目大意:用已给出的点围出面积最大的凸包,输出面积/50(向下取整) —————————————————————————— 第一道凸包?以及不知道第几次的奶牛题…… 显然裸题&#x…...