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 上获得。