关于.net:确定TextFile编码?

Determine TextFile Encoding?

我需要确定文本文件的内容是否等于以下文本编码之一:

1
2
3
4
5
6
7
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编码存储在内存中,因此一旦将其加载到字符串中,原始编码就会丢失。下面是将文件加载到字节数组的一个简单示例:好的。

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

自动确定给定字节数组的正确编码是众所周知的困难。有时,为了有帮助,数据的作者会在数据的开头插入一个称为bom(字节顺序标记)的东西。如果存在一个BOM,那么检测编码就不会有任何困难,因为每个编码都使用不同的BOM。好的。

自动从BOM中检测编码的最简单方法是让StreamReader为您进行编码。在StreamReader的构造函数中,可以为detectEncodingFromByteOrderMarks参数传递True。然后,您可以通过访问流的CurrentEncoding属性来获取流的编码。但是,只有在StreamReader读取了BOM之后,CurrentEncoding属性才能工作。因此,在获得编码之前,您必须先读取BOM,例如:好的。

1
2
3
4
5
6
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可能只检测某些编码:好的。

The detectEncodingFromByteOrderMarks parameter detects the encoding by looking at the first three bytes of the stream. It automatically recognizes UTF-8, little-endian Unicode, and big-endian Unicode text if the file starts with the appropriate byte order marks. See the Encoding.GetPreamble method for more information.

Ok.

另外,如果StreamReader无法从BOM中确定编码,或者如果BOM不存在,那么它将默认为UTF-8编码,而不会给您任何失败的指示。如果您需要比这更精细的控制,那么您可以很容易地阅读这个BOM并自己解释它。您所要做的就是将字节数组中的前几个字节与一些已知的、预期的BOM进行比较,看看它们是否匹配。以下是一些常见的物料清单:好的。

  • utf-8:EF BB BF
  • UTF-16大端字节顺序:FE FF
  • utf-16小端字节顺序:FF FE
  • utf-32大端字节顺序:00 00 FE FF
  • utf-32小端字节顺序:FF FE 00 00

因此,例如,为了查看UTF-16(小endian)BOM是否存在于字节数组的开头,您可以简单地执行如下操作:好的。

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

很方便,.NET中的Encoding类包含一个名为GetPreamble的方法,该方法返回编码所使用的BOM,因此您甚至不需要记住它们都是什么。因此,要检查字节数组是否以unicode(utf-16,小endian)的bom开头,可以这样做:好的。

1
2
3
4
5
6
7
8
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长度本身可以在不同的编码之间变化,所以这样做更安全:好的。

1
2
3
4
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,并将其与字节数组进行比较,直到找到匹配的对象为止。例如:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

一旦您创建了这样的函数,那么您就可以检测到这样的文件编码:好的。

1
2
3
4
5
6
7
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库的列表,这些库试图自动检测字节数组的编码,您可能会发现这些库很有用:好的。

  • 文本文件编码检测器
  • UTF8检查器
  • 获取扩展编码

即使这个问题是针对C,您也可能会发现它的答案很有用。好的。好啊。