业务开发算法 50 讲
黄清昊
Hashdata 数据库内核工程师,LeetCode 高赞答主,公众号微扰理论作者
23292 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
业务开发算法 50 讲
15
15
1.0x
00:00/00:00
登录|注册

05|HashMap:一个优秀的散列表是怎么来的?

减少哈希冲突
容量翻倍
空间和时间效率平衡
默认值 0.75
负载因子和阈值
扩容机制
动态扩容
处理冲突
插入键值对
异或和右移操作优化
hashCode() 方法
红黑树(链表过长时)
开链法
高效查找和插入
将键映射到数组下标
讨论负载因子的修改时机
ConcurrentHashMap
扩容策略
负载因子
resize 方法
putVal 方法
散列函数
处理哈希冲突
散列函数
遍历文档,统计单词出现频率
使用 HashMap
基于键访问元素
存储键值对
课后作业
线程安全
性能优化
JDK 实现
HashMap 设计思路
统计单词次数
关联式容器
HashMap 分析

该思维导图由 AI 生成,仅供参考

你好,我是微扰君。
过去四讲我们学习了 STL 中全部的序列式容器,数组、链表、队列、栈;今天来谈一谈另一类容器,关联式容器。所谓“关联式”,就是存储数据的时候,不只是存储元素的值本身,同时对要存储的元素关联一个键,形成一组键值对。这样在访问的时候,我们就可以基于键,访问到容器内的元素。
关联式容器本身其实是 STL 中的概念,其他高级语言中也有类似的概念。我们这次就以 JDK 为例,讲解几种关联式容器的原理和实现。

统计单词次数

我们就从一个实际需求讲起。现在有一篇很长的英文文档,如果希望统计每个单词在文档中出现了多少次,应该怎么做呢?
如果熟悉 HashMap 的小伙伴一定会很快说出来,我们开一个 HashMap,以 string 类型为 key,int 类型为 value;遍历文档中的每个单词 word ,找到键值对中 key 为 word 的项,并对相关的 value 进行自增操作。如果该 key= word 的项在 HashMap 中不存在,我们就插入一个 (word,1) 的项表示新增。
这样每组键值对表示的就是某个单词对应的数量,等整个文档遍历完成,我们就可以得到每个单词的数量了。用 Java 语言实现这个逻辑也不难。
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
String doc = "aaa bbb ccc aaa bbb ccc ccc bbb ccc ddd";
String[] words = doc.split(" ");
for (String s : words) {
if (!map.containsKey(s)) {
map.put(s, 1);
} else {
map.put(s, map.get(s) + 1);
}
}
System.out.println(map);
}
}
但是 HashMap 是怎么做到高效统计单词对应数量的?它设计思路的核心究竟是什么呢?这个问题非常有意思,我们一起来思考一下。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入剖析了HashMap的设计思路和核心原理,通过对比使用数组和HashMap的不同实现方式,引出了HashMap的优化思路。文章介绍了如何利用HashMap统计文档中单词出现的次数,并提出了将单词映射为26进制数的优化方法,大大提高了统计效率。在JDK中,HashMap的实现采用了简洁的散列函数,结合了对hashcode的计算和位运算的处理,以及开链法来处理哈希冲突,保证了散列表的高效性和稳定性。此外,文章还对HashMap的resize操作进行了详细解析,说明了其扩容机制和负载因子的作用。总的来说,本文通过对HashMap的实际应用和JDK的实现进行深入剖析,为读者提供了全面的了解和实践价值。读者可以从中学习到如何手写数据结构统计单词的数量,以及在实际工程中如何使用HashMap或者ConcurrentHashMap来处理散列表的相关问题。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《业务开发算法 50 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(10)

  • 最新
  • 精选
  • Paul Shan
    负载因子定义了扩容时机,在容量和冲突数目取得均衡,一般默认值适合多数情况。个人猜测hashmap用在内存较小的情况下(例如嵌入式系统)应该调整loadfactor,因为这个时候的环境就不是通常环境,需要增加冲突来降低存储。

    作者回复: 是的 如果我们内存非常有限的时候,就要将loadfactor提高

    2021-12-21
    6
  • 吴钩
    我的理解是:load factor是时间空间权衡的经验参数,当我们明确有时间空间的侧重点时可以改。比如空间不是问题,get的场景多且性能要求高,put的扩容时间损失可以接受,就可以调低load factor,让HashMap多多扩容。

    作者回复: 说得没错

    2021-12-30
    3
  • 假设 n=3 i=0 -> h = 31 * 0 + val[0] i=1 -> h = 31 * (31 * 0 + val[0]) + val[1] i=2 -> h = 31 * (31 * (31 * 0 + val[0]) + val[1]) + val[2] h = 31*31*31*0 + 31*31*val[0] + 31*val[1] + val[2] h = 31^(n-1)*val[0] + 31^(n-2)*val[1] + val[2]

    作者回复: 是这么回事~

    2021-12-23
  • 复习 : 如果是2^n 那么只有最高位为1,所以 num & 2^n -1 也就是 低位全是1 就获取到了取余的数据 11 % 2 1011 & 0001 = 1

    作者回复: 理解正确

    2021-12-22
  • 31*i 可以直接转化为(i << 5)- i 看到这句就想起csapp第二章(应该是第二章)

    作者回复: 差不多 csapp是好书,值得反复阅读~

    2021-12-22
  • 毛小树
    为什么hashCode的计算选择31? 1. 一定要奇数。不能用偶数。 2. 素数不素数其实关系不大。 3. 用31是因为31=2^5-1。可以把乘法转换成开销更小的位移操作。提高效率。
    2022-06-19
    1
  • 拓山
    为什么是31? https://stackoverflow.com/questions/299304/why-does-javas-hashcode-in-string-use-31-as-a-multiplier/299748#299748 排名第一的回答给出了理论公式:31 * i == (i << 5) - i,但对工程实践意义不大。 要结合排名第二的回答: 【Goodrich and Tamassia computed from over 50,000 English words (formed as the union of the word lists provided in two variants of Unix) that using the constants 31, 33, 37, 39, and 41 will produce fewer than 7 collisions in each case. This may be the reason that so many Java implementations choose such constants.】 正如 Goodrich 和 Tamassia 指出的那样,如果你对超过 50,000 个英文单词(由两个不同版本的 Unix 字典合并而成)进行 hash code 运算,并使用常数 31, 33, 37, 39 和 41 作为乘子,每个常数算出的哈希值冲突数都小于7个,所以在上面几个常数中,常数 31 被 Java 实现所选用也就不足为奇了。 可以看到31是乘子效果最好的最小值,因此选择31是理论+实践的完美数字
    2023-08-09归属地:浙江
  • A君
    哈希表目的是将巨大的稀疏的空间映射到小的连续空间,方法是散列化后再取模,遇到哈希冲突后,可以通过数组+链表的方式解决
    2023-02-07归属地:上海
  • SevenHe
    我有个疑问,当HashMap量足够大时,扩容所需的rehash开销可能会很大。如果为了保证HashMap的即时可用性,是不是要先在另外开个线程里扩容,在没ready之前,原有容器还可以继续对外提供hash服务。
    2022-03-21
    2
  • 猫头鹰爱拿铁
    resize方法里都会执行lotail=e和hitail=e这个和lotail=lotail.next和hitail=hitail.next是不是等价
    2022-02-23
收起评论
显示
设置
留言
10
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部