HashMap当中的hash()方法

/**
     Computes key.hashCode() and spreads (XORs) higher bits of hash to lower.  Because the table uses power-of-two masking,
     sets of hashes that vary only in bits above the current mask will always collide.
     (Among known examples are sets of Float keys holding consecutive whole numbers in small tables.)
     So we apply a transform that spreads the impact of higher bits downward.
     There is a tradeoff between speed, utility, and quality of bit-spreading.
     Because many common sets of hashes are already reasonably distributed (so don't benefit from spreading),
     and because we use trees to handle large sets of collisions in bins,
     we just XOR some shifted bits in the cheapest possible way to reduce systematic lossage,
     as well as to incorporate impact of the highest bits that would otherwise never be used in index calculations because of table bounds.
     计算key.hashCode()并将哈希值的高位扩展为低位。因为这个表格使用了2的幂掩码,
     仅在当前掩码上方变动位的散列集将始终发生冲突。
     (已知的例子包括在小表中保存连续整数的Float键集。)
     因此,我们应用了一个将较高位的影响向下传播的变换。
     比特传播的速度、实用性和质量之间存在一个权衡。
     因为许多公共散列集已经合理分布(所以不会从传播中受益),
     因为我们用树来处理箱子中大量的碰撞,
     我们只是用最便宜的方法异或一些移位位来减少系统损耗,
     以及合并最高位的影响,否则不会在索引计算中使用,因为表的界限。
     */
    static final int hash(Object key) {
        int h; //定义一个int类型的h变量
        //如果键为空值则空值的hash为0,否则键的hash等于这个键的hashCode ^ (h>>>16)
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
package com.test.util;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = HashMapTest.class)
public class HashMapTest {

    /**
     * 输出一个 int 值的二进制数
     * @param num
     */
    private static void printInfo(int num){
        System.out.println(Integer.toBinaryString(num));
    }

    /**
     * 参考文档:
     * https://www.cnblogs.com/hongten/p/hongten_java_yiweiyunsuangfu.html
     * https://blog.csdn.net/cobbwho/article/details/54907203?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
     * https://blog.csdn.net/weixin_43215250/article/details/84193451
     * 无符号右移与带符号右移的区别就是 无符号始终补0
     * 带符号右移n位需要区分该数是正数还是负数,如果该数为正数则在二进制数前补n个0,如果该数为负数则在该负数的二进制数前补n个0,二进制数后面抹掉n个位数,得到的二进制数即为十进制数带符号右移n个位后得到的另外一个十进制数的二进制数
     * 而无符号右移n位即不需要区分这个十进制数是正数还是负数,往该十进制数的二进制数前补n个0,二进制数后面抹掉n个位数,得到的二进制数即为十进制数带符号右移n个位后得到的另外一个十进制数的二进制数
     * 带符号右移需要注意 负数的二进制数是该负数的绝对值的补码(最高位为符号位->除去符号位其余进行反码得到反码->反码最后一位加1->得到的二进制数即为该负数的二进制数)
     *
     */
    @Test
    public void test_noCharacterRightMove(){
        //测试无符号右移
        int number = -11;
        //原始数二进制
        System.out.println("打印原始数:"+number +" 的二进制");
        printInfo(number);

        //左移一位
        number = number << 1;
        System.out.println("打印左移一位的原始数:"+number +" 的二进制");
        printInfo(number);

        //右移一位
        number = number >> 1;
        System.out.println("打印右移一位的原始数:"+number +" 的二进制");
        printInfo(number);

        System.out.println("================================");
        /*负数的原始码即负数的绝对值的补码
        0000 0000 0000 0000 0000 0000 0000 1011
        ->1000 0000 0000 0000 0000 0000 0000 1011(原始码,最高位1为负,0为正)
        ->1111 1111 1111 1111 1111 1111 1111 0100(反码)
        ->1111 1111 1111 1111 1111 1111 1111 0101(补码)
        */
        number = -11;
        System.out.println("打印原始数负数:"+number +" 的二进制");
        printInfo(number);

        /*带符号右移2位
        1111 1111 1111 1111 1111 1111 1111 0101 将负数的补码,也就是负数的二进制数 右移两位
        由于是负数,则二进制数前面补1,二进制数前面补了多少位,二进制数后面就抹掉多少位:
        11 1111 1111 1111 1111 1111 1111 1111 01
        (得到的该二进制数则为-3的补码)
        11 1111 1111 1111 1111 1111 1111 1111 01 如果反推回去则得到十进制数即
        -> 11 1111 1111 1111 1111 1111 1111 1111 00 (将补码的最后一位减1)
        -> 10 0000 0000 0000 0000 0000 0000 0000 11 (除去最高位不动,其余位进行反码)
        -> 00 0000 0000 0000 0000 0000 0000 0000 11 (得到反码之后,最高位表示符号位即1:负,0:正)
        -> -3

        -3的二进制位表示
        3:0000 0000 0000 0000 0000 0000 0000 0011
        -> 1000 0000 0000 0000 0000 0000 0000 0011 (最高位表示符号位1:负,0:正)
        -> 1111 1111 1111 1111 1111 1111 1111 1100 (除去最高位,其余位进行反码)
        -> 1111 1111 1111 1111 1111 1111 1111 1101 (将反码的最后一位加1的到补码,即负数的二进制数)
         */
        number = -11 >> 2;
        System.out.println("打印带符号右移两位的原始数:"+number +" 的二进制");
        printInfo(number);

        /*无符号右移2位
        1111 1111 1111 1111 1111 1111 1111 0101 将负数-11的补码,也就是负数的二进制数 右移两位
        由于是无符号右移2位,则二进制数前面补0,二进制数前面补了多少位,二进制数后面就抹掉多少位:
        00 1111 1111 1111 1111 1111 1111 1111 01 得到的二进制数即为负数右移后的十进制数的二进制数
        得到该二进制数的十进制数:1073741821(在线二进制数转换为十进制数工具:https://tool.lu/hexconvert/)
         */
        number = -11 >>> 2;
        System.out.println("打印无符号右移两位的原始数:"+number +" 的二进制");
        printInfo(number);
        /**
         43210      位数
         --------
         1010       十进制:10     原始数         number
         10100      十进制:20     左移一位       number = number << 1;
         1010       十进制:10     右移一位       number = number >> 1;
         对于:>>>
         无符号右移,忽略符号位,空位都以0补齐
         value >>> num     --   num 指定要移位值value 移动的位数。
         无符号右移的规则只记住一点:忽略了符号位扩展,0补最高位  无符号右移运算符>>> 只是对32位和64位的值有意义

         BIN 二进制、OCT 八进制、DEC 十进制、HEX 十六进制
         二进制(逢二进一,需要该位数上的值则让该位为1,不需要则让该位为0,各个位上所对应的值添加到一起就是十进制)
         2^6 2^5 2^4 2³ 2² 2¹ 2º
         1: 2º = 1
         2:2¹ 2º = 1 0
         3: 2¹ 2º = 1 1
         4: 2² 2¹ 2º = 1 0 0
         5:2² 2¹ 2º = 1 0 1
         6:2² 2¹ 2º = 1 1 0
         7:2² 2¹ 2º = 1 1 1
         8:2³ 2² 2¹ 2º = 1 0 0 0
         9: 2³ 2² 2¹ 2º = 1 0 0 1
         10:2³ 2² 2¹ 2º = 1 0 1 0
         11:2³ 2² 2¹ 2º = 1 0 1 1
         比如说当前的10的二进制数为 1 0 1 0,此时10左移一位则等于 1 0 1 0 0,那么此时左移一位后的十进制数值则为
         2^4 2³ 2² 2¹ 2º = 16 + 0 + 4 + 0 + 0 = 20  即左移一位后的值为20
         20 右移一位,那就是 1 0 1 0 0 二进制数恢复到原来的10 的二进制数也就是 1 0 1 0
         20的二进制数1 0 1 0 0右移一位等于1 0 1 0
         2³ 2² 2¹ 2º = 8 + 0 + 2 + 0 =10
         上述即可进行了解左移和右移(左移就是往值的二进制数后面补0或者1,而右移则是往值的二进制数前面补0或者1,右移→的位数将会把右边的数给抹掉,因为左边右移进来了新的即补的0或者1)
         >>是带符号右移,>>>是无符号右移
         带符号右移就是将那个数转为2进制然后在前面补0或1,如果是正数就补0,负数补1(阅读:https://blog.csdn.net/weixin_43215250/article/details/84193451)
         例如11 >> 2,则是将数字11右移2位,即1 0 1 1往右边移动,而右边则没有2的取值,即没有2的-1次方,2的-2次方这样子,所以就给抹掉了。
         右移前面补多少位,则后面需要抹掉多少位
         11 >> 2: 1 0 1 1 -> 0 0 1 0 = 2³ 2² 2¹ 2º = 0 + 0 + 2 + 0 = 2
         11的二进制形式为:0000 0000 0000 0000 0000 0000 0000 1011,然后把低位的最后两个数字移出,因为该数字是正数,所以在高位补零。
         则得到的最终结果是0000 0000 0000 0000 0000 0000 0000 0010。转换为十进制是2。
         无符号右移与带符号右移的区别就是 无符号始终补0
         前提:负数的二进制(即负数的绝对值的二进制补码):负数的绝对值得到二进制原码->最高位表示符号位,即正则0,负则1->除去最高位符号位其余全部反过来得到反码->最后将反码的最后一位+1得到补码
         带符号右移:
         得到十进制数的二进制后,十进制数为正数则在二进制前进行补0,为负数则在二进制前补1;补完得到的二进制数转换为十进制数即,十进制数带符号右移得到的十进制值
         无符号右移:
         同样是得到十进制数的二进制后,不管十进制数是正数还是负数,都只往十进制的二进制数前补0(前面补了多少位,后面抹掉多少位),得到的二进制数转化为十进制数即十进制数无符号右移得到的十进制数
         */
    }
    @Test
    public void test_hash(){
        Object key=null;
        int h; //定义一个int类型的h变量
        //如果键为空值则控制的hash为0,否则键的hash等于这个键的hashCode ^ (h>>>16)
        System.out.println("当key为空时,key的hash值为:");
        //当该key不为空的时候,将该key的hashCode取值赋值给变量h,让该h去 ^ (h>>>16)
        System.out.println((key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16));

        Object key1="hello world";
        System.out.println("当key不为空时,key的hash值为:");
        System.out.println("hashCode: "+key1.hashCode());//打印出来是一串数值:1794106052
        System.out.println("hashCode>>>16: "+(key1.hashCode() >>> 16));//得到hashCode数值的二进制数(正数的二进制/负数则为正数的二进制的补码),二进制数前补16个0,二进制数后面抹掉16位数,得到的二进制数则为十进制数无符号右移16位后得到的十进制数的二进制数
        System.out.println("hashCode ^ hashCode>>>16: "+(key1.hashCode() ^ (key1.hashCode()>>>16)));//打印出来是一串数值:1794106052

        System.out.println("===========================================");
        /*
        ^是针对二进制的二目运算符。运算规则:两个二进制数值如果在同一位上相同,则结果中该位为0,否则为1,比如1011 & 0010 = 1001。
        两个数值的二进制数,相同位数上的值如果相同则为0否则为1得到的二进制数,即为另一个十进制数的二进制数
        5的二进制数为: 2² 2¹ 2º = 1 0 1
        6的二进制数为: 2² 2¹ 2º = 1 1 0
        得到的二进制数为:         0 1 1 = 3
        */
        System.out.println(1794106052 ^ 27375);
        System.out.println(5 ^ 6);
        /**
         java中有三种移位运算符
         <<      :     左移运算符,num << 1,相当于num乘以2
         >>      :     右移运算符,num >> 1,相当于num除以2
         >>>    :     无符号右移,忽略符号位,空位都以0补齐
         -------------------------------------------------
         ^      :    异或运算符,两个十进制数的二进制数,每一个位数上进行比较,两个位数上,相同则为0,不同则为1,得到的二进制数即为 十进制数经过异或之后得到的另外一个十进制数的二进制数
         */

        System.out.println("=======================================");
        String simple = "1";
        int l = simple.hashCode();
        System.out.println(simple+" 的hashCode值为:"+l);//1的hashCode值为49则
        /**
         得到49的二进制数
         2^5  2^4  2³ 2² 2¹ 2º
         32   16   8  4  2  1
         1    1    0  0  0  1
         0000 0000 0000 0000 0000 0000 0011 0001
         无符号右移16位,补在二进制数的前面都是0,补了多少位,二进制数后面则抹掉多少位
         得到的十进制无符号右移16位后的二进制数即为另外一个十进制数的二进制数
         0000 0000 0000 0000 0000 0000 0000 0000该二进制数代表的十进制数值为0
         得到hashCode值的二进制数后与hashCode值的二进制无符号右移16位后的二进制值进行 异或操作即
         (相同为0,不同为1)
         原数的二进制数为:                                              0000 0000 0000 0000 0000 0000 0011 0001 该二进制数表示的十进制数为:49
         原数进行了无符号右移16位后的二进制数为:                        0000 0000 0000 0000 0000 0000 0000 0000 该二进制数表示的十进制数为:0
         49的二进制数与49进行无符号右移16后进行异或操作得到的二进制数为:0000 0000 0000 0000 0000 0000 0011 0001 该二进制数表示的十进制数为:2^5 + 2^4 + 2º =  32 + 16 + 1 = 49

         可以这么说嘛?就是一个十进制数的二进制数,该二进制数只在后面16位有编码,即后面十六位前面的数都是0
         即 0000 0000 0000 0000 1111 1111 1111 1111 二进制数后面的16位有变化,前面16位都是0的情况下,
         这样的十进制数(即某一个数的hashCode),它经过>>>16无符号右移16位的情况后,得到的都是0000 0000 0000 0000 0000 0000 0000 0000
         因为无符号右移了16位,即把原来的二进制数后面的有变化的16位数的值都替换成了0了;无符号右移在二进制数前面补的都是0;
         当该十进制数的二进制数 与 该十进制数进行无符号右移16位后得到的二进制数 进行异或操作 得到的二进制数仍然为 该十进制数的二进制数;
         而该十进制数的二进制数转化成十进制数,即还是原来的十进制数,即还是该某一个数的hashCode取值(如果该原本hashCode值的二进制数变化超过了16位,那么在进行无符号右移16位就会发生变化,而其异或之后的值也会发生变化)
         */
        int n = simple.hashCode() >>> 16;
        System.out.println(simple+" 无符号右移16位后的值为:"+n);
        int m = l ^ n;
        System.out.println(simple+" 的hashCode: ["+l+"] 与 "+simple+" 的无符号右移16位后的值:["+n+"] 经过异或操作得到的值为:["+m+"]");

        System.out.println("以下即为HashMap当中hash()方法的代码:");
        System.out.println((simple == null) ? 0 : (h = simple.hashCode()) ^ (h >>> 16));

        /**
         1. 了解二进制
         2. 了解左移(即在二进制后补0/1)、右移(在二进制前补0/1,二进制前补了多少位,二进制后面则抹掉多少位)
         3. 了解负数的二进制是该负数的绝对值的补码(绝对值的二进制->最高位代表符号位1:负,0:正->除去最高位进行反码->得到反码,将最后一位加1->得到的二进制即为补码,即为负数的二进制)
         4. 了解右移(带符号右移)中正数则在二进制前补0,负数则在二进制前补1
         5. 了解无符号右移(在二进制前补0,二进制前补了多少位,二进制后面则抹掉多少位)
         */
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值