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

Posted

技术标签:

【中文标题】如何检查字节数组是不是包含 Java 中的 Unicode 字符串?【英文标题】:How can I check whether a byte array contains a Unicode string in Java?如何检查字节数组是否包含 Java 中的 Unicode 字符串? 【发布时间】:2010-11-14 15:48:48 【问题描述】:

给定一个 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 或其他编码机制?

【问题讨论】:

一个类似的问题有一些来自 Edward Wilde 的有用链接 - ***.com/questions/377294/… 【参考方案1】:

在原始问题中:Java 中如何检查字节数组是否包含 Unicode 字符串?我发现术语 Java Unicode 本质上是指 Utf16 代码单元。我自己解决了这个问题并创建了一些代码,可以帮助任何有这类问题的人找到答案。

我创建了 2 个主要方法,一个将显示 Utf-8 代码单元,另一个将创建 Utf-16 代码单元。 Utf-16 代码单元是您在使用 Java 和 javascript 时会遇到的……通常以“\ud83d”的形式看到

有关代码单元和转换的更多帮助,请访问网站;

https://r12a.github.io/apps/conversion/

这是代码...

    byte[] array_bytes = text.toString().getBytes();
    char[] array_chars = text.toString().toCharArray();
    System.out.println();
    byteArrayToUtf8CodeUnits(array_bytes);
    System.out.println();
    charArrayToUtf16CodeUnits(array_chars);


public static void byteArrayToUtf8CodeUnits(byte[] byte_array)

    /*for (int k = 0; k < array.length; k++)
    
        System.out.println(name + "[" + k + "] = " + "0x" + byteToHex(array[k]));
    */
    System.out.println("array.length: = " + byte_array.length);
    //------------------------------------------------------------------------------------------
    for (int k = 0; k < byte_array.length; k++)
    
        System.out.println("array byte: " + "[" + k + "]" + " converted to hex" + " = " + byteToHex(byte_array[k]));
    
    //------------------------------------------------------------------------------------------

public static void charArrayToUtf16CodeUnits(char[] char_array)

    /*Utf16 code units are also known as Java Unicode*/
    System.out.println("array.length: = " + char_array.length);
    //------------------------------------------------------------------------------------------
    for (int i = 0; i < char_array.length; i++)
    
        System.out.println("array char: " + "[" + i + "]" + " converted to hex" + " = " + charToHex(char_array[i]));
    
    //------------------------------------------------------------------------------------------

static public String byteToHex(byte b)

    //Returns hex String representation of byte b
    char hexDigit[] =
            
                    '0', '1', '2', '3', '4', '5', '6', '7',
                    '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
            ;
    char[] array =  hexDigit[(b >> 4) & 0x0f], hexDigit[b & 0x0f] ;
    return new String(array);

static public String charToHex(char c)

    //Returns hex String representation of char c
    byte hi = (byte) (c >>> 8);
    byte lo = (byte) (c & 0xff);

    return byteToHex(hi) + byteToHex(lo);

【讨论】:

【参考方案2】:

我认为 Michael 已经很好地解释了 in his answer 这可能是找出字节数组是否包含所有有效 utf-8 序列的唯一方法。我在 php 中使用以下代码

function is_utf8($string) 

    return preg_match('%^(?:
          [\x09\x0A\x0D\x20-\x7E]            # ASCII
        | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
        |  \xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]2  # straight 3-byte
        |  \xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
        |  \xF0[\x90-\xBF][\x80-\xBF]2     # planes 1-3
        | [\xF1-\xF3][\x80-\xBF]3          # planes 4-15
        |  \xF4[\x80-\x8F][\x80-\xBF]2     # plane 16
    )*$%xs', $string);

 

取自W3.org

【讨论】:

"Java 中可以使用哪些方法"【参考方案3】:

这是一种使用 W3C site 中的 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 接口的东西(所以char[] 也出来了)。通过将byte[] 解码为ISO-8859-1,您可以创建一个字符串,其中每个char 具有与原始数组中相应字节相同的无符号数值。

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

当您使用它时,您可以剥离 UTF-8 BOM(如果有的话);否则,UTF-8 CharsetDecoder 会将其作为文本传递。

UTF-16 会困难得多,因为很少有字节序列总是无效。我唯一能想到的就是缺少低代理伙伴的高代理角色,反之亦然。除此之外,您将需要一些上下文来确定给定序列是否有效。您可能有一个西里尔字母,后跟一个中文表意文字,然后是一个笑脸dingbat,但它是完全有效的UTF-16。

【讨论】:

【参考方案4】:

问题假设字符串和二进制数据之间存在根本区别。虽然直觉上是这样,但几乎不可能准确地定义这种差异是什么。

Java 字符串是 16 位数量的序列,对应于(几乎)2**16 个 Unicode 基本代码点之一。但是,如果您查看那些 16 位“字符”,每个字符都可以平等地表示一个整数、一对字节、一个像素等等。位模式没有任何内在的含义来说明它们所代表的内容。

现在假设您将问题重新表述为寻求一种方法来区分 UTF-8 编码的 TEXT 和任意二进制数据。这有帮助吗?理论上不会,因为编码任何书面文本的位模式也可以是数字序列。 (这里很难说“任意”的真正含义。你能告诉我如何测试一个数字是否“任意”吗?)

我们可以在这里做的最好的事情是:

    测试字节是否为有效的 UTF-8 编码。 测试解码的 16 位数量是否都是合法的,“分配的”UTF-8 代码点。 (一些 16 位数量是非法的(例如 0xffff),而其他数量目前没有分配给任何字符。)但是如果文本文档真的使用未分配的代码点怎么办? 根据假定的文档语言测试 Unicode 代码点是否属于您期望的“平面”。但是,如果您不知道应该使用哪种语言,或者文档使用多种语言怎么办? 测试是代码点序列看起来像单词、句子或其他任何东西。但是,如果我们有一些恰好包含嵌入式文本序列的“二进制数据”呢?

总而言之,如果解码失败,您可以判断一个字节序列肯定不是 UTF-8。除此之外,如果您对语言做出假设,您可以说一个字节序列可能可能不是一个 UTF-8 编码的文本文档。

IMO,您能做的最好的事情就是避免陷入您的程序需要做出此决定的情况。如果无法避免,请认识到您的程序可能会出错。通过思考和努力,您可以使这不太可能发生,但概率永远不会为零。

【讨论】:

【参考方案5】:

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

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

但是,有可能获得解码为完全无意义的字符串(可能来自各种不同的脚本)的有效 UTF-8 数据。短序列更有可能出现这种情况。如果您对此感到担心,则可能需要进行更仔细的分析,以查看作为字母的字符是否都属于同一个code chart。再说一次,当您有混合脚本的有效文本输入时,这可能会产生误报。

【讨论】:

【参考方案6】:

如果字节数组以Byte Order Mark (BOM) 开头,那么很容易区分使用了什么编码。用于处理文本流的标准 Java 类可能会自动为您处理。

如果您的字节数据中没有 BOM,这将更加困难 - .NET 类可以执行统计分析来尝试计算编码,但我认为这是假设您知道自己是处理文本数据(只是不知道使用了哪种编码)。

如果您对输入数据的格式有任何控制权,那么最好的选择是确保它包含字节顺序标记。

【讨论】:

Java 不会自动插入 BOM,也不会在解码时将其移除。 Erk,我应该说 Java 不处理 UTF-8 的 BOM。 UTF-16/UTF-32 是否支持取决于选择的编码机制:java.sun.com/javase/6/docs/technotes/guides/intl/…【参考方案7】:

尝试解码。如果您没有收到任何错误,那么它是一个有效的 UTF-8 字符串。

【讨论】:

-1:事实错误。非文本二进制流可能被解码为有效的 UTF-8 字符串。如果 UTF-8 解码失败,则意味着您的二进制数据不是 UTF-8;但如果 UTF-8 解码没有失败,则不能保证二进制数据 UTF-8。 +1 绝对正确。如果解码没有错误,则它是有效的 UTF-8 文本数据。它可能是毫无意义的文本数据,例如拉丁语、中文、泰语和希腊语字符的狂野混合,但这是语义上的区别,而不是技术上的区别。 公平点迈克尔。我想在那种情况下我应该说:-1 不回答这个问题。断言它是一个有效的 UTF-8 字符串并没有回答这个问题,它试图找出它是一个字符串还是二进制数据。仅仅因为它是一个有效的 UTF-8 表示并不能告诉您原始数据是二进制的(碰巧是有效的 UTF-8)还是原始数据是否是真正的文本数据。

以上是关于如何检查字节数组是不是包含 Java 中的 Unicode 字符串?的主要内容,如果未能解决你的问题,请参考以下文章

Java - 如何检查字符串是不是包含字符串数组的元素?

检查数组是不是包含Java中的值的最有效方法? [复制]

当数组是C中的struct类型时,如何检查条目数组的第一个条目是不是为空?

Java 迭代字节数组中的位

如何检查arraylist中的数组是不是包含某个值?

如何检查数组是不是包含 JavaScript 中的值?