1. 概述

本文将带你了解 Java 中将字节数组转换为十六进制字符串(hex string)的各种方式,以及反向操作的实现方式。

我们会从原理入手,逐步实现转换逻辑,并介绍几种常见的库级实现方案,帮助你根据场景选择最合适的方案。

2. 字节与十六进制之间的转换原理

2.1. 字节转十六进制

Java 中的 byte 是一个有符号的 8 位整数。因此,要将其转为十六进制字符串,我们需要 将高 4 位和低 4 位分别转为十六进制字符,然后拼接起来。这样每个字节会生成两个十六进制字符。

举个例子,45 的二进制是 0010 1101,其十六进制表示就是 2d

0010 = 2 (十进制) = 2 (十六进制)
1101 = 13 (十进制) = d (十六进制)

所以:45 = 0010 1101 = 0x2d

下面是 Java 实现:

public String byteToHex(byte num) {
    char[] hexDigits = new char[2];
    hexDigits[0] = Character.forDigit((num >> 4) & 0xF, 16);
    hexDigits[1] = Character.forDigit((num & 0xF), 16);
    return new String(hexDigits);
}

我们来逐行分析:

  • 创建一个长度为 2 的 char 数组,用于存放两个十六进制字符:

    char[] hexDigits = new char[2];
    
  • 提取高 4 位,并使用掩码 0xF 避免负数的符号扩展问题:

    hexDigits[0] = Character.forDigit((num >> 4) & 0xF, 16);
    
  • 提取低 4 位:

    hexDigits[1] = Character.forDigit((num & 0xF), 16);
    
  • 最后将 char 数组转为字符串返回。

对于负数 -4(二进制为 1111 1100)的处理如下:

hexDigits[0]:
1111 1100 >> 4 = 1111 1111 1111 1111 1111 1111 1111 1111
1111 1111 1111 1111 1111 1111 1111 1111 & 0xF = 0000 0000 0000 0000 0000 0000 0000 1111 = 0xf

hexDigits[1]:
1111 1100 & 0xF = 0000 1100 = 0xc

所以:-4 = 1111 1100 = fc

⚠️ 注意:Character.forDigit() 方法返回的字符是小写形式。

2.2. 十六进制转字节

每个字节是 8 位,因此我们需要两个十六进制字符来表示一个字节。

举个例子:

Hexadecimal: 2d
2 = 0010 (二进制)
d = 1101 (二进制)

所以:2d = 0010 1101 = 45

Java 实现如下:

public byte hexToByte(String hexString) {
    int firstDigit = toDigit(hexString.charAt(0));
    int secondDigit = toDigit(hexString.charAt(1));
    return (byte) ((firstDigit << 4) + secondDigit);
}

private int toDigit(char hexChar) {
    int digit = Character.digit(hexChar, 16);
    if(digit == -1) {
        throw new IllegalArgumentException(
          "Invalid Hexadecimal Character: "+ hexChar);
    }
    return digit;
}

分析:

  • 提取两个字符并转为数字:

    int firstDigit = toDigit(hexString.charAt(0));
    int secondDigit = toDigit(hexString.charAt(1));
    
  • 左移高 4 位,然后加上低 4 位:

    return (byte) ((firstDigit << 4) + secondDigit);
    
  • toDigit() 方法使用 Character.digit() 来转换字符。如果字符不是合法的十六进制字符,返回 -1,我们在这里抛出异常。

3. 字节数组与十六进制字符串的转换

3.1. 字节数组转十六进制字符串

遍历字节数组,对每个字节调用 byteToHex 方法:

public String encodeHexString(byte[] byteArray) {
    StringBuffer hexStringBuffer = new StringBuffer();
    for (int i = 0; i < byteArray.length; i++) {
        hexStringBuffer.append(byteToHex(byteArray[i]));
    }
    return hexStringBuffer.toString();
}

✅ 输出始终是小写。

3.2. 十六进制字符串转字节数组

首先判断字符串长度是否为偶数(否则无法成对解析),然后每两个字符组成一个字节:

public byte[] decodeHexString(String hexString) {
    if (hexString.length() % 2 == 1) {
        throw new IllegalArgumentException(
          "Invalid hexadecimal String supplied.");
    }
    
    byte[] bytes = new byte[hexString.length() / 2];
    for (int i = 0; i < hexString.length(); i += 2) {
        bytes[i / 2] = hexToByte(hexString.substring(i, i + 2));
    }
    return bytes;
}

4. 使用 BigInteger 实现转换

4.1. 字节数组转十六进制字符串

public String encodeUsingBigIntegerStringFormat(byte[] bytes) {
    BigInteger bigInteger = new BigInteger(1, bytes);
    return String.format(
      "%0" + (bytes.length << 1) + "x", bigInteger);
}

✅ 输出是小写,且补零对齐。

也可以使用 toString(16),但不会补零:

public String encodeUsingBigIntegerToString(byte[] bytes) {
    BigInteger bigInteger = new BigInteger(1, bytes);
    return bigInteger.toString(16);
}

4.2. 十六进制字符串转字节数组

public byte[] decodeUsingBigInteger(String hexString) {
    byte[] byteArray = new BigInteger(hexString, 16)
      .toByteArray();
    if (byteArray[0] == 0) {
        byte[] output = new byte[byteArray.length - 1];
        System.arraycopy(
          byteArray, 1, output, 
          0, output.length);
        return output;
    }
    return byteArray;
}

⚠️ 注意:toByteArray() 会多出一个符号位,需要手动处理。

5. 使用 JAXB 的 DatatypeConverter

public String encodeUsingDataTypeConverter(byte[] bytes) {
    return DatatypeConverter.printHexBinary(bytes);
}

public byte[] decodeUsingDataTypeConverter(String hexString) {
    return DatatypeConverter.parseHexBinary(hexString);
}

✅ 输出是大写。

⚠️ Java 9+ 需要显式引入 java.xml.bind 模块。

6. 使用 Apache Commons Codec

public String encodeUsingApacheCommons(byte[] bytes) 
  throws DecoderException {
    return Hex.encodeHexString(bytes);
}

public byte[] decodeUsingApacheCommons(String hexString) 
  throws DecoderException {
    return Hex.decodeHex(hexString);
}

✅ 输出是小写。

7. 使用 Google Guava

public String encodeUsingGuava(byte[] bytes) {
    return BaseEncoding.base16().encode(bytes);
}

public byte[] decodeUsingGuava(String hexString) {
    return BaseEncoding.base16()
      .decode(hexString.toUpperCase());
}

✅ 默认是大写编码,如需小写可使用 .lowerCase()

8. Java 17 新特性:HexFormat

8.1. 字节数组转十六进制字符串

public String encodeUsingHexFormat(byte[] bytes) {
    HexFormat hexFormat = HexFormat.of();
    return hexFormat.formatHex(bytes);
}

8.2. 十六进制字符串转字节数组

public byte[] decodeUsingHexFormat(String hexString) {
    HexFormat hexFormat = HexFormat.of();
    return hexFormat.parseHex(hexString);
}

✅ Java 17 引入的标准 API,简单直观。

9. 总结

方法 优点 缺点
手动实现 无依赖,可控性强 代码量稍大
BigInteger 简洁 需处理符号位
DatatypeConverter 简单易用 Java 9+ 需模块支持
Commons Codec 稳定成熟 需引入依赖
Guava 功能丰富 需引入依赖
HexFormat (Java 17) 标准化,简洁 JDK 版本要求高

✅ 如果你没有使用外部库,推荐手动实现;如果项目已引入相关库,直接使用即可。避免为了两个工具方法而引入整个库。



原始标题:Converting Between Byte Arrays and Hexadecimal Strings in Java | Baeldung