如何检查字节数组是否包含 Java 中的 Unicode 字符串?

2022-09-03 06:50:20

给定一个字节数组,该数组要么是 UTF-8 编码的字符串,要么是任意二进制数据,那么在 Java 中可以使用哪些方法来确定它是哪种?

数组可以由类似于以下内容的代码生成:

byte[] utf8 = "Hello World".getBytes("UTF-8");

或者,它可能是由类似于以下内容的代码生成的:

byte[] messageContent = new byte[256];
for (int i = 0; i < messageContent.length; i++) {
    messageContent[i] = (byte) i;
}

关键的一点是,我们不知道数组包含什么,但需要找出来才能填写以下函数:

public final String getString(final byte[] dataToProcess) {
    // Determine whether dataToProcess contains arbitrary data or a UTF-8 encoded string
    // If dataToProcess contains arbitrary data then we will BASE64 encode it and return.
    // If dataToProcess contains an encoded string then we will decode it and return.
}

如何扩展以涵盖 UTF-16 或其他编码机制?


答案 1

不可能在所有情况下都完全准确地做出该决定,因为 UTF-8 编码的字符串一种任意二进制数据,但您可以查找在 UTF-8 中无效的字节序列。如果你找到任何,你知道它不是UTF-8。

如果数组足够大,这应该可以很好地工作,因为这样的序列很可能出现在“随机”二进制数据(如压缩数据或图像文件)中。

但是,可以获得有效的UTF-8数据,这些数据可以解码为完全荒谬的字符串(可能来自各种不同的脚本)。对于短序列,这种情况更有可能发生。如果您担心这一点,则可能必须进行更仔细的分析,以查看字母字符是否都属于同一代码图。再说一遍,当您具有混合脚本的有效文本输入时,这可能会产生漏报。


答案 2

以下是使用 W3C 站点中的 UTF-8“二进制”正则表达式的方法

static boolean looksLikeUTF8(byte[] utf8) throws UnsupportedEncodingException 
{
  Pattern p = Pattern.compile("\\A(\n" +
    "  [\\x09\\x0A\\x0D\\x20-\\x7E]             # ASCII\\n" +
    "| [\\xC2-\\xDF][\\x80-\\xBF]               # non-overlong 2-byte\n" +
    "|  \\xE0[\\xA0-\\xBF][\\x80-\\xBF]         # excluding overlongs\n" +
    "| [\\xE1-\\xEC\\xEE\\xEF][\\x80-\\xBF]{2}  # straight 3-byte\n" +
    "|  \\xED[\\x80-\\x9F][\\x80-\\xBF]         # excluding surrogates\n" +
    "|  \\xF0[\\x90-\\xBF][\\x80-\\xBF]{2}      # planes 1-3\n" +
    "| [\\xF1-\\xF3][\\x80-\\xBF]{3}            # planes 4-15\n" +
    "|  \\xF4[\\x80-\\x8F][\\x80-\\xBF]{2}      # plane 16\n" +
    ")*\\z", Pattern.COMMENTS);

  String phonyString = new String(utf8, "ISO-8859-1");
  return p.matcher(phonyString).matches();
}

正如最初所写的,正则表达式应该用于字节数组,但你不能用Java的正则表达式来做到这一点;目标必须是实现CharSequence接口的东西(所以a也出来了)。通过将 解码为 ISO-8859-1,可以创建一个字符串,其中每个字符串都具有与原始数组中相应字节相同的无符号数值。char[]byte[]char

正如其他人所指出的,像这样的测试只能告诉你可以包含UTF-8文本,而不是它但是正则表达式是如此详尽,原始二进制数据似乎极不可能滑过它。即使所有零的数组也不匹配,因为正则表达式永远不会匹配 。如果唯一的可能性是UTF-8和二进制,我愿意相信这个测试。byte[]NUL

当你在它的时候,你可以剥离UTF-8 BOM,如果有的话;否则,UTF-8 字符集解码器将像文本一样传递它。

UTF-16 将更加困难,因为很少有字节序列始终无效。我能想到的唯一一个是缺少低代理伴侣的高代理字符,反之亦然。除此之外,您还需要一些上下文来决定给定序列是否有效。你可能有一个西里尔字母,后面跟着一个中国表意文字,后面跟着一个笑脸叮当,但它是完全有效的UTF-16。