还在用 Arrays.hashCode?Java 自己也能写出更快的版本!

大家好,这里是架构资源栈 !点击上方关注,添加"星标",一起学习大厂前沿架构!

关注、发送C1即可获取JetBrains全家桶激活工具和码!

Java 自带的 Arrays.hashCode(byte[]) 方法已经存在多年,是不少开发者生成哈希值的首选工具。但 Dynatrace 的研究人员最近发现,即使不使用任何原生代码,仅靠 Java 自身的手段,也能写出 性能更强的版本,甚至超过了 OpenJDK 24 的"底层优化"实现。


Java 原版 hashCode 的瓶颈在哪?

从 JDK 1.5 起,Arrays.hashCode(byte[]) 的逻辑其实非常朴素:

java 复制代码
static int hashCode(byte[] b) {
  if (b == null) return 0;
  int h = 1;
  for (byte q : b) {
    h = 31 * h + q;
  }
  return h;
}

这是经典的「乘 31 加元素」的哈希递推式。但问题在于:逐字节地串行处理,几乎没有编译器优化空间。

为此,研究者参考了 Daniel Lemire 提出的"更快的哈希技巧",通过手动展开循环和**并行处理(SWAR & SIMD)**两种方式,大幅加速了这一过程。


实测结果:比 OpenJDK 本身还快

他们在 AWS 的 c5.metal 实例上,基于 OpenJDK 24.0.1,对不同实现版本做了对比测试,包含:

  • Java 默认实现
  • OpenJDK 自带 Intrinsic 实现(SIMD 加速)
  • 手动展开的 Java 循环
  • 全新 SWAR 实现(寄存器并行)
  • 全新 SIMD 实现(Java Vector API)

测试逻辑基于 JMH,每组包含 1 万个随机长度的 byte[] 数组。结果显示:

🔥 SWAR 实现比默认版本快 2.9 倍 🚀 SIMD 版本甚至超越了 OpenJDK 的底层 intrinsic 实现


优化一:SWAR(寄存器内并行)技术

SWAR(SIMD Within A Register)指的是在一个寄存器内部,完成多个小数据单元的并行操作。

核心思路:

  • 每次读取 8 个字节,打包成一个 long(64 位)
  • 通过位运算和掩码,在这个 long 上并行执行加、乘等运算
  • 避免负数处理的溢出问题:给每个 byte +128,再统一做减法修正

示例代码:

java 复制代码
long x = getLong(b, k) ^ 0x8080808080808080L;
x = 31 * (x & 0x00FF00FF00FF00FFL) + ((x >>> 8) & 0x00FF00FF00FF00FFL);
x = P2 * (x & 0x0000FFFF0000FFFFL) + ((x >>> 16) & 0x0000FFFF0000FFFFL);
h = P8 * h + P4 * (int) x + (int) (x >>> 32) + U;

这段代码里用到了大量"无进位"运算技巧,将四组 byte 同时处理成中间结果,有效利用了 CPU 运算资源。

最终 SWAR 实现如下:

java 复制代码
static int hashCodeSWAR(byte[] b) {
  if (b == null) return 0;
  if (b.length == 0) return 1;
  if (b.length == 1) return 31 + b[0];
  int h = 1;
  int k = 0;
  for (; k <= b.length - 8; k += 8) {
    long x = getLong(b, k) ^ 0x8080808080808080L;
    x = 31 * (x & 0x00FF00FF00FF00FFL) + ((x >>> 8) & 0x00FF00FF00FF00FFL);
    x = P2 * (x & 0x0000FFFF0000FFFFL) + ((x >>> 16) & 0x0000FFFF0000FFFFL);
    h = P8 * h + P4 * (int) x + (int) (x >>> 32) + U;
  }
  return finalize(h, b, k);
}

优化二:Java SIMD(向量化 API)实现

Java 从 JDK 16 起提供了 Vector API,用来显式操作 SIMD 指令。

他们使用 Vector API,直接在 Java 层实现了一版 SIMD 版本的哈希计算,比 OpenJDK intrinsic 还快!

实现亮点:

  • 使用 Vector API 加载 byte[]
  • 先 reinterpret 为 short 做无符号加法
  • 再 reinterpret 为 int 执行 31 的乘法链
  • 最终通过向量归约计算出哈希结果

示例代码:

java 复制代码
var s = ByteVector.fromArray(ByteVector.SPECIES_PREFERRED, b, k)
        .lanewise(XOR, (byte) 0x80)
        .reinterpretAsShorts();

var i = s.and((short) 0xFF)
        .mul((short) 31)
        .add(s.lanewise(LSHR, 8))
        .reinterpretAsInts();

a = a.add(i.and(0xFFFF).mul(P2).add(i.lanewise(LSHR, 16)));

最后的修正:

java 复制代码
int h = (1 + a.mul(W).reduceLanes(ADD)) * F[remaining];

其中 F[] 是为了解决补零后 hash 值偏差而预先计算的逆元数组。


总结:JDK 自带的 hashCode 实现并不够快!

这篇文章提供了两个高性能替代方案:

实现方式 优势
SWAR 无需依赖 SIMD,纯寄存器级并行操作
SIMD + Vector API 完全基于 Java Vector API,性能超越 Intrinsic 实现

👉 项目源码地址github.com/dynatrace-r...

如果你在做大量 byte[] 处理(如网络协议、缓存键、文件分片哈希等),不妨试试这些优化方式。你会惊喜地发现,Java 的性能天花板,其实还可以再抬高一点。

原文地址:mp.weixin.qq.com/s/CZWiGU_4F...

本文由博客一文多发平台 OpenWrite 发布!

相关推荐
Cao_Shixin攻城狮23 分钟前
Flutter运行Android项目时显示java版本不兼容(Unsupported class file major version 65)的处理
android·java·flutter
fouryears_234175 小时前
Spring,Spring Boot 和 Spring MVC 的关系以及区别
java·spring boot·spring·mvc
阿葱(聪)5 小时前
java 在k8s中的部署流程
java·开发语言·docker·kubernetes
浮生带你学Java6 小时前
2025Java面试题及答案整理( 2025年 7 月最新版,持续更新)
java·开发语言·数据库·面试·职场和发展
板板正6 小时前
SpringAI——提示词(Prompt)、提示词模板(PromptTemplate)
java·spring boot·ai·prompt
板板正6 小时前
SpringAI——对话记忆
java·spring boot·ai
期待のcode6 小时前
图片上传实现
java·前端·javascript·数据库·servlet·交互
李长渊哦7 小时前
深入理解Java中的Map.Entry接口
java·开发语言
夜月蓝汐7 小时前
JAVA中的Collection集合及ArrayList,LinkedLIst,HashSet,TreeSet和其它实现类的常用方法
java·开发语言