Encoding.UTF8.GetString没有考虑Preamble / BOM

时间:2012-07-28 13:16:38

标签: .net unicode character-encoding byte-order-mark

在.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"));
}

4 个答案:

答案 0 :(得分:22)

  

看起来这个方法忽略了BOM(字节顺序标记),它可能是UTF8字符串的合法二进制表示的一部分,并将其作为字符。

看起来它根本不“忽略”它 - 它忠实地将它转换为BOM角色。毕竟那就是它。

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

请注意,如果您 使用Encoding.GetBytes后跟Encoding.GetString ,请使用StreamWriter后跟StreamReader,两种形式都会生成然后吞下或不生成BOM。只有当您使用StreamWriter(使用Encoding.GetPreamble)与直接Encoding.GetString调用混合时才会使用“额外”字符进行混音。

答案 1 :(得分:8)

基于Jon Skeet的回答(谢谢!),这就是我刚刚做到的:

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

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

答案 2 :(得分:0)

我知道我有点迟到了,但是如果你需要,这里是我正在使用的代码(随意适应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

答案 3 :(得分:0)

对于那些不想使用流的人,我发现了使用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);
    }
}