hashmap

本文深入探讨Java中HashMap的工作原理,包括位运算的基础、hash函数的设计思路及其扰动操作,以及如何通过控制数组长度为2的幂次来提高性能。同时,文章还讨论了hash碰撞的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 一、分析hashmap之前需要先了解位运算

 

Java中的<< 和 >> 和 >>> 详细分析
<<表示左移移,不分正负数,低位补0; 

注:以下数据类型默认为byte-8位

左移时不管正负,低位补0

正数:r = 20 << 2

  20的二进制补码:0001 0100

  向左移动两位后:0101 0000

         结果:r = 80

负数:r = -20 << 2

  -20 的二进制原码 :1001 0100

  -20 的二进制反码 :1110 1011

  -20 的二进制补码 :1110 1100

  左移两位后的补码:1011 0000

        反码:1010 1111

        原码:1101 0000

        结果:r = -80

>>表示右移,如果该数为正,则高位补0,若为负数,则高位补1;

注:以下数据类型默认为byte-8位

正数:r = 20 >> 2

  20的二进制补码:0001 0100

  向右移动两位后:0000 0101

       结果:r = 5

负数:r = -20 >> 2

  -20 的二进制原码 :1001 0100

  -20 的二进制反码 :1110 1011

  -20 的二进制补码 :1110 1100 

  右移两位后的补码:1111 1011 

        反码:1111 1010

        原码:1000 0101

        结果:r = -5

>>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0

正数: r = 20 >>> 2

    的结果与 r = 20 >> 2 相同;

负数: r = -20 >>> 2

注:以下数据类型默认为int 32位

  -20:源码:10000000 00000000 00000000 00010100

    反码:11111111  11111111   11111111   11101011

    补码:11111111  11111111   11111111   11101100

    右移:00111111  11111111   11111111   11111011

    结果:r = 1073741819

了解完位运算后,我们需要对其hashmap内部做一个了解

二、hash函数

以下内容参考:
今日头条

研究HashMap----常见问题_不洇的博客-优快云博客

浅显理解 hashcode 和 hash 算法_stateiso的博客-优快云博客_hashcode怎么计算

HashMap中hash(Object key)原理,为什么(hashcode >>> 16)。_杨涛的博客的博客-优快云博客

hash函数的目标是计算key在数组中的下标
判断一个哈希函数的标准:散列是否均匀、计算是否简单

hashmap哈希函数的步骤:
   1.对key对象的hashcode进行扰动
   2.通过取模求得数组下标

扰动是为了让hashcode的随机性更高
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
也就是高16和低16位进行异或
>>> 16的话 32位前16位是0 
我们知道异或运算两个相应位不同才为1,相同为0
那么我们理解 (h = key.hashCode()) ^ (h >>> 16) 
h >>> 16 高16为0 与 任意位异或得到的 还是那个位数
这个就会保持高16位不变 
看下图:

以8位进行演示:
高四位还是保持原先hashcode值,高四位的值保持不变
高低位进行异或,让高位参与散列运算,散列更加均衡

扰动之后需要对结果取模
HashMap在jdk1.8并不是简单使用 % 进行取模,而是采用了另外一种更加高性能的方法
HashMap控制数组长度为2的整数次幂
hashcode进行求余运算 和 让hashcode与数组长度-1进行位与运算是 相同 的效果

位与运算的效率却比求余高得多,从而提升了性能
扩容运算中也利用到了该特性
取模运算的源码看到 putVal() 方法,该方法在 put() 方法中被调用
if ((p = tab[i = (n - 1) & hash]) == null)

完整的hash计算过程可以参考下图

HashMap的数组长度为2的整数次幂,那么HashMap是如何控制数组的长度为2的整数次幂的?
两种情况:
1.初始化时指定的长度
未在HashMap构造器中指定长度,则初始长度为16
当我们初始化指定一个非2的整数次幂长度时,HashMap会调用 tableSizeFor() 方法,转化成 大于该指定数的最小2的整数次幂
2.扩容时的长度增量
HashMap每次扩容的大小都是原来的两倍,控制了数组大小一定是2的整数次幂

小结:
HashMap通过高16位与低16位进行异或运算来让高位参与散列,提高散列效果;
HashMap控制数组的长度为2的整数次幂来简化取模运算,提高性能;
HashMap通过控制初始化的数组长度为2的整数次幂、扩容为原来的2倍来控制数组长度一定为2的整数次幂

三、hash碰撞

hash冲突指的是两个不同的key经过hash计算之后得到的数组下标是相同的
解决hash冲突的方式很多,如开放定址法、再哈希法、公共溢出表法、链地址法
HashMap采用的是 链地址法 ,jdk1.8之后还增加了红黑树的优化

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值