1. 简介

十六进制字符串在编程中常用于以可读形式表示二进制数据。因此,在处理底层数据、加密算法或网络协议时,经常需要将十六进制字符串转换为字节数组。幸运的是,Kotlin 提供了多种高效的方式来完成这一转换。

本文将介绍几种在 Kotlin 中将十六进制字符串转换为字节数组的常用方法,帮助你在实际开发中灵活选择合适方案,避免踩坑。

2. 使用 for 循环手动实现

最直观的方式是通过 for 循环逐对解析十六进制字符。这种方式虽然代码稍多,但逻辑清晰,适合理解底层原理。

fun hexStringToByteArrayCustom(hex: String): ByteArray {
    val length = hex.length
    val byteArray = ByteArray(length / 2)
    for (i in byteArray.indices) {
        val index = i * 2
        val byte = hex.substring(index, index + 2).toInt(16).toByte()
        byteArray[i] = byte
    }
    return byteArray
}

说明:

  • 创建长度为原字符串一半的 ByteArray(每两个 hex 字符对应一个字节)
  • 遍历每个索引,提取两个字符组成的子串
  • 使用 toInt(16) 将其解析为十进制整数,再转为 Byte

⚠️ 注意: 此方法假设输入字符串长度为偶数且仅包含合法十六进制字符,否则会抛出异常。生产环境需加校验。

测试用例验证正确性:

@Test
fun `convert hex string to byte array using for loop`() {
    val hexString = "48656C6C6F20576F726C64"
    val expectedByteArray = byteArrayOf(72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100)

    assertArrayEquals(expectedByteArray, hexStringToByteArrayUsingForLoop(hexString))
}

输出对应 ASCII 字符串 "Hello World",结果正确。

3. 使用标准库方法(chunked + map)

Kotlin 标准库提供了函数式风格的简洁写法,结合 chunked(2)map 可一行搞定。

fun hexStringToByteArrayUsingStandardLibraryMethods(hex: String): ByteArray {
    return hex.chunked(2)
      .map { it.toInt(16).toByte() }
      .toByteArray()
}

优点:

  • 代码简洁,易读
  • 利用了 Kotlin 强大的集合操作 API

缺点:

  • chunked 会创建中间列表,对超长字符串有轻微性能开销

测试验证:

@Test
fun `convert hex string to byte array using standard libraries methods`() {
    val hexString = "48656C6C6F20576F726C64"
    val expectedByteArray = byteArrayOf(72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100)

    assertArrayEquals(expectedByteArray, hexStringToByteArrayUsingStandardLibraryMethods(hexString))
}

4. 使用 hexToByteArray()(实验性 API)

Kotlin 在 kotlin.text 包中提供了一个实验性的扩展函数 hexToByteArray(),可以直接调用。

@OptIn(ExperimentalStdlibApi::class)
@Test
fun `convert hex string to byte array using hexToByteArray`() {
    val hexString = "48656C6C6F20576F726C64"
    val expectedByteArray = byteArrayOf(72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100)

    assertArrayEquals(expectedByteArray, hexString.hexToByteArray())
}

⚠️ 关键点:

  • 必须添加 @OptIn(ExperimentalStdlibApi::class) 注解才能使用
  • 属于实验性功能,API 不稳定,不建议在生产环境直接依赖
  • 内部实现做了输入校验和大小写兼容,相对安全

📌 建议仅用于原型或学习场景,正式项目慎用。

5. 使用 BigInteger 转换

对于非常长的十六进制字符串(如大数运算场景),可以借助 java.math.BigInteger 来处理。

fun hexStringToByteArrayUsingBigInteger(hexString: String): ByteArray {
    val bigInteger = BigInteger(hexString, 16)
    return bigInteger.toByteArray()
}

适用场景:

  • 处理超长 hex 字符串(如哈希值、公钥等)
  • 已有 BigInteger 上下文,无需额外引入依赖

⚠️ 注意事项:

  • BigInteger.toByteArray() 返回的结果可能多出一个符号位(补码表示),例如 "FF" 会变成 [0, -1] 而不是 [-1]
  • 若需精确匹配原始字节流,可能需要手动去除前导零或调整长度

测试示例:

@Test
fun `convert hex string to byte array using BigInteger`() {
    val hexString = "48656C6C6F20576F726C64"
    val expectedByteArray = byteArrayOf(72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100)
        
    assertArrayEquals(expectedByteArray, hexStringToByteArrayUsingBigInteger(hexString))
}

虽然此例通过,但在处理 0x80 以上高位字节时需特别小心符号扩展问题。

6. 总结与选型建议

方法 是否推荐 场景
✅ 手动 for 循环 ⚠️ 学习可用 理解原理,控制细节
chunked(2).map.toByte().toByteArray() ✅ 推荐 日常开发首选,简洁清晰
hexToByteArray() ⚠️ 慎用 实验性 API,稳定性差
BigInteger ✅ 特定场景 大数、密码学相关

📌 最终建议:

  • 一般业务场景优先使用 chunked + map 方案
  • 对性能敏感且频繁调用的场景,可封装一个无中间对象的循环版本
  • 注意输入合法性校验(长度为偶数、只含 0-9a-fA-F
  • 如需处理负数或固定长度字节流,注意 BigInteger 的符号位问题

所有示例代码已整理至 GitHub: https://github.com/baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-strings-5


原始标题:Convert Hex String to Byte Array in Kotlin