有没有办法使用 Java 流将十六进制字符串转换为字节?
Is there a way to convert Hex string to bytes using Java streams?
下面长 运行 中的代码片段总是会导致内存不足错误,尤其是从非常庞大的 file/content 中读取时。
有没有另一种方法可以重写这个,尤其是使用流?
我在这里看到了一种将字节数组转换为十六进制字符串的方法:Effective way to get hex string from a byte array using lambdas and streams
public static byte[] hexStringToBytes(String hexString) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Hex string to convert to byte[] " + hexString);
}
byte[] buf = new byte[hexString.length() / 2];
String twoDigitHexToConvertToByte;
for (int i = 0; i < buf.length; i++) {
twoDigitHexToConvertToByte = extractPairFromStringBasedOnIndex(hexString, i);
parseStringToBytesAndStoreInArrayOnIndex(twoDigitHexToConvertToByte, buf, i);
}
return buf;
}
private static void parseStringToBytesAndStoreInArrayOnIndex(String twoDigitHexToConvertToByte, byte[] buf, int i) {
try {
buf[i] = (byte) Integer.parseInt(twoDigitHexToConvertToByte, HEX_RADIX);
} catch (NumberFormatException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.info("Tried to convert non hex string:", e);
} else {
LOGGER.info("Tried to convert non hex string:" + e.getMessage());
}
throw new HexStringToBytesException("Tried to convert non hex string"); // NOSONAR xlisjov don't want original cause since it caused exceptions.
}
}
private static String extractPairFromStringBasedOnIndex(String hexString, int pairNumber) {
return hexString.substring(2 * pairNumber, 2 * pairNumber + 2);
}
将十六进制字符串转换为字节数组的最简单方法是 JDK 17 的 HexFormat.parseHex(…)
。
byte[] bytes = HexFormat.of().parseHex("c0ffeec0de");
System.out.println(Arrays.toString(bytes));
System.out.println(HexFormat.of().formatHex(bytes));
[-64, -1, -18, -64, -34]
c0ffeec0de
这是最方便的方法,也可以处理格式化输入,例如
byte[] bytes = HexFormat.ofDelimiter(" ").withPrefix("0x")
.parseHex("0xc0 0xff 0xee 0xc0 0xde");
请注意,如果您必须处理整个文件,即使是直接的
String s = Files.readString(pathToYourFile);
byte[] bytes = HexFormat.of().parseHex(s);
可能运行具有合理的性能,只要你有足够的临时内存。如果满足前提条件,即基于 ASCII 的字符集和十六进制字符串的情况,readString
方法将读入一个数组,该数组将成为结果字符串的后备缓冲区。换句话说,跳过了缓冲区之间的隐式复制,这是其他方法所固有的。
不过检查先决条件需要花费一些时间,我们可以跳过:
String s = Files.readString(pathToYourFile, StandardCharsets.ISO_8859_1);
byte[] bytes = HexFormat.of().parseHex(s);
自 JDK9 以来,这会强制执行紧凑字符串所使用的相同编码。由于十六进制字符串仅包含 ASCII 字符,因此它将正确解释其字符集基于 ASCII 的所有来源¹。仅对于不正确的来源,异常消息中可能会出现错误字符的错误解释。
很难击败它,如果使用 JDK 17 是一种选择,那么尝试替代方案是不值得的。但是如果你使用的是旧的JDK,你可能会解析一个像
这样的文件
byte[] bytes;
try(FileChannel fch = FileChannel.open(pathToYourFile, StandardOpenOption.READ)) {
bytes = hexStringToBytes(fch.map(MapMode.READ_ONLY, 0, fch.size()));
}
public static byte[] hexStringToBytes(ByteBuffer hexBytes) {
byte[] bytes = new byte[hexBytes.remaining() >> 1];
for(int i = 0; i < bytes.length; i++)
bytes[i] = (byte)((Character.digit(hexBytes.get(), 16) << 4)
| Character.digit(hexBytes.get(), 16));
return bytes;
}
这也利用了十六进制字符串基于 ASCII 的事实,因此除非您使用相当不常见的 charset/encoding,否则我们可以处理文件数据以缩短字符集转换。如果没有足够的物理内存来保存整个文件,这种方法也可以使用,但是,性能当然会降低。
文件也不能大于 2GiB 才能使用单个内存映射操作。可以在多个内存映射步骤中执行操作,但您很快就会 运行 进入结果的数组长度限制,因此如果这是一个问题,您必须重新考虑整个方法。
¹ 所以这对 UTF-16 和 EBCDIC 都不起作用,这是您在现实生活中可能必须处理的仅有的两个反例,尽管这些都非常罕见。
下面长 运行 中的代码片段总是会导致内存不足错误,尤其是从非常庞大的 file/content 中读取时。
有没有另一种方法可以重写这个,尤其是使用流?
我在这里看到了一种将字节数组转换为十六进制字符串的方法:Effective way to get hex string from a byte array using lambdas and streams
public static byte[] hexStringToBytes(String hexString) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Hex string to convert to byte[] " + hexString);
}
byte[] buf = new byte[hexString.length() / 2];
String twoDigitHexToConvertToByte;
for (int i = 0; i < buf.length; i++) {
twoDigitHexToConvertToByte = extractPairFromStringBasedOnIndex(hexString, i);
parseStringToBytesAndStoreInArrayOnIndex(twoDigitHexToConvertToByte, buf, i);
}
return buf;
}
private static void parseStringToBytesAndStoreInArrayOnIndex(String twoDigitHexToConvertToByte, byte[] buf, int i) {
try {
buf[i] = (byte) Integer.parseInt(twoDigitHexToConvertToByte, HEX_RADIX);
} catch (NumberFormatException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.info("Tried to convert non hex string:", e);
} else {
LOGGER.info("Tried to convert non hex string:" + e.getMessage());
}
throw new HexStringToBytesException("Tried to convert non hex string"); // NOSONAR xlisjov don't want original cause since it caused exceptions.
}
}
private static String extractPairFromStringBasedOnIndex(String hexString, int pairNumber) {
return hexString.substring(2 * pairNumber, 2 * pairNumber + 2);
}
将十六进制字符串转换为字节数组的最简单方法是 JDK 17 的 HexFormat.parseHex(…)
。
byte[] bytes = HexFormat.of().parseHex("c0ffeec0de");
System.out.println(Arrays.toString(bytes));
System.out.println(HexFormat.of().formatHex(bytes));
[-64, -1, -18, -64, -34]
c0ffeec0de
这是最方便的方法,也可以处理格式化输入,例如
byte[] bytes = HexFormat.ofDelimiter(" ").withPrefix("0x")
.parseHex("0xc0 0xff 0xee 0xc0 0xde");
请注意,如果您必须处理整个文件,即使是直接的
String s = Files.readString(pathToYourFile);
byte[] bytes = HexFormat.of().parseHex(s);
可能运行具有合理的性能,只要你有足够的临时内存。如果满足前提条件,即基于 ASCII 的字符集和十六进制字符串的情况,readString
方法将读入一个数组,该数组将成为结果字符串的后备缓冲区。换句话说,跳过了缓冲区之间的隐式复制,这是其他方法所固有的。
不过检查先决条件需要花费一些时间,我们可以跳过:
String s = Files.readString(pathToYourFile, StandardCharsets.ISO_8859_1);
byte[] bytes = HexFormat.of().parseHex(s);
自 JDK9 以来,这会强制执行紧凑字符串所使用的相同编码。由于十六进制字符串仅包含 ASCII 字符,因此它将正确解释其字符集基于 ASCII 的所有来源¹。仅对于不正确的来源,异常消息中可能会出现错误字符的错误解释。
很难击败它,如果使用 JDK 17 是一种选择,那么尝试替代方案是不值得的。但是如果你使用的是旧的JDK,你可能会解析一个像
这样的文件byte[] bytes;
try(FileChannel fch = FileChannel.open(pathToYourFile, StandardOpenOption.READ)) {
bytes = hexStringToBytes(fch.map(MapMode.READ_ONLY, 0, fch.size()));
}
public static byte[] hexStringToBytes(ByteBuffer hexBytes) {
byte[] bytes = new byte[hexBytes.remaining() >> 1];
for(int i = 0; i < bytes.length; i++)
bytes[i] = (byte)((Character.digit(hexBytes.get(), 16) << 4)
| Character.digit(hexBytes.get(), 16));
return bytes;
}
这也利用了十六进制字符串基于 ASCII 的事实,因此除非您使用相当不常见的 charset/encoding,否则我们可以处理文件数据以缩短字符集转换。如果没有足够的物理内存来保存整个文件,这种方法也可以使用,但是,性能当然会降低。
文件也不能大于 2GiB 才能使用单个内存映射操作。可以在多个内存映射步骤中执行操作,但您很快就会 运行 进入结果的数组长度限制,因此如果这是一个问题,您必须重新考虑整个方法。
¹ 所以这对 UTF-16 和 EBCDIC 都不起作用,这是您在现实生活中可能必须处理的仅有的两个反例,尽管这些都非常罕见。