String.format处理字节数组转十六进制易出错,因Java中byte有符号,负数提升为int时发生符号扩展,导致输出8位而非2位十六进制;正确做法是先& 0xFF消除符号扩展,再取高低4位查表转换。

为什么 String.format 处理字节数组转十六进制容易出错
因为 byte 在 Java 中是有符号的,范围是 -128 到 127。直接用 String.format("%02x", b) 时,负数会被解释为补码形式的大整数(比如 -1 变成 ffffffff),最终输出 8 位而非预期的 2 位十六进制。
- 错误现象:
new byte[]{(byte)0xFF}转出来是"ffffff"或"ffffffff",不是"ff" - 根本原因:
String.format对byte自动提升为int,但没做无符号转换 - 正确做法:先用
& 0xFF把高 24 位清零,强制得到 0–255 的值
用位运算实现高效、可控的 byte → hex 转换
核心就是两个操作:& 0xFF 提取低 8 位(消除符号扩展),再用 >> 4 和 & 0xF 分别取高 4 位和低 4 位,映射到 hex 字符。
-
int high = (b & 0xFF) >> 4—— 先无符号化,再右移取高半字节 -
int low = b & 0x0F—— 直接掩码取低半字节(注意:这里b & 0x0F安全,因为0x0F只有低 4 位,不会暴露符号位) - 查表比
Character.forDigit快,也避免StringBuilder动态扩容开销
char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; i++) {
int v = bytes[i] & 0xFF;
hexChars[i * 2] = HEX_ARRAY[v >>> 4];
hexChars[i * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}Apache Commons Codec 的 Hex.encodeHexString 值不值得用
它内部也是位运算实现,逻辑可靠,但引入依赖前得权衡两点:
- 如果你项目已依赖
commons-codec,直接调Hex.encodeHexString(bytes)省心,还支持大写(Hex.encodeHexString(bytes, true)) - 如果只是偶尔转 hex,加一个 300KB 的 jar 显得重;且默认返回小写,要大写得额外传参,不如自己 10 行搞定
- 注意:它的输出不含
0x前缀,也不带空格或分隔符,和手写逻辑行为一致
常见陷阱:UTF-8 字符串转 byte 再转 hex 时编码不一致
很多人想“把字符串变成 hex”,却漏掉编码环节。比如 "你好".getBytes() 用的是平台默认编码(Windows 可能是 GBK),不是 UTF-8,导致 hex 结果不可移植。
立即学习“Java免费学习笔记(深入)”;
- 务必显式指定编码:
"你好".getBytes(StandardCharsets.UTF_8) - 否则同一段代码,在 macOS 和 Windows 上输出完全不同的 hex 字符串
- 如果后续 hex 要被其他系统解析(比如前端 JS
Uint8Array.from(hexStr.match(/../g), h => parseInt(h, 16))),编码必须对齐
实际写的时候,最易忽略的是字节符号性和编码一致性——前者让 hex 多出一堆 ff,后者让同一字符串在不同机器上变出两套 hex。这两个点卡住,后面所有处理都白搭。










