关于0x7fffffff含义及getHashValue方法替代实现的问询
嘿,我来帮你拆解这段哈希代码里的细节,还有几种靠谱的替代实现方式供你参考~
先搞懂0x7fffffff的作用
你看到的这段代码:
public int getHashValue(K key){ return (key.hashCode() & 0x7fffffff) % size; }
里的0x7fffffff是十六进制表示的整数,转换成二进制就是最高位为0,剩下31位全是1,对应十进制的2^31 - 1,也就是Java中int类型的最大值(因为int是32位有符号数,最高位是符号位)。
它的核心作用是消除哈希值的符号位:Java里hashCode()返回的是有符号int,可能是负数。如果直接对size取模,负数取模会得到负的结果,直接用来当数组索引肯定会越界。而和0x7fffffff做按位与操作后,会把原hashCode的最高位(符号位)强制置为0,不管原hash是正还是负,结果都会变成非负整数,这样再取模就得到合法的索引范围了。
几种替代实现方式
1. 使用Math.abs()(简单但有坑)
这是最直观的写法,直接把哈希值转成正数:
public int getHashValue(K key){ return Math.abs(key.hashCode()) % size; }
⚠️ 注意:当key.hashCode()恰好是Integer.MIN_VALUE时,Math.abs()会返回它本身(因为Integer.MIN_VALUE的绝对值超过了int的最大值,会发生溢出),结果还是负数,取模后依然会有索引越界的风险,所以这个方式有潜在问题。
2. 转成无符号长整型取模(更安全)
把有符号int转成无符号的long,再取模就能彻底避免负数问题:
public int getHashValue(K key){ long unsignedHash = Integer.toUnsignedLong(key.hashCode()); return (int)(unsignedHash % size); }
这个方法不会有溢出问题,因为long的范围足够大,能容纳int转成无符号后的所有值,是比较稳妥的选择。
3. 先做哈希扰动再处理符号位
如果担心原生hashCode的分布不够均匀,容易产生碰撞,可以先做一次哈希扰动,再处理符号位:
public int getHashValue(K key){ int hash = key.hashCode(); // 把高16位和低16位异或,增强哈希分布的均匀性 hash = hash ^ (hash >>> 16); return (hash & 0x7fffffff) % size; }
这种方式在HashMap的源码里也有类似实现,能减少哈希碰撞的概率。
4. 利用第三方工具类(如果允许依赖)
比如用Guava的Hashing工具类,它提供了更健壮的哈希实现:
import com.google.common.hash.Hashing; public int getHashValue(K key){ int hash = Hashing.murmur3_32().hashInt(key.hashCode()).asInt(); return (hash & 0x7fffffff) % size; }
MurmurHash是一种高性能的非加密哈希算法,哈希分布更均匀,碰撞概率更低,但需要引入Guava依赖。
内容的提问来源于stack exchange,提问作者b.alex

