1. 概述

十六进制表示法因其可读性强、表达紧凑,在二进制数据的文本化表示中被广泛使用。为了简化相关操作,Kotlin 在标准库中引入了 HexFormat API,用于将数据格式化为十六进制字符串,以及反向解析。

本文将深入探讨 HexFormat API 的核心能力,并通过实际用例帮助你避免常见“坑点”。

✅ 适用场景:日志输出、协议编解码、MAC/IP 地址处理、调试信息展示等需要二进制与文本互转的场合。

2. 核心概念

在使用前,先理解其底层设计,避免后续踩坑。

2.1. HexFormat 类

HexFormatkotlin.text 包中的核心配置类,定义了整个十六进制格式化的规则集合。其结构如下:

@ExperimentalStdlibApi
@SinceKotlin("1.9")
public class HexFormat internal constructor(
    val upperCase: Boolean,
    val bytes: BytesHexFormat,
    val number: NumberHexFormat
) {
    // 其他方法和属性
}

⚠️ 注意:

  • 带有 @ExperimentalStdlibApi 注解,属于实验性 API。
  • **使用时必须添加 @OptIn(ExperimentalStdlibApi::class)**,否则编译报错。

它包含三个关键属性:

  • upperCase:是否使用大写字母(A-F)。
  • bytes:针对 ByteArray 的格式化规则。
  • number:针对数值类型(如 Byte、Int)的格式化规则。

2.2. NumberHexFormat 与 BytesHexFormat

NumberHexFormat

控制数值转十六进制的行为:

public class NumberHexFormat internal constructor(
    val prefix: String,
    val suffix: String,
    val removeLeadingZeros: Boolean
)

可配置项:

  • prefix / suffix:前后缀,如 "0x""%"
  • removeLeadingZeros:是否去除前导零,对 Long 等宽类型尤其有用。

BytesHexFormat

专用于 ByteArray 的布局控制:

public class BytesHexFormat internal constructor(
    val bytesPerLine: Int,
    val bytesPerGroup: Int,
    val groupSeparator: String,
    val byteSeparator: String,
    val bytePrefix: String,
    val byteSuffix: String
)

常用配置:

  • bytesPerGroup:每组字节数(如 MAC 地址每 1 字节一组)。
  • groupSeparator:组间分隔符(如 ":""-")。
  • byteSeparator:字节内分隔符(较少用)。
  • bytesPerLine:每行最多字节数(用于美化长数据输出)。

掌握这些配置,就能灵活应对各种格式需求。

3. 数值转十六进制

如何将 ByteIntLong 等类型转为 hex 字符串。

3.1. 默认格式

val number: Byte = 63
assertEquals("3f", number.toHexString())

✅ 调用 toHexString() 时若未传参,会自动使用 HexFormat.Default 实例。

也可显式传入:

assertEquals("3f", number.toHexString(HexFormat.Default))

该扩展函数支持所有整型:ByteShortIntLong

3.2. 添加前缀

某些场景(如 URL 编码)需添加特定前缀:

val prefixNumberFormat = HexFormat {
    number.prefix = "%"
}

使用自定义格式:

assertEquals("%3f", number.toHexString(prefixNumberFormat))

输出结果自动带上 % 前缀。

3.3. 去除前导零

对于 Long 类型,默认会补足 16 位(8 字节):

val number: Long = 63
assertEquals("000000000000003f", number.toHexString()) // ❌ 太冗长

通过配置去除前导零:

val compactFormat = HexFormat {
    number {
        removeLeadingZeros = true
    }
}
assertEquals("3f", number.toHexString(compactFormat)) // ✅ 干净利落

这在日志或 UI 展示中非常实用。

4. 十六进制解析为数值

使用 hexTo<Type>() 系列扩展函数将 hex 字符串还原为数值。

基础用法:

assertEquals(63, "3f".hexToByte())

⚠️ 陷阱:若字符串含自定义前缀(如 %3f),直接解析会抛 IllegalArgumentException

assertThrows<IllegalArgumentException> { "%3f".hexToByte() }

原因:默认使用 HexFormat.Default,不识别 %

正确做法是传入对应的 HexFormat 实例:

assertEquals(63, "%3f".hexToByte(prefixNumberFormat))

✅ 只要格式匹配,即可成功解析。

5. ByteArray 转十六进制

典型用例:格式化 MAC 地址。

定义 MAC 地址专用格式:

val macAddressFormat = HexFormat {
    upperCase = true          // 使用大写
    bytes.bytesPerGroup = 1   // 每组 1 字节
    bytes.groupSeparator = ":" // 组间用冒号分隔
}

准备原始字节数组(注意负数表示):

val macAddressBytes = byteArrayOf(2, 66, -64, -117, -14, 94)

转换为 hex 字符串:

val macAddressHexValue = macAddressBytes.toHexString(macAddressFormat)
assertEquals("02:42:C0:8B:F2:5E", macAddressHexValue)

✅ 输出符合标准 MAC 地址格式。

6. 十六进制解析为 ByteArray

将格式化的 hex 字符串还原为字节数组。

val macAddressHexValue = "02:42:C0:8B:F2:5E"

关键:必须传入与格式化时相同的 HexFormat 实例,否则无法识别分隔符:

val macAddressBytes = macAddressHexValue.hexToByteArray(macAddressFormat)

验证结果:

assertArrayEquals(byteArrayOf(2, 66, -64, -117, -14, 94), macAddressBytes)

⚠️ 若传错或不传 HexFormat,会抛 NumberFormatException

7. 总结

HexFormat API 提供了一套类型安全、配置灵活的十六进制编解码方案。核心要点:

  • ✅ 使用 toHexString()hexTo<Type>() 进行格式化与解析。
  • ✅ 自定义 HexFormat 实例以支持前缀、分隔符、大小写等需求。
  • ⚠️ 实验性 API 需 @OptIn(ExperimentalStdlibApi::class)
  • ⚠️ 解析时务必传入正确的 HexFormat,否则易出错。

代码示例已上传至 GitHub:Baeldung Kotlin 教程仓库。建议集合备用。


原始标题:Guide to the HexFormat API