Encoding.UTF8.GetString 不考虑 Preamble/BOM

Posted

技术标签:

【中文标题】Encoding.UTF8.GetString 不考虑 Preamble/BOM【英文标题】:Encoding.UTF8.GetString doesn't take into account the Preamble/BOM 【发布时间】:2012-07-26 22:20:47 【问题描述】:

在 .NET 中,我尝试使用 Encoding.UTF8.GetString 方法,该方法采用字节数组并将其转换为 string

此方法似乎忽略了BOM (Byte Order Mark),它可能是 UTF8 字符串的合法二进制表示的一部分,并将其视为一个字符。

我知道我可以根据需要使用TextReader 来消化 BOM,但我认为 GetString 方法应该是某种使我们的代码更短的宏。

我错过了什么吗?这是故意的吗?

这是一个复制代码:

static void Main(string[] args)

    string s1 = "abc";
    byte[] abcWithBom;
    using (var ms = new MemoryStream())
    using (var sw = new StreamWriter(ms, new UTF8Encoding(true)))
    
        sw.Write(s1);
        sw.Flush();
        abcWithBom = ms.ToArray();
        Console.WriteLine(FormatArray(abcWithBom)); // ef, bb, bf, 61, 62, 63
    

    byte[] abcWithoutBom;
    using (var ms = new MemoryStream())
    using (var sw = new StreamWriter(ms, new UTF8Encoding(false)))
    
        sw.Write(s1);
        sw.Flush();
        abcWithoutBom = ms.ToArray();
        Console.WriteLine(FormatArray(abcWithoutBom)); // 61, 62, 63
    

    var restore1 = Encoding.UTF8.GetString(abcWithoutBom);
    Console.WriteLine(restore1.Length); // 3
    Console.WriteLine(restore1); // abc

    var restore2 = Encoding.UTF8.GetString(abcWithBom);
    Console.WriteLine(restore2.Length); // 4 (!)
    Console.WriteLine(restore2); // ?abc


private static string FormatArray(byte[] bytes1)

    return string.Join(", ", from b in bytes1 select b.ToString("x"));

【问题讨论】:

【参考方案1】:

此方法似乎忽略了 BOM(字节顺序标记),它可能是 UTF8 字符串的合法二进制表示的一部分,并将其视为字符。

它看起来根本没有“忽略”它 - 它忠实地将其转换为 BOM 字符。毕竟就是这样。

如果您想让您的代码忽略它转换的任何字符串中的 BOM,这取决于您...或使用StreamReader

请注意,如果您要么使用Encoding.GetBytes,然后使用Encoding.GetString使用StreamWriter,然后使用StreamReader,这两种形式要么产生然后吞下,要么不生产 BOM。只有当您将StreamWriter(使用Encoding.GetPreamble)与直接Encoding.GetString 调用混合使用时,您最终会得到“额外”字符。

【讨论】:

@RonKlein 此外,您可以说 restore2 = restore2.TrimStart('\uFEFF') 删除前导 BOM 字符。我也曾一度想知道为什么(new UTF8Encoding(true)).GetBytes("abc")(new UTF8Encoding(false)).GetBytes("abc") 会产生相同的输出,但正如您现在可能知道的那样,GetBytes 并不假定您在文件的开头,因此它从不使用@987654333 @。如果您使用GetBytesGetString,则必须明确地GetPreamble,或明确地跳过序言。【参考方案2】:

根据 Jon Skeet 的回答(谢谢!),我就是这样做的:

var memoryStream = new MemoryStream(byteArray);
var s = new StreamReader(memoryStream).ReadToEnd();

请注意,这可能只有在您正在读取的字节数组中有 BOM 时才能可靠地工作。如果没有,您可能需要查看another StreamReader constructor overload,它带有一个 Encoding 参数,以便您可以告诉它字节数组包含什么。

【讨论】:

我想您可能需要this constructor overload,它可以让您指定它是否应该查找 BOM 来确定编码。【参考方案3】:

对于那些不想使用流的人,我找到了一个使用 Linq 的非常简单的解决方案:

public static string GetStringExcludeBOMPreamble(this Encoding encoding, byte[] bytes)

    var preamble = encoding.GetPreamble();
    if (preamble?.Length > 0 && bytes.Length >= preamble.Length && bytes.Take(preamble.Length).SequenceEqual(preamble))
    
        return encoding.GetString(bytes, preamble.Length, bytes.Length - preamble.Length);
    
    else
    
        return encoding.GetString(bytes);
    

【讨论】:

【参考方案4】:

我知道我参加聚会有点晚了,但如果需要,这是我正在使用的代码(可以随意适应 C#):

Public Function Serialize(Of YourXMLClass)(ByVal obj As YourXMLClass,
                                                      Optional ByVal omitXMLDeclaration As Boolean = True,
                                                      Optional ByVal omitXMLNamespace As Boolean = True) As String

    Dim serializer As New XmlSerializer(obj.GetType)
    Using memStream As New MemoryStream()
        Dim settings As New XmlWriterSettings() With 
                    .Encoding = Encoding.UTF8,
                    .Indent = True,
                    .omitXMLDeclaration = omitXMLDeclaration

        Using writer As XmlWriter = XmlWriter.Create(memStream, settings)
            Dim xns As New XmlSerializerNamespaces
            If (omitXMLNamespace) Then xns.Add("", "")
            serializer.Serialize(writer, obj, xns)
        End Using

        Return Encoding.UTF8.GetString(memStream.ToArray())
    End Using
End Function

Public Function Deserialize(Of YourXMLClass)(ByVal obj As YourXMLClass, ByVal xml As String) As YourXMLClass
    Dim result As YourXMLClass
    Dim serializer As New XmlSerializer(GetType(YourXMLClass))

    Using memStream As New MemoryStream()
        Dim bytes As Byte() = Encoding.UTF8.GetBytes(xml.ToArray)
        memStream.Write(bytes, 0, bytes.Count)
        memStream.Seek(0, SeekOrigin.Begin)

        Using reader As XmlReader = XmlReader.Create(memStream)
            result = DirectCast(serializer.Deserialize(reader), YourXMLClass)
        End Using

    End Using
    Return result
End Function

【讨论】:

以上是关于Encoding.UTF8.GetString 不考虑 Preamble/BOM的主要内容,如果未能解决你的问题,请参考以下文章

c#字符串转成utf8的问题

C#下载网页

字符串utf-8相互转换

WinJS 中的 System.Text.Encoding

c# byte数组转string

无法在 C# 中加载 XML