确定 TextFile 编码?
Posted
技术标签:
【中文标题】确定 TextFile 编码?【英文标题】:Determine TextFile Encoding? 【发布时间】:2013-09-25 18:35:36 【问题描述】:我需要确定文本文件的内容是否等于以下文本编码之一:
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
我不知道如何读取文件的字节标记,我见过 sn-ps 这样做但只能确定文件是 ASCII 还是 Unicode,因此我需要更通用的东西。
【问题讨论】:
你不能可靠地做到这一点。 ***.com/questions/3825390/… @adripanico 请查看该答案下的 cmets,我也对其进行了测试,但它返回 VS 的编码,而不是文件的编码。当文件为 ANSI 编码时,它返回“UTF8”。 @Daniel Hilgarth 请你说你为什么这么认为?,我不是专家,但我认为如果不能可靠地完成,那么“notepad.exe”就不能可靠地知道什么编码类型使用文件,但当您按下“保存”按钮时,记事本始终知道并始终显示这些文件的确切编码。 我也不知道为什么版主在我要求 VBNET 解决方案时标记了这个答案,而这个答案是针对 C# 的,而且假设的解决方案也不起作用...... 【参考方案1】:第一步是将文件加载为字节数组而不是字符串。字符串始终以 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
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
方便的是,.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,有些应用程序仍会自动检测编码,但它们是通过启发式(即有根据的猜测)来检测的,有时它们并不准确。基本上,他们使用每种编码加载数据,然后查看数据是否“看起来”可以理解。 This page 提供了一些关于记事本自动检测算法内部问题的有趣见解。 This page 展示了如何利用 Internet Explorer 使用的基于 COM 的自动检测算法(在 C# 中)。以下是人们编写的一些 C# 库的列表,这些库尝试自动检测字节数组的编码,您可能会发现这些库很有帮助:
TextFileEncodingDetector Utf8Checker GetTextEncoding即使this question 是针对 C# 的,您也可能会发现它的答案很有用。
【讨论】:
如果我可以在一个按钮中设置我最喜欢的答案,这将是其中之一,非常感谢! 谢谢!我添加了有关如何使用StreamReader
执行此操作的信息,这是要包含的重要一点。我认为它对人们来说失败的原因是,在另一个被标记为重复的 C# 答案中,他们没有在获取编码之前先在流上执行Read
。在读取 BOM 之前,它只会返回默认的 UTF-8 编码。
StreamReader 方法为 ANSI 文件返回 UTF8,我仍然喜欢你写的第一个方法,因为它检测到很好的 UTF8 文件,而且如果检测到任何编码,那么我可以返回一个“最可能的编码”作为 ANSI 编码的机会,这对我检测 ANSI 文件和 UTF 文件非常有用,但我认为 sr 方法不能在几行中完成,再次感谢!
正确,因为 ANSI 编码的文件从来没有 BOM,StreamReader
将始终采用默认的 UTF-8。我仍然不明白为什么每个人都投票决定将其作为副本关闭。另一个答案是不正确的,并且在 C# 中。奇怪的。我投票决定重新开放它。我们会看看这是否会发生。无论如何,很高兴我能提供帮助。
出色的答案!谢谢它真的拯救了我的一天!以上是关于确定 TextFile 编码?的主要内容,如果未能解决你的问题,请参考以下文章
如何在节点导出器中设置 --collector.textfile.directory