2009-07-28 110 views
13

給定一個字節數組,它可以是UTF-8編碼的字符串或任意二進制數據,可以使用在Java中確定它是哪一種?如何檢查字節數組是否包含Java中的Unicode字符串?

該陣列可以通過類似的代碼生成:

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

類似的問題已經從愛德華·王爾德一些有用的鏈接 - http://stackoverflow.com/questions/377294/howto-identify-utf- 8編碼字符串 – JonoW 2009-07-28 10:23:35

回答

-1

嘗試解碼它。如果你沒有得到任何錯誤,那麼它是一個有效的UTF-8字符串。

+2

-1:事實錯誤。非文本二進制流可能被解碼爲有效的UTF-8字符串。如果UTF-8解碼失敗,那意味着你的二進制數據不是UTF-8;但是如果UTF-8解碼不會失敗,那就不會保證二進制數據是UTF-8。 – 2009-07-28 10:27:32

+1

+1絕對正確。如果它解碼沒有錯誤,它是有效的UTF-8文本數據。它可能是絕對沒有意義的文本數據,例如拉丁文,中文,泰文和希臘文字符的混合,但這是一種語義上的區別,而不是技術上的區別。 – 2009-07-28 10:35:01

+1

公平點Michael。我想在這種情況下,我應該說:-1不回答這個問題。斷言它是一個有效的UTF-8字符串不會回答這個問題,它試圖找出它是一個字符串還是二進制數據。只是因爲它是一個合法的UTF-8表示不會告訴你很多關於原始數據是二進制(這恰好是巧合有效UTF-8),還是原來是真正的文本數據。 – 2009-07-28 12:16:54

10

它不可能使在所有情況下,充分精確度的決定,因爲一個UTF-8編碼的字符串一種任意的二進制數據,但你可以看看是invalid in UTF-8字節序列。如果你找到了,你知道它不是UTF-8。

如果數組足夠大,這應該可以很好地工作,因爲這樣的序列很可能出現在「隨機」二進制數據中,例如壓縮數據或圖像文件。

但是,有可能獲得有效的UTF-8數據解碼爲完全無意義的字符串(可能來自各種不同的腳本)。短序列更可能。如果您擔心這一點,您可能需要進行更仔細的分析,以查看字母是否屬於同一個code chart。再次,當您有有效的文本輸入混合腳本時,這可能會產生錯誤的否定結果。

0

如果字節數組以Byte Order Mark(BOM)開頭,那麼很容易區分使用了哪種編碼。處理文本流的標準Java類可能會自動爲您處理。

如果你的字節數據中沒有物料清單,這將會更加困難--.NET類可以執行統計分析來嘗試編制編碼,但我認爲這是假設你知道你正在處理文本數據(只是不知道使用哪種編碼)。

如果您對輸入數據的格式有任何控制權,那麼最好的選擇是確保它包含一個字節順序標記。

3

該問題假設字符串和二進制數據之間存在根本差異。雖然這很直觀,但幾乎不可能精確定義這種差異。

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編碼的文本文檔。

海事組織,你可以做的最好的事情是避免陷入你需要做出這個決定的情況。如果無法避免,請認識到您的程序可能會出錯。通過思想和努力,你可以做到這一點,但概率永遠不會爲零。

4

下面是使用UTF-8「二進制」正則表達式從W3C site

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將會困難得多,因爲只有很少的字節序列是總是無效。我能想到的唯一一個是高代理人物,他們錯過了低代理人的伴侶,反之亦然。除此之外,你需要一些上下文來決定給定的序列是否有效。你可能會看到一個西裏爾字母,後面跟着一箇中國的表意文字,後面跟着一個笑臉的丁字母,但它會是完全有效的UTF-16。

-1

我認爲邁克爾已經解釋得很好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

0

採取它在原來的問題:我如何檢查的字節數組是否包含在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); 
} 
相關問題