Java中字节数组和十六进制字符串之间的转换

 

1. 概述

在本教程中,我们将看看将字节数组转换为十六进制字符串,反之亦然的不同方法。

我们还将了解转换机制并编写我们的实现来实现这一点。

2. 字节和十六进制之间的转换

首先,我们来看看字节和十六进制数之间的转换逻辑。

2.1. 字节到十六进制

字节是 Java 中的 8 位有符号整数。因此,我们需要将每个 4 位段分别转换为十六进制并将它们连接起来。因此,我们将在转换后得到两个十六进制字符。

例如,我们可以用二进制将 45 写为 0010 1101,而十六进制的等价物将是“2d”:

0010 = 2 (base 10) = 2 (base 16)
1101 = 13 (base 10) = d (base 16)

Therefore: 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 位来隔离高阶位。然后,我们应用了一个掩码来隔离低阶 4 位。需要屏蔽,因为负数在内部表示为正数的二进制补码:

hexDigits[0] = Character.forDigit((num >> 4) & 0xF, 16);

然后我们将剩余的 4 位转换为十六进制:

hexDigits[1] = Character.forDigit((num & 0xF), 16);

最后,我们 从 char 数组创建一个 String对象。然后,将此对象作为转换后的十六进制数组返回。

现在,让我们了解这对于负字节 -4 是如何工作的:

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

Therefore: -4 (base 10) = 1111 1100 (base 2) = fc (base 16)

还值得注意的是,Character. forDigit ()方法总是返回小写字符。

2.2. 十六进制转字节

现在,让我们将十六进制数字转换为字节。众所周知,一个字节包含 8 位。因此,我们需要两个十六进制数字来创建一个字节

首先,我们将每个十六进制数字分别转换为二进制等价物。

然后,我们需要连接两个四位段以获得等效的字节:

Hexadecimal: 2d
2 = 0010 (base 2)
d = 1101 (base 2)

Therefore: 2d = 0010 1101 (base 2) = 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 位。因此,二进制表示在四个最低有效位处具有零。

然后,我们将最低有效数字添加到它:

return (byte) ((firstDigit << 4) + secondDigit);

现在,让我们仔细检查toDigit()方法。我们使用 Character.digit()方法进行转换。如果传递给此方法的字符值不是指定基数中的有效数字,则返回 -1。

我们正在验证返回值并在传递无效值时抛出异常。

3. 字节数组和十六进制字符串之间的转换

至此,我们知道如何将字节转换为十六进制,反之亦然。让我们扩展这个算法并将字节数组转换为/从十六进制String

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

我们需要遍历数组并为每个字节生成十六进制对:

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

我们可以通过传递一个符号和字节数组创建一个BigInteger类型的对象

现在,我们可以借助String类中定义的静态方法格式生成十六进制字符串

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

提供的格式将生成一个以零填充的小写十六进制字符串。我们还可以通过将“x”替换为“X”来生成大写字符串。

或者,我们可以使用BigInteger 中toString()方法。使用toString()方法的细微差别 在于输出没有用前导零填充

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

现在,让我们来看看十六进制字符串字节数组的转换:

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()方法产生的附加符号位。我们已经编写了专门的代码来处理这个额外的位。

因此,在使用BigInteger 类进行转换之前,我们应该了解这些细节。

5. 使用 DataTypeConverter

DataTypeConverter 类与JAXB库提供。在 Java 8 之前,这是标准库的一部分。从 Java 9 开始,我们需要显式地将java.xml.bind模块添加到运行时。

让我们看一下使用DataTypeConverter类的实现:

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

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

如上所示,使用DataTypeConverter 类非常方便。printHexBinary()方法输出总是大写的。此类提供一组用于数据类型转换的打印和解析方法。

在选择这种方法之前,我们需要确保该类在运行时可用。

6. 使用 Apache 的 Commons-Codec 库

我们可以使用 随 Apache commons-codec 库提供的 Hex类:

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

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

encodeHexString输出总是小写

7. 使用谷歌的番石榴库

让我们来看看BaseEncoding类如何用于将字节数组编码和解码为十六进制 字符串:

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

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

该 BaseEncoding编码和解码使用大写字符默认。如果我们需要使用小写字符,则应使用静态方法lowercase创建一个新的编码实例。

8. 结论

在本文中,我们学习了字节数组到十六进制字符串的转换算法。我们还讨论了将字节数组编码为十六进制字符串的各种方法,反之亦然。

不建议添加一个库来仅使用几个实用程序方法。因此,如果我们还没有使用外部库,我们应该使用所讨论的算法。所述DataTypeConverter 类是另一种不同的数据类型之间的编码/解码。

最后,本教程的完整源代码可在 GitHub 上获得

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值