Dot-Net

確定 TextFile 編碼?

  • September 20, 2013

我需要確定文本文件的內容是否等於以下文本編碼之一:

System.Text.Encoding.ASCII
System.Text.Encoding.BigEndianUnicode ' UTF-L 16
System.Text.Encoding.Default ' ANSI
System.Text.Encoding.Unicode ' UTF16
System.Text.Encoding.UTF32
System.Text.Encoding.UTF7
System.Text.Encoding.UTF8

我不知道如何讀取文件的字節標記,我見過這樣做的片段,但只能確定文件是 ASCII 還是 Unicode,因此我需要更通用的東西。

第一步是將文件載入為字節數組而不是字元串。字元串始終以 UTF-16 編碼儲存在記憶體中,因此一旦將其載入到字元串中,原始編碼就會失去。這是將文件載入到字節數組中的一種方法的簡單範例:

Dim data() As Byte = File.ReadAllBytes("test.txt")

眾所周知,自動確定給定字節數組的正確編碼非常困難。有時,為了提供幫助,數據的作者會在數據的開頭插入稱為 BOM(字節順序標記)的東西。如果存在 BOM,則可以輕鬆檢測編碼,因為每種編碼都使用不同的 BOM。

從 BOM 中自動檢測編碼的最簡單方法是讓系統StreamReader為您完成。在 的建構子中,StreamReader您可以傳遞參數。然後您可以通過訪問其屬性來獲取流的編碼。但是,在讀取 BOM之後,該屬性才會起作用。因此,您首先必須閱讀 BOM,然後才能獲得編碼,例如:True``detectEncodingFromByteOrderMarks``CurrentEncoding``CurrentEncoding``StreamReader

Public Function GetFileEncoding(filePath As String) As Encoding
   Using sr As New StreamReader(filePath, True)
       sr.Read()
       Return sr.CurrentEncoding
   End Using
End Function

然而,這種方法的問題在於MSDN似乎暗示它StreamReader可能只檢測某些類型的編碼:

detectEncodingFromByteOrderMarks 參數通過查看流的前三個字節來檢測編碼。如果文件以適當的字節順序標記開頭,它會自動辨識 UTF-8、little-endian Unicode 和 big-endian Unicode 文本。有關詳細資訊,請參閱 Encoding.GetPreamble 方法。

此外,如果StreamReader無法從 BOM 確定編碼,或者如果 BOM 不存在,它將預設為 UTF-8 編碼,而不會給您任何失敗的指示。如果您需要比這更精細的控制,您可以很容易地閱讀 BOM 並自己解釋它。您所要做的就是將字節數組中的前幾個字節與一些已知的、預期的 BOM 進行比較,看看它們是否匹配。以下是一些常見 BOM 的列表:

  • UTF-8:EF BB BF
  • UTF-16 大端字節序:FE FF
  • UTF-16 little endian 字節順序:FF FE
  • UTF-32 大端字節序:00 00 FE FF
  • UTF-32 little endian 字節順序:FF FE 00 00

因此,例如,要查看字節數組的開頭是否存在 UTF-16(小端序)BOM,您可以簡單地執行以下操作:

If (data(0) = &HFF) And (data(1) = &HFE) Then
   ' Data starts with UTF-16 (little endian) BOM
End If

方便的是Encoding,.NET 中的類包含一個呼叫的方法,該方法GetPreamble返回編碼使用的 BOM,因此您甚至不需要記住它們都是什麼。因此,要檢查字節數組是否以 Unicode 的 BOM(UTF-16,little-endian)開頭,您可以這樣做:

Function IsUtf16LittleEndian(data() as Byte) As Boolean
   Dim bom() As Byte = Encoding.Unicode.GetPreamble()
   If (data(0) = bom(0)) And (data(1) = bom(1) Then
       Return True
   Else
       Return False
   End If
End Function

當然,上面的函式假設數據長度至少是兩個字節,而 BOM 正好是兩個字節。因此,雖然它盡可能清楚地說明瞭如何做到這一點,但這並不是最安全的方法。為了使其能夠容忍不同的數組長度,特別是因為 BOM 長度本身可能因一種編碼而異,因此執行以下操作會更安全:

Function IsUtf16LittleEndian(data() as Byte) As Boolean
   Dim bom() As Byte = Encoding.Unicode.GetPreamble()
   Return data.Zip(bom, Function(x, y) x = y).All(Function(x) x)
End Function

那麼,問題就變成了,如何獲得所有編碼的列表?恰巧,.NETEncoding類還提供了一個共享(靜態)方法GetEncodings,該方法返回所有支持的編碼對象的列表。因此,您可以創建一個循環所有編碼對象的方法,獲取每個編碼對象的 BOM 並將其與字節數組進行比較,直到找到匹配的對象。例如:

Public Function DetectEncodingFromBom(data() As Byte) As Encoding
   Return Encoding.GetEncodings().
       Select(Function(info) info.GetEncoding()).
       FirstOrDefault(Function(enc) DataStartsWithBom(data, enc))
End Function

Private Function DataStartsWithBom(data() As Byte, enc As Encoding) As Boolean
   Dim bom() As Byte = enc.GetPreamble()
   If bom.Length <> 0 Then
       Return data.
           Zip(bom, Function(x, y) x = y).
           All(Function(x) x)
   Else
       Return False
   End If
End Function

一旦你做了一個這樣的函式,你就可以像這樣檢測文件的編碼:

Dim data() As Byte = File.ReadAllBytes("test.txt")
Dim detectedEncoding As Encoding = DetectEncodingFromBom(data)
If detectedEncoding Is Nothing Then
   Console.WriteLine("Unable to detect encoding")
Else
   Console.WriteLine(detectedEncoding.EncodingName)
End If

但是,問題依然存在,沒有BOM時如何自動檢測正確的編碼?從技術上講,建議您在使用 UTF-8 時不要將 BOM 放在數據的開頭,並且沒有為任何 ANSI 程式碼頁定義 BOM。因此,文本文件可能沒有 BOM 肯定不是不可能的。如果您處理的所有文件都是英文的,那麼可以假設如果沒有 BOM,那麼 UTF-8 就足夠了。但是,如果任何文件碰巧使用了其他東西,而沒有 BOM,那麼這將不起作用。

正如您正確觀察到的那樣,即使不存在 BOM,有些應用程序仍會自動檢測編碼,但它們是通過啟發式(即有根據的猜測)來檢測的,有時它們並不准確。基本上,他們使用每種編碼載入數據,然後查看數據是否“看起來”可以理解。 這個頁面提供了一些關於記事本自動檢測算法內部問題的有趣見解。 本頁展示瞭如何利用 Internet Explorer 使用的基於 COM 的自動檢測算法(在 C# 中)。以下是人們編寫的一些 C# 庫的列表,這些庫嘗試自動檢測字節數組的編碼,您可能會發現這些庫很有幫助:

即使這個問題是針對 C# 的,您也可能會發現它的答案很有用。

引用自:https://stackoverflow.com/questions/18915633