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. Java中浮点数的坑

    基本数据类型 浮点数存在误差 浮点数有一个需要特别注意的点就是浮点数是有误差的,比如以下这段代码你觉得输出的什么结果: public class Demo { public static void m ...

  2. 第一周JVM核心技术-工具与GC策略

    一. JDK工具 1.1 内置命令行工具 工具 简介 jps/jinfo 查看java进程 jstat 查看JVM内部GC信息 jmap 查看JVM堆或类占用空间信息 jstack 查看线程信息 jc ...

  3. HOOK技术之SSDT hook(x86/x64)

    x86 SSDT Hook 32位下进行SSDT Hook比较简单,通过修改SSDT表中需要hook的系统服务为自己的函数,在自己的函数中进行过滤判断达到hook的目的. 获取KeServiceDes ...

  4. Convert character array to string in MATLAB

    Matlab提取特征值是经常要读取多个图片文件,把文件名保存在数组中后再读取会出错.从stackoverflow中找到如下解决方法: I've a M by N matrix, each cell c ...

  5. php中的一些碎的知识点

    PHP函数之可变函数,即可以通过变量的名字来调用函数,因为变量的值是可变的,所以可以通过改变一个变量来调用不同的函数 例如 function name(){     echo "name&q ...

  6. [Java] 开课吧--JVM

    双亲委派 向上委托,向下加载 收到加载任务后,先交给父类加载器,只有当父类加载器无法完成,才会执行加载 保证只有一个类加载器加载,避免重复加载 破坏:JDK 1.2后才使用,JDK  1.1的核心类没 ...

  7. [Python] Tkinter command

    例1:创建按钮 import tkinter as tk class App: def __init__(self,root): frame = tk.Frame(root) frame.pack() ...

  8. .jnlp 文件打开方式

    .jnlp 文件打开方式 jnlp文件打开需要安装jre ,java环境,通过java环境运行即可,下面介绍详细步骤 1.下载.安装最新版jre环境,直接下一步即可 2 java配置 打开控制面板,查 ...

  9. 国外DIY网站

    https://site.douban.com/109950/widget/notes/250072/note/248811721/

  10. 使用Tomcat插件控制台中文乱码解决方案(IDEA)(Day_51)

    解决方案 1. File -> Settings... 2. 搜索 Runner (运行程序),在 'VM options:' 中添加:-Dfile.encoding=GB2312 注:GB23 ...