系列文章目录java
前面咱们讨论了HashMap的结构, 接下来几篇咱们从源码角度来看HashMap的实现细节.算法
本篇咱们就来聊聊HashMap的hash算法segmentfault
本文的源码基于 jdk8 版本.数组
上一篇文章咱们提到, 为了利用数组索引进行快速查找, 咱们须要先将 key
值映射成数组下标. 由于数组的下标是有限的集合, 因此咱们能够先经过hash算法将key
映射成整数, 再将整数映射成有限的数组下标:函数
Object -> int -> index
对于 Object -> int
部分, 使用的就是hash function, 而对于 int -> index
部分, 咱们能够简单的使用对数组大小取模来实现.性能
下面咱们就来看看HashMap使用了什么hash算法.code
首先咱们来看维基百科对于hash function的定义:对象
散列函数(英语:Hash function)又称散列算法、哈希函数,是一种从任何一种数据中建立小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,从新建立一个叫作散列值(hash values,hash codes,hash sums,或hashes)的指纹。
在java中, hash函数是一个native方法, 这个定义在Object类中, 因此全部的对象都会继承.继承
public native int hashCode();
由于这是一个本地方法, 因此咱们没法看到它的具体实现, 可是从函数签名上能够看出, 该方法将任意对象映射成一个整型值.调用该方法, 咱们就完成了 Object -> int
的映射索引
因此将 key
映射成index
的方式能够是
key.hashCode() % table.length
那么HashMap是这样作的吗? 事实上, HashMap定义了本身的散列方法:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
咱们知道, int类型是32位的, h ^ h >>> 16
其实就是将hashCode的高16位和低16位进行异或, 这充分利用了高半位和低半位的信息, 对低位进行了扰动
, 目的是为了使该hashCode映射成数组下标时能够更均匀, 详细的解释能够参考这里.
另外, 从这个函数中, 咱们还能够获得一个意外收获:
HashMap中key值能够为null, 且null值必定存储在数组的第一个位置.
前面咱们提到, 将hash值转换成数组下标咱们能够采用取模运算, 可是取模运算是十分耗时的.
另外一方面, 咱们知道, 当一个数是 2^n 时, 任意整数对2^n取模等效于:
h % 2^n = h & (2^n -1)
这样咱们就将取模操做转换成了位操做, 而位操做的速度远远快于取模操做.
为此, HashMap中, table的大小都是2的n次方, 即便你在构造函数中指定了table的大小, HashMap也会将该值扩大为距离它最近的2的整数次幂的值. 这在咱们下面分析构造函数的时候就能看到了.
(完)
下一篇 : 深刻理解HashMap(三): 关键源码逐行分析之构造函数
查看更多系列文章:系列文章目录