2012-07-28 84 views
22

在.NET中,我试图使用Encoding.UTF8.GetString方法,该方法需要一个字节数组并将其转换为stringEncoding.UTF8.GetString没有考虑到前导码/ BOM

看起来这种方法忽略了BOM (Byte Order Mark),它可能是UTF8字符串的合法二进制表示的一部分,并将其作为字符使用。

我知道我可以根据需要使用TextReader消化物料清单,但我认为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")); 
} 

回答

18

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

它看起来并不像它“忽略”它 - 它忠实地将其转换为BOM字符。毕竟,这就是它的原因。

如果您想让您的代码忽略它转换的任何字符串中的BOM,则由您来做...或使用StreamReader

请注意,如果您要么使用Encoding.GetBytes其次Encoding.GetString使用StreamWriter其次StreamReader,这两种形式要么产生再吞或不生产BOM。只有当您使用StreamWriter(使用Encoding.GetPreamble)与直接拨打Encoding.GetString的电话混合使用时,最终会出现“额外”字符。 (!感谢)

+0

我明白了。感谢您的澄清! – 2012-07-28 13:59:59

+5

@RonKlein此外,您可以说'restore2 = restore2.TrimStart('\ uFEFF')'删除前导BOM字符。我也一度想知道为什么'(new UTF8Encoding(true)).getBytes(“abc”)'和'(new UTF8Encoding(false))。GetBytes(“abc”)'产生相同的输出,现在知道,'GetBytes'并不假定你在文件的开头,所以它从不使用'GetPreamble'。如果你使用'GetBytes'或者'GetString',你必须显式地声明'GetPreamble',或者明确地跳过前导码。 – 2012-07-29 12:45:57

7

基于由乔恩飞碟双向的答案,这是多么我只是做了它:

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

注意,这可能只会可靠,如果有字节数组的BOM工作你正在阅读。如果不是,您可能需要查看another StreamReader constructor overload,它需要一个Encoding参数,以便您可以告诉它该字节数组包含的内容。

+0

我想你可能想[此构造函数重载](https://msdn.microsoft.com/en-us/library/ms143457(v = vs.110).aspx),而不是让它指定它是否应该查找BOM来找出编码。 – drzaus 2017-06-13 15:07:12

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