HashMap 计算 Hash 值的扰动函数

简介: HashMap 计算 Hash 值的扰动函数

计算过程


以下代码叫做 “扰动函数

//java 8 中的散列值优化函数
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

理论上 hash 散列是一个 int 值,如果直接拿出来作为下标访问 hashmap 的话,考虑到二进制 32 位,取值范围在-2147483648 ~ 2147483647。大概有 40 亿个 key , 只要哈希函数映射比较均匀松散,一般很难出现碰撞。

一个客观的问题:要存下 40 亿长度的数组,服务器内存是不能放下的。通常咱们 HashMap 的默认长度为 16 。所以这个 hashCode , (key.hashCode ) 是不能直接来使用的。使用之前先做对数组长度的与运算,得到的值才能用来访问数组下标。

代码如下:

// n = hashmap 的长度
p = tab[i = (n - 1) & hash])

这里为什么要使用 n -1 ,来进行与运算,这里详单与是一个”低位掩码”, 以默认长度 16 为例子。和某个数进行与运算,结果的大小是 < 16 的。如下所示:

 

10000000 00100000 00001001
&   00000000 00000000 00001111
------------------------------
    00000000 00000000 00001001  // 高位全部归 0, 只保留后四位

这个时候会有一个问题,如果本身的散列值分布松散,只要是取后面几位的话,碰撞也会非常严重。还有如果散列本身做得不好的话,分布上成等差数列的漏洞,可能出现最后几位出现规律性的重复。

这个时候“扰动函数”的价值就体现出来了。如下所示:

image.png

在 hash 函数中有这样的一段代码:(h = key.hashCode()) ^ (h >>> 16) 右位移 16 位, 正好是32bit 的一半,与自己的高半区做成异或,就是为了混合原始的哈希码的高位和低位,以此来加大低位的随机性。并且混合后的低位掺杂了高位的部分特征,这样高位的信息变相保存下来。其实按照开发经验来说绝大多数情况使用的时候 HashMap 的长度不会超过 1000,所以提升低位的随机性可以提升可以减少 hash 冲突,提升程序性能。

如果感兴趣的小伙伴可以浏览下一下 Peter Lawlay 的专栏《An introduction to optimising a hashing strategy》的一个实验:他随机选取了 352 个字符串,在散列值完全没有冲突的前提下,对低位做掩码,取数组下标。

结果显示, 当 hashmap 的数组长度为 512 的时候,也就是采用低位掩码取低 9 位的时候,在没有扰动函数的情况下,发生了 103 次碰撞,接近 30%。而在使用扰动函数之后只有 92 次碰撞。碰撞减少了将近10%。说明扰动函数确实有功效的。

但是明显 Java 8 觉得扰动做一次就够用了,做 4 次的话,可能边际效用也不大, 为了效率考虑就改成了一次。

代码演示


import java.lang.reflect.Field;
import java.util.HashMap;
/**
 * HashMap 计算 hashKey
 * <p>
 * 演示:扰动函数
 *
 * @see HashMap#hash(Object)
 */
public class HashKeyTest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        HashMap<String, String> map = new HashMap<>();
        String k = "王羲之";
        String v = "大书法家";
        map.put(k, v);
        Field field = map.getClass().getDeclaredField("table");
        field.setAccessible(Boolean.TRUE);
        Object[] nodes = (Object[]) field.get(map);
        int h = k.hashCode();
        System.out.println("  h=" + h);
        System.out.println();
        // 调用 hashCode 结果
        System.out.println("  h=hashCode()    " + num0x(h) + "  调用 hashCode");
        // 无符号右移 16
        System.out.println("  h>>>16          " + num0x(h >>> 16));
        System.out.println("--------------------------------------------------");
        // 计算 hash
        System.out.println("  hash=h^(h>>>16) " + num0x(h ^ (h >>> 16)) + "  计算 hash");
        System.out.println("--------------------------------------------------");
        // 计算下标
        System.out.println("  (n-1)&hash      " + num0x(15 & (h ^ (h >>> 16))) + "  计算下标");
        System.out.println();
        int idx = (15 & (h ^ (h >>> 16)));
        // 输出下标
        System.out.println("  下标: " + idx);
        // 在下标中去获取数据
        System.out.println("  查询结果:" + nodes[idx]);
    }
    /**
     * 输入 int 转换为 二进制字符串
     *
     * @param num 数字
     * @return 二进制字符串
     */
    static String num0x(int num) {
        String num0x = "";
        for (int i = 31; i >= 0; i--) {
            num0x += (num & 1 << i) == 0 ? "0" : "1";
        }
        return num0x;
    }
}


运行结果如下:

image.png


参考资料:https://www.zhihu.com/question/20733617


相关文章
|
6月前
|
存储 容器
List,linkeedlist集合介绍,特点,二者区别,增长因子,去重复
List,linkeedlist集合介绍,特点,二者区别,增长因子,去重复
|
6月前
HashMap遍历方式
HashMap遍历方式
|
5月前
|
存储 算法 安全
C++ 通过CryptoPP计算Hash值
Crypto++ (CryptoPP) 是一个用于密码学和加密的 C++ 库。它是一个开源项目,提供了大量的密码学算法和功能,包括对称加密、非对称加密、哈希函数、消息认证码 (MAC)、数字签名等。Crypto++ 的目标是提供高性能和可靠的密码学工具,以满足软件开发中对安全性的需求。该库包含了许多常见的密码学算法,如AES、DES、RSA、DSA、SHA等,使开发者能够轻松地在他们的应用程序中实现安全性和加密功能。Crypto++ 是以面向对象的方式设计的,因此它的使用通常涉及使用类和对象来表示不同的密码学概念和算法。
43 1
C++ 通过CryptoPP计算Hash值
|
2月前
|
前端开发 数据库
两个map中的数据,按照相同键,将所对应的值相加方法
两个map中的数据,按照相同键,将所对应的值相加方法
14 0
|
7月前
|
存储 数据安全/隐私保护
均匀散列函数(Uniform Hash Function)
均匀散列函数(Uniform Hash Function)是一种将不同长度的输入数据映射到相同大小的输出数据的散列函数。均匀散列函数的主要特点是,对于相同的输入数据,无论其长度如何,都会得到相同的输出散列值。这种散列函数常用于数据结构的存储和查找,例如哈希表、散列表等。
102 3
|
9月前
|
存储 算法 Serverless
【哈希的模拟实现】
【哈希的模拟实现】
51 0
|
存储 自然语言处理 安全
Map&Set哈希桶(基础+常用方法总结)
Map&Set哈希桶(基础+常用方法总结)
Map&Set哈希桶(基础+常用方法总结)
|
移动开发 算法 C++
模拟哈希的实现
模拟哈希的实现
|
Java
hashmap 的重新散列和装载因子
HashMap 的装载因子是 0.75
84 0
|
算法 计算机视觉
ML之Hash_HamMingDistance:基于输入图片哈希化(均值哈希+差值哈希)即8*8个元素的单向vector利用汉明距离算法进行判别
ML之Hash_HamMingDistance:基于输入图片哈希化(均值哈希+差值哈希)即8*8个元素的单向vector利用汉明距离算法进行判别
ML之Hash_HamMingDistance:基于输入图片哈希化(均值哈希+差值哈希)即8*8个元素的单向vector利用汉明距离算法进行判别