【问题标题】:Encoding.UTF8.GetString doesn't take into account the Preamble/BOMEncoding.UTF8.GetString 不考虑 Preamble/BOM
【发布时间】:2021-06-22 15:23:26
【问题描述】:

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

【问题讨论】:

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


    【解决方案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 参数,以便您可以告诉它字节数组包含什么。

    【讨论】:

    【解决方案3】:

    我知道我参加聚会有点晚了,但如果需要,这是我正在使用的代码(可以随意适应 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
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2012-04-02
        • 1970-01-01
        • 2018-01-06
        • 2020-10-26
        • 2018-04-07
        • 2016-09-13
        • 2019-06-23
        相关资源
        最近更新 更多