有没有办法使用Java流将十六进制字符串转换为字节?

1 lambda hex stream java-8 java-stream

从长远来看,下面的代码片段总是会导致内存不足错误,尤其是在读取非常庞大的文件/内容时。

有没有另一种方法可以重写它,特别是使用流?

我在这里看到了一种将字节数组转换为十六进制字符串的方法: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);
    }
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 5

将十六进制字符串转换为字节数组的最简单方法是 JDK\xc2\xa017\xe2\x80\x99sHexFormat.parseHex(\xe2\x80\xa6)

\n
byte[] bytes = HexFormat.of().parseHex("c0ffeec0de");\nSystem.out.println(Arrays.toString(bytes));\nSystem.out.println(HexFormat.of().formatHex(bytes));\n
Run Code Online (Sandbox Code Playgroud)\n
[-64, -1, -18, -64, -34]\nc0ffeec0de\n
Run Code Online (Sandbox Code Playgroud)\n

这是最方便的方法,因为还可以处理格式化输入,例如

\n
byte[] bytes = HexFormat.ofDelimiter(" ").withPrefix("0x")\n    .parseHex("0xc0 0xff 0xee 0xc0 0xde");\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,如果您必须处理整个文件,即使是直接的文件

\n
String s = Files.readString(pathToYourFile);\nbyte[] bytes = HexFormat.of().parseHex(s);\n
Run Code Online (Sandbox Code Playgroud)\n

只要您有足够的临时内存,就可以以合理的性能运行。如果满足先决条件(基于 ASCII 的字符集和十六进制字符串就是这种情况),readString方法将读入一个数组,该数组将成为结果 string\xe2\x80\x99s 后备缓冲区。换句话说,跳过了其他方法固有的缓冲区之间的隐式复制。

\n

不过,\xe2\x80\x99s 花了一些时间来检查先决条件,我们可以跳过:

\n
String s = Files.readString(pathToYourFile, StandardCharsets.ISO_8859_1);\nbyte[] bytes = HexFormat.of().parseHex(s);\n
Run Code Online (Sandbox Code Playgroud)\n

这强制执行自 JDK\xc2\xa09 以来紧凑字符串使用的相同编码。由于十六进制字符串仅包含 ASCII 字符,因此它将正确解释字符集基于 ASCII 的所有源\xc2\xb9。仅对于不正确的来源,异常消息中可能会出现错误字符的误解。

\n

它\xe2\x80\x99很难打败它,如果使用 JDK\xc2\xa017 是一种选择,那么尝试替代方案是不值得的。但如果您使用的是较旧的 JDK,您可能会解析如下文件

\n
byte[] bytes;\ntry(FileChannel fch = FileChannel.open(pathToYourFile, StandardOpenOption.READ)) {\n    bytes = hexStringToBytes(fch.map(MapMode.READ_ONLY, 0, fch.size()));\n}\n
Run Code Online (Sandbox Code Playgroud)\n
public static byte[] hexStringToBytes(ByteBuffer hexBytes) {\n    byte[] bytes = new byte[hexBytes.remaining() >> 1];\n    for(int i = 0; i < bytes.length; i++)\n        bytes[i] = (byte)((Character.digit(hexBytes.get(), 16) << 4)\n                         | Character.digit(hexBytes.get(), 16));\n    return bytes;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这也利用了十六进制字符串是基于 ASCII 的事实,因此除非您使用相当不常见的字符集/编码,否则我们可以通过简化字符集转换来处理文件数据。如果\xe2\x80\x99s没有足够的物理内存来保存整个文件,这种方法也可以工作,但是,性能当然会降低。

\n

该文件也不得大于 2GiB 才能使用单个内存映射操作。在多个内存映射步骤中执行操作是可能的,但是您很快就会遇到结果的数组长度限制,因此如果这是一个问题,您无论如何都必须重新考虑整个方法。

\n

\xc2\xb9 因此,\xe2\x80\x99 不适用于 UTF-16 或 EBCDIC,这是您在现实生活中可能必须处理的唯一两个反例,尽管即使这些也非常罕见。

\n