登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

Java HexFormat 怎样把字节数组转换为可读十六进制

来源:17golang原创

时间:2026-11-04 17:02:18 418浏览 收藏

在日志、协议调试或指纹展示中,byte[] 直接打印出来通常不适合阅读。Java 17 引入的 HexFormat 可以把字节数组转换成连续或带分隔符的十六进制文本,也能按同一格式解析回来。关键是:数组格式化用 formatHex,单个数值用 toHexDigits,二者的分隔符和前后缀规则不同。

官方 API 文档:https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/HexFormat.html

想得到可读结果,先用 HexFormat.ofDelimiter(":").withUpperCase() 构造格式,再调用 formatHex(bytes);需要回到字节数组时,必须用同一组 delimiter、prefix 和 suffix 调用 parseHex。

本文要点:①数组格式化与单值转换分开选 API;②配置对象是不可变副本,可以安全复用;③解析是严格格式匹配,不是“尽量猜测”。

先分清 HexFormat 的两种工作模式

HexFormat 同时覆盖“字节数组”和“primitive 单值”两类任务。字节数组使用 formatHex 或 parseHex,会处理每个字节的两位十六进制字符,以及可选的分隔符、前缀和后缀。单个 byte、int 或 long 使用 toHexDigits,它只关心大小写和固定宽度,不会套用数组格式的 delimiter。

因此,协议字段、指纹和日志行优先建立一个数组格式;需要把一个整数变成固定宽度十六进制时,才直接使用静态转换方法。不要因为方法名相近,就期待 toHexDigits(255) 自动输出数组格式或前缀。

用 formatHex 输出字节数组

最小配置是 HexFormat.of(),输出小写、无分隔符文本。要让日志中的字节边界更明显,可以使用 ofDelimiter,再通过 withUpperCase、withPrefix 或 withSuffix 返回新的格式对象:

import java.util.HexFormat;

public class HexFormatDemo {
    public static void main(String[] args) {
        // 用冒号分隔并使用大写,适合展示指纹或报文摘要。
        HexFormat fingerprint = HexFormat.ofDelimiter(":")
                .withUpperCase();
        byte[] bytes = {0x00, 0x01, 0x2a, (byte) 0xff};

        // formatHex 会为每个字节生成两位十六进制字符。
        String text = fingerprint.formatHex(bytes);
        System.out.println(text); // 00:01:2A:FF
    }
}

如果团队约定每个值带 #,可以继续调用 withPrefix("#")。这些 withXXX 方法返回副本,原来的 fingerprint 不会被修改,所以可以把格式对象定义为共享常量或注入到多个服务中。

HexFormat formatHex 将 byte 数组转换为带分隔符十六进制文本的静态说明图
图1:HexFormat.formatHex 的静态说明图,展示数组、格式配置与文本结果的关系。

用 parseHex 做严格往返

解析时不要只看字符串“像不像十六进制”。parseHex 会按照当前格式检查每个字节的前缀、两位十六进制字符、后缀和字节之间的分隔符;字符非法、分隔符缺失或前后缀不匹配时会抛出 IllegalArgumentException。最稳妥的做法是让产生文本和消费文本的两端共享同一个格式定义:

import java.util.Arrays;
import java.util.HexFormat;

public class HexRoundTrip {
    public static void main(String[] args) {
        // 解析规则必须与生成端保持一致:冒号分隔、每项带 #、使用小写。
        HexFormat format = HexFormat.ofDelimiter(":")
                .withPrefix("#")
                .withLowerCase();
        byte[] original = {0x10, 0x2a, (byte) 0xf0};
        String encoded = format.formatHex(original);

        try {
            // parseHex 只接受完全符合上述格式的文本。
            byte[] decoded = format.parseHex(encoded);
            System.out.println(Arrays.equals(original, decoded)); // true
        } catch (IllegalArgumentException ex) {
            // 把格式错误与业务层的校验失败分开记录。
            System.err.println("十六进制格式无效:" + ex.getMessage());
        }
    }
}

这个例子中,#10:#2a:#f0 可以成功解析;若改成 #10-#2a-#f0,分隔符不符就会失败。解析方法不负责替你删除空格、猜测分隔符或修复半个字节,因此外部输入要先明确协议规则。

HexFormat parseHex 合法往返与非法输入边界的静态说明图
图2:HexFormat.parseHex 的静态边界说明图,展示合法往返与非法输入的分流。

按规模和边界选择 API

小型日志可以直接调用 formatHex(byte[])。如果只需要数组的一段,使用带 fromIndex 和 toIndex 的重载,注意右端点是排他的,越界会抛出 IndexOutOfBoundsException。对于大数组或连续输出,formatHex(Appendable, byte[]) 可以写入 StringBuilder、Writer 等目标,减少一次完整中间字符串的压力。

还要记住三个边界:所有相关参数默认不能为 null;空数组格式化结果为空字符串;HexFormat 是不可变且线程安全的值类,比较配置应使用 equals,不要用 == 判断两个实例是否相同。

常见问题

为什么 toHexDigits 没有输出冒号或前缀?因为它服务于单个 primitive 的固定宽度转换,只读取大小写设置;delimiter、prefix 和 suffix 只参与数组格式化与解析。

解析时能不能接受大小写混用?十六进制字符本身按大小写不敏感解析,但 delimiter、prefix、suffix 以及它们出现的位置仍必须符合当前格式。若输入来自用户或外部协议,应先定义是否允许空白和其他包装字符,而不是依赖 parseHex 猜测。

把 HexFormat 当成“格式契约”而不只是转换工具,最容易维护:生产端固定配置,消费端复用同一配置;展示需求用 formatHex,单值需求用 toHexDigits,非法输入则在 parseHex 边界显式处理。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>