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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何检查字节数组是否包含Java中的Unicode字符串?相关的知识,希望对你有一定的参考价值。

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

答案

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

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

但是,有可能获得有效的UTF-8数据,这些数据解码为完全无意义的字符串(可能来自各种不同的脚本)。短序列更可能发生这种情况。如果你担心这一点,你可能需要进行更仔细的分析,看看字母是否都属于同一个code chart。然后,当您具有混合脚本的有效文本输入时,这可能会产生错误否定。

另一答案

这是一种使用W3C site的UTF-8“二进制”正则表达式的方法

static boolean looksLikeUTF8(byte[] utf8) throws UnsupportedEncodingException 
{
  Pattern p = Pattern.compile("\A(
" +
    "  [\x09\x0A\x0D\x20-\x7E]             # ASCII\n" +
    "| [\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
" +
    ")*\z", Pattern.COMMENTS);

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

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

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

当你在它时,你可以剥离UTF-8 BOM(如果有的话);否则,UTF-8 CharsetDecoder将传递它,就好像它是文本一样。

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

另一答案

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

Java String是16位量的序列,对应于(几乎)2 ** 16个Unicode基本代码点之一。但是如果你看那些16位'字符',每个字符可以同样代表一个整数,一对字节,一个像素,等等。位模式没有任何关于它们代表什么的固有内容。

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

我们在这里做的最好的事情如下:

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

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

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

另一答案

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

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

如果您可以控制输入数据的格式,最好的选择是确保它包含字节顺序标记。

另一答案

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

我创建了两个主要方法,一个将显示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);
}
另一答案

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

另一答案

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

function is_utf8($string) {

    return preg_match('%^(?:
          [x09x0Ax0Dx20-x7E]            # ASCII
        | [xC2-xDF][x80-xBF]             # non-overlong 2-byte
        |  xE0[xA0-xBF][x80-xBF]        # excluding overlongs
        | [xE1-xECxEExEF][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中的Unicode字符串?的主要内容,如果未能解决你的问题,请参考以下文章

确定字节数组是不是包含 ANSI 或 Unicode 字符串?

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

golang unicode/utf8源码分析

灵魂拷问:如何检查Java数组中是否包含某个值 ?

如何判断java中char是中文字符还是英文字符

如何检查数组是否包含JavaScript中的对象?