关于HashMap中的扰动函数的疑问

Posted wukonga1234

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于HashMap中的扰动函数的疑问相关的知识,希望对你有一定的参考价值。


         最近再看jdk8的hashmap源码,当看到这一步的时候有点疑问,去网上搜了一下,看到的所有文章基本上都是一篇抄一篇的(反正目前各大社区就是这么个状况),那个意思就是让高16位也参与运算,增加结果的随机性,减小hash碰撞???

技术图片

       乍一听好像是那么回事,但是越想越不对劲;我怎么都觉得是无论怎么运算最后不都是看低几位吗,在哪个固定的长度里每个数出现的概率不还是随机的吗(1/length);高位参与运算之后肯定能保证的是原来低位相同的值更加不同,但是不能保证本来不同的值运算后还是不通吧。怀着各种疑问,简单做了下实验。
与运算基数从2的4次幂到16次幂,分别对原始hash和扰动函数后的值进行统计
实验数据为随机8为的字符串值,平时我们用字符串作为key的情况最多吧
最后统计结果为:100次碰撞率的均值
代码:

import com.alibaba.fastjson.JSON;

        import java.util.ArrayList;
        import java.util.Random;
        import java.util.List;

/**
 * @description:
 * @author: wukong
 * @remark: create wukong 2019/12/26 22:49
 */
public class HashTest {
    public static void main(String[] args) {
        int length = 1 << 8;
        List<Double> doubles = new ArrayList<>(100);
        List<Double> double2s = new ArrayList<>(100);
        // 测试次数
        int count = 100;
        for (int i = 0; i < count; i++) {
            hashCalculate(length, doubles, double2s);
        }
        System.out.println("均值1:" + doubles.stream().mapToDouble((item) -> item).summaryStatistics().getAverage());
        System.out.println("均值2:" + double2s.stream().mapToDouble((item) -> item).summaryStatistics().getAverage());
        System.out.println("集合1:" + JSON.toJSON(doubles));
        System.out.println("集合2" + JSON.toJSON(double2s));
    }

    /**
     * @Description: hash碰撞率计算
     */
    private static void hashCalculate(int length, List<Double> doubles, List<Double> double2s) {
        int cardinal = length - 1;
        int load = (int) (length * 0.75);
        int crash = 0;
        int crash2 = 0;
        List<Integer> list = new ArrayList<>();
        List<Integer> list2 = new ArrayList<>();
        for (int i = 0; i < load; i++) {
            // 随机key获取哈希值
            int hash = getRandomString().hashCode();
            // 直接与基数进行与运算
            int result = cardinal & hash;
            // jdk8中hashmap扰动函数
            int disturbHash = hash ^ (hash >>> 16);
            // 扰动后的值与运算
            int result2 = cardinal & disturbHash;
            //统计直接运算碰撞次数
            if (!list.contains(result)) {
                list.add(result);
            } else {
                crash++;
            }
            //统计扰乱后碰撞次数
            if (!list2.contains(result2)) {
                list2.add(result2);
            } else {
                crash2++;
            }
        }
        double crashProbability = crash / (double) length;
        double crashProbability2 = crash2 / (double) length;
        doubles.add(crashProbability);
        double2s.add(crashProbability2);
//      System.out.println("当长度为" + length + "时,hash值直接与运算的碰撞率为:" + crashProbability);
//      System.out.println("当长度为" + length + "时,扰动函数之后与运算的碰撞率为:" + crashProbability2);
    }

    /**
     * @Description: 获取随机key字符串
     */
    private static String getRandomString() {
        String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        int length = 8;
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(62);
            sb.append(str.charAt(number));
        }
        return sb.toString();
    }
}

运行结果示例(以下长度为2的16次幂时):

技术图片

将数据整理完成后,做了一个折线图:

技术图片


根据实验结果,我得到结论和我的想法一致,两者碰撞率应该会趋于一致,这个扰动函数好像是没用的,更确切的说这一步所有的扰动函数应该都是没用的。。。。。
我做完实验后,不知道是喜是悲,人家设计的怎么会有问题,还有更多的人也认同那样的设计,但是我却越想越觉得这一步是不可能降低碰撞率的。。。。也不知是我哪里想偏了,想错了,还是因为概率论什么的没学,或者是实验的时候哪里写错了,希望各位能纠正一下我错误的想法

以上是关于关于HashMap中的扰动函数的疑问的主要内容,如果未能解决你的问题,请参考以下文章

HashMap中确定数组位置为什么要用hash进行扰动

C/C++手撕哈希表详解

HashMap原理:哈希函数的设计

浅谈HashMap原理,记录entrySet中的一些疑问

面试Java——集合之HashMap和ConcurrentHashMap

面试题:来,问你几个关于HashMap的问题?