HashMap是怎么解决哈希冲突的?

Hash

一般翻译为“散列”,也有直接音译为“哈希”的,这就是把任意长度的输入通过散列算法,变换成 固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间通 常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入 值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

所有散列函数都有如下一个基本特性:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也 不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同。

什么是哈希冲突

当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰 撞)。

HashMap的数据结构

在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除 困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各 自的优势,使用一种叫做链地址法的方式可以解决哈希冲突:

HashMap是怎么解决哈希冲突的?_第1张图片

这样我们就可以将拥有相同哈希值的对象(img)组织成一个链表放在hash值所对应的 bucket下,但相比 于hashCode返回的int类型,我们HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4(即 2的四次方16)要远小于int类型的范围,所以我们如果只是单纯的用hashCode取余来获取对应的 bucket这将会大大增加哈希碰撞的概率,并且最坏情况下还会将HashMap变成一个单链表,所以我们还 需要对hashCode作一定的优化 hash()函数

上面提到的问题,主要是因为如果使用hashCode取余,那么相当于参与运算的只有hashCode的低位, 高位是没有起到任何作用的,所以我们的思路就是让 hashCode取值出的高位也参与运算,进一步降低 hash碰撞的概率,使得数据分布更平均,我们把这样的操作称为扰动,在JDK 1.8中的hash()函数如下:

static final int hash(Object key) {
2 int h;
3 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 与自己右移16位
进行异或运算(高低位异或)
4 }

这比在JDK 1.7中,更为简洁,相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行 了1次位运算和1次异或运算(2次扰动);

JDK1.8新增红黑树

 HashMap是怎么解决哈希冲突的?_第2张图片

通过上面的链地址法(使用散列表)和扰(img)动函数我们成功让我们的数据分布更平均,哈希碰撞减 少,但是当我们的HashMap中存在大量数据时,加入我们某个 bucket下对应的链表有n个元素,那么遍 历时间复杂度就为O(n),为了针对这个问题,JDK1.8在HashMap中新增了红黑树的数据结构,进一步使 得遍历复杂度降低至O(logn);

简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的:

1. 使用链地址法(使用散列表)来链接拥有相同hash值的数据;

2. 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;

3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快; 

你可能感兴趣的:(java基础相关,算法和数据结构,链表,数据结构,java,算法)