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. 第一章 FreeBSD之系统安装

    一.默认选择 >>[Boot Multi User],按回车键 二.默认选择 >> [Install] 三.选择默认的键盘设置 >>[Select] 四.配置主机名 ...

  2. MySQL从库维护经验分享

    前言: MySQL 主从架构应该是最常用的一组架构了.从库会实时同步主库传输来的数据,一般从库可以作为备用节点或作查询使用.其实不只是主库需要多关注,从库有时候也要经常维护,本篇文章将会分享几点从库维 ...

  3. KMP(The Knuth-Morris-Pratt Algorithm)

    本文代码来自于中国大学MOOC KMP课件下载 注释内容为自己理解,如有错误请评论,或者私信给我,谢谢 #include <stdio.h> #include "stdlib.h ...

  4. Select Screen 0 with xrandr Ask QuestionScreen 0" here describes your whole virtual display made of these two outputs: eDP-1-

    Screen 0" here describes your whole virtual display made of these two outputs: eDP-1-1: physica ...

  5. 通过format学习,python的内部方法是面象对象的-python面向对象

    1.常用的形式 s ="{0} {0} qqq {0} xxx {1}".format('dog','cat')print(s) 结果:dog dog qqq dog xxx ca ...

  6. wmctrl像xmonad那样方便地用快捷键来控制任务窗口的显示

    窗口左右互搏之wmctrl篇 分类: LINUX 2012-10-24 16:34:41   一直有个念头,就是能够像xmonad那样方便地用快捷键来控制任务窗口的显示,今天弄wmctrl,刚好有时间 ...

  7. python基础之centos7源码安装python3

    一.先安装python3所依赖的软件包,非常重要(否则可能会出现python3安装成功,却缺少相应的pip) yum groupinstall "Development tools" ...

  8. Mysql索引的创建与删除

    1. 创建索引 1.1 使用Alter创建索引 1 添加主键索引 特点:数据列不允许重复,不能为null,一张表只能有一个主键:Mysql主动将该字段进行排序 ALTER TABLE 表名 ADD P ...

  9. GO汇编-函数

    GO汇编-函数 终于到函数了!因为Go汇编语言中,可以也建议通过Go语言来定义全局变量,那么剩下的也就是函数了.只有掌握了汇编函数的基本用法,才能真正算是Go汇编语言入门.本章将简单讨论Go汇编中函数 ...

  10. 浅析IOC 和 DI

    学习过spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...