我需要确定文本文件的内容是否等于这些文本编码之一:
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,因此我需要更普遍的东西。
答案 0 :(得分:55)
第一步是将文件加载为字节数组而不是字符串。字符串始终使用UTF-16编码存储在内存中,因此一旦将其加载到字符串中,原始编码就会丢失。这是将文件加载到字节数组中的一种简单示例:
Dim data() As Byte = File.ReadAllBytes("test.txt")
自动确定给定字节数组的正确编码是非常困难的。有时,为了提供帮助,数据的作者将在数据的开头插入一个名为BOM(字节顺序标记)的内容。如果存在BOM,则可以轻松检测编码,因为每个编码都使用不同的BOM。
从BOM自动检测编码的最简单方法是让StreamReader
为您执行此操作。在StreamReader
的构造函数中,您可以为True
参数传递detectEncodingFromByteOrderMarks
。然后,您可以通过访问其CurrentEncoding
属性来获取流的编码。但是,CurrentEncoding
属性在StreamReader
读取BOM之后才会生效。因此,您首先必须先读取BOM,然后才能获得编码,例如:
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表的列表:
EF BB BF
FE FF
FF FE
00 00 FE FF
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
方便地,.NET中的Encoding
类包含一个名为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
那么,问题就变成了,你怎么得到所有编码的清单?好吧,.NET Encoding
类也提供了一个名为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存在,也有一些应用程序仍会自动检测编码,但它们通过启发式(即有根据的猜测)来完成,有时它们不准确。基本上,他们使用每个编码加载数据,然后查看数据&#34;看起来&#34;理解。 This page为记事本自动检测算法中的问题提供了一些有趣的见解。 This page显示了如何利用Internet Explorer使用的基于COM的自动检测算法(在C#中)。以下是人们编写的一些C#库的列表,这些库试图自动检测字节数组的编码,您可能会觉得这很有用:
即使this question用于C#,您也可能会发现它的答案很有用。