Java中的HashMap相信大家都不陌生,也是大家编程时最常用的数据结构之一,各种面试题更是恨不得掘地三尺的去问HashMap、HashTable、ConcurrentHashMap,无论面试题多么刁钻的问,只要我们真正的掌握了它的设计思想,便可以不变应万变,hold住所有的面试题了。

本文主要包含以下内容,力求深入浅出一步一步彻底明白HashMap的设计思想:

  1. 数组的优势
  2. 数组是特殊的键值对
  3. Hash函数
  4. Hash冲突
  5. 此时再看HashMap源码

文章干货内容较多,建议大家“收藏”后持续阅读!

关注“java架构设计”,阅读更多技术干货文章!

数组的优势

整型数组

上图是一个含有8个元素的整型数组,数组下标从0到7,

如果我们要获取第四个元素的值,直接a[3]就可以了,时间复杂度为O(1),

如果我们要将第四个元素的值替换为36,直接a[3]=36就可以了,时间复杂度也是O(1),

也就是说基于下标的随机访问和赋值数组元素的时间复杂度都是O(1),无论这个数组是多大(在内存充足的条件下),这是数组的优势之一。

数组不够,我们还需要键值对

通过上面的简单描述,我们可以知道数组可以通过“下标”来获取数组中的指定元素,但是这个下标只能是正整数,且从0开始。

但是如果我们想通过一个浮点数、一个字符串、一个对象来获取对应的元素呢?也就是所谓的键值对,是不是数组就满足不了了?

我们可以把数组看做是一种特殊的键值对,key就是数组的下标,value就是下标对应的元素:

键值对

所以我们要求KV数据结构里面的key是一个对象,而不仅仅只能是数组中的一个下标。因此我们需要创造出一种数据结构,他至少需要具有以下的特性:

  1. O(1)复杂度的访问任何一个key对应的值
  2. 这个key可以支持整型、浮点、字符串、对象等任何类型的数据

第一个特性要求我们需要一个像数组下标一样的整型数字来快速访问数组。

第二个特性要求我们的下标不限制只能是整型。

显然我们需要对key做一些特殊处理,这个时候Hash函数就上场了。

Hash函数

哈希函数的作用就是通过哈希算法把任意类型的key转换成固定类型、固定长度的散列值,也就是我们所期望的数组下标(整型)。

因此哈希函数需要具有如下的特征:

  1. 相同的内容经过哈希算法计算后输出结果一致;
  2. 不同的内容经过哈希算法计算后输出不同的结果,但也可能会出现相同的输出值(即哈希碰撞);

因此,一个优秀的哈希算法是不同的内容经过哈希计算后输出的结果具有分布均匀的特点,也就是低碰撞率。

同时,计算速度必须要快!

比较出名的哈希算法有time33算法、Murmurhash算法,这些算法都在追求更好的均匀分布和更快的计算速度。

Java8中java.lang.String类的hashCode方法实现:

java.lang.String#hashCode()

现在来写一个简单的测试类来测试Java中的String类实现的hashCode:

输出:

s1.hashCode=92599395s2.hashCode=92599395s3.hashCode=92599396

可以看出来s1和s2是相同的字符串,输出了相同的hash值,s3和s1、s2不同,输出的hash值也不同,但是也很接近。

说明java采用的hash算法分散性不好,如果用Murmurhash算法,差异就会很大,即哈希算法的剧烈度大,感兴趣的同学可以用Guava中Murmurhash方法试验一下。

通过Hash算法,我们可以计算出任何一种数据类型的哈希值且为整型,这样就满足了数组的下标必须为整数的要求了。

但是又来了一个问题,通过上面的实验我们拿到的hashCode值很大,无法作为数组的下标,否则我们的数组占用的内存就太大了!

所以就采用了根据hashCode取余的方式,比如Java中的HashMap默认size是16,那么92599395%16=3,那么实际上abcde这个字符串就存储在HashMap数组中下标为3的地方。

Hash冲突

上面已经讲过Hash算法无法做到完全均匀分布,也就是说可能会有那么两个不一样的字符串经过hash计算后得到相同的值,此时两个不同的字符串都得对应同一个数组下标上,这就造成了所谓的Hash冲突。

因此,为了解决Hash冲突问题,我们需要下标对应的元素不再仅仅是当前对应的字符串了,而应该是当前的字符串再加上它的next节点的对象地址,这样的一个对象应该如下:

当根据key去找值时候,先计算出key的hash值再取余得到数组的下标,然后根据下标获取到元素,再判断该元素的key是否和当前的key相等(如何判断相等?equals方法!),如果不相等,继续取next节点,继续判断。

说到这里大家如果对HashMap熟悉的话,是不是发现这其实就是HashMap的简单版,一个数组+链表的实现:

再看HashMap源码

如果你已经看到这里,那么我相信你一定已经了解了HashMap的结构了,那么请回去看HashMap的源码吧,你会发现原来是这么简单!这个时候你已经达到了“看山不是山”的境界了!面试官问你任何关于HashMap的问题相信你都能回答了。

搞清楚了HashMap之后,HashTable和HashMap的区别?ConcurrentHashMap是线程安全的基于分段锁的HashMap?为了优化链表的性能,当链表的数超过8之后就变成平衡树了等等。。。

后面做的事情要么是为了线程安全要么就是为了性能。

如果你这么去理解HashMap就会发现它真的很简单的更多相关文章

  1. JSON:如果你愿意一层一层剥开我的心,你会发现...这里水很深——深入理解JSON

    我们先来看一个JS中常见的JS对象序列化成JSON字符串的问题,请问,以下JS对象通过JSON.stringify后的字符串是怎样的?先不要急着复制粘贴到控制台,先自己打开一个代码编辑器或者纸,写写看 ...

  2. 深入理解HashMap

    转自:http://annegu.iteye.com/blog/539465 Hashmap是一种非常常用的.应用广泛的数据类型,最近研究到相关的内容,就正好复习一下.网上关于hashmap的文章很多 ...

  3. 集合之深入理解HashMap

    Hashmap是一种非常常用的.应用广泛的数据类型 1.hashmap的数据结构 要知道hashmap是什么,首先要搞清楚它的数据结构,在java编程语言中,最基本的结构就是两种,一个是数组,另外一个 ...

  4. 深入理解HashMap(原理,查找,扩容)

    面试的时候闻到了Hashmap的扩容机制,之前只看到了Hasmap的实现机制,补一下基础知识,讲的非常好 原文链接: http://www.iteye.com/topic/539465 Hashmap ...

  5. 深入理解HashMap(及hash函数的真正巧妙之处)

    原文地址:http://www.iteye.com/topic/539465 Hashmap是一种非常常用的.应用广泛的数据类型,最近研究到相关的内容,就正好复习一下.网上关于hashmap的文章很多 ...

  6. 深入理解HashMap(jdk7)

    HashMap的结构图示 ​ jdk1.7的HashMap采用数组+单链表实现,尽管定义了hash函数来避免冲突,但因为数组长度有限,还是会出现两个不同的Key经过计算后在数组中的位置一样,1.7版本 ...

  7. Map 综述(一):彻头彻尾理解 HashMap

    转载自:https://blog.csdn.net/justloveyou_/article/details/62893086 摘要: HashMap是Map族中最为常用的一种,也是 Java Col ...

  8. 从逆向的角度去理解C++虚函数表

    很久没有写过文章了,自己一直是做C/C++开发的,我一直认为,作为一个C/C++程序员,如果能够好好学一下汇编和逆向分析,那么对于我们去理解C/C++将会有很大的帮助,因为程序中所有的奥秘都藏在汇编中 ...

  9. JAVA中的数据结构 - 真正的去理解红黑树

    一, 红黑树所处数据结构的位置: 在JDK源码中, 有treeMap和JDK8的HashMap都用到了红黑树去存储 红黑树可以看成B树的一种: 从二叉树看,红黑树是一颗相对平衡的二叉树 二叉树--&g ...

随机推荐

  1. (一)安装mysql

    数据库 数据库的相关概念 DB:数据库(database):存储数据的"仓库".它保存了一系列有组织的数据. DBMS:数据库管理系统(Database Management Sy ...

  2. 鼠标右键添加vscode

    实现效果 右键文件夹,可以使用vscode打开 右键文件,可以使用vscode打开 右键空白处,可以使用vscode打开 进行实现 下载压缩包 为了方便操作,和减少错误,将.reg文件上传到网盘中,点 ...

  3. 用 edgeadm 一键安装边缘 K8s 集群和原生 K8s 集群

    背景 目前,很多边缘计算容器开源项目在使用上均存在一个默认的前提:用户需要提前准备一个标准的或者特定工具搭建的 Kubernetes 集群,然后再通过特定工具或者其他方式在集群中部署相应组件来体验边缘 ...

  4. Pytorch_Part6_正则化

    VisualPytorch beta发布了! 功能概述:通过可视化拖拽网络层方式搭建模型,可选择不同数据集.损失函数.优化器生成可运行pytorch代码 扩展功能:1. 模型搭建支持模块的嵌套:2. ...

  5. SQL中那么多函数,Java8为什么还要提供重复的Stream方法,多此一举?

    有个同学提出一个这样的疑问; 在业务系统中,数据一般都从sql中查询,类似使用where,order by,limit,聚合函数等,为什么还要用java8的Stream方法? 对这个问题,大家有什么见 ...

  6. git使用简单教程-(转自linux人)

    什么是Git Git是目前世界上最先进的分布式版本控制系统.最初由Linus Torvalds编写,用作Linux内核代码的管理.如果你是windows用户,看到这里你可能会担心"是不是只能 ...

  7. stm32中关于NVIC_SetVectorTable函数使用的疑惑与理解

    [转载]2017年12月4日14:48:29 先描述下这几天碰到的一个奇怪的问题: 一个基于stm32的工程中使用到了IAP编程,其中boot空间预留长度为0x6100,实际boot的bin文件大小为 ...

  8. java并发编程工具类JUC第一篇:BlockingQueue阻塞队列

    Java BlockingQueue接口java.util.concurrent.BlockingQueue表示一个可以存取元素,并且线程安全的队列.换句话说,当多线程同时从 JavaBlocking ...

  9. Epicor Advanced Unit of Measure

    作为先进的ERP系统,Epicor 不断发展以解决业务问题.2020 年 10 月,Epicor ERP 10.2.700 的一项强大的新功能高级计量单位 (UoM) 已投入生产. 发行说明看似简单, ...

  10. 深入浅出Promise

    Abstract Promise的意思是承诺(在红宝书中翻译为期约),新华字典:(动)对某项事务答应照办. Promise最早出现在Commn JS,随后形成了Promise/A规范. Promise ...