2012-07-28 18 views
22

W .NET, próbuję użyć metody Encoding.UTF8.GetString, która pobiera tablicę bajtów i konwertuje ją na string.Encoding.UTF8.GetString nie bierze pod uwagę Preambuły/BOM

Wygląda na to, że ta metoda ignoruje BOM (Byte Order Mark), która może być częścią prawidłowej reprezentacji binarnej ciągu UTF8 i przyjmuje go jako znak.

wiem, że mogę użyć TextReader do strawienia LM, ile potrzeba, ale myślałem, że metoda GetString powinien być jakiś makro sprawia, że ​​nasz kod krótszy.

Czy brakuje mi czegoś? Czy to tak celowo?

Oto kod reprodukcji:

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

Odpowiedz

18

Wygląda to metoda ignoruje BOM (Byte Order Mark), co może być częścią uzasadnionego binarnej reprezentacji ciąg UTF8 i bierze jako postać.

Nie wygląda na to, że "ignoruje" go w ogóle - wiernie zamienia go na znak BOM. Tak właśnie jest.

Jeśli chcesz, aby kod ignorować BOM w dowolny ciąg to konwertuje, to do ciebie, aby zrobić ... albo użyć StreamReader.

Zauważ, że jeśli albo użycie Encoding.GetBytes następnie Encoding.GetStringlub użytku StreamWriter następnie StreamReader, obie formy albo będzie produkować następnie połknąć lub nie produkują LM. Dopiero gdy miksujesz używając StreamWriter (która używa Encoding.GetPreamble) z bezpośrednim połączeniem Encoding.GetString, otrzymasz znak "ekstra". (! Dzięki)

+0

widzę. Dziękuję za wyjaśnienie! –

+5

@RonKlein Dodatkowo możesz powiedzieć 'restore2 = restore2.TrimStart ('\ uFFFF')', aby usunąć wiodące znaki BOM. Również kiedyś zastanawiałem się, dlaczego '(nowe UTF8Encoding (true)). GetBytes (" abc ")' i '(nowe UTF8Encoding (false)). GetBytes (" abc ")' produkują te same dane wyjściowe, ale jak prawdopodobnie Wiem, że 'GetBytes' nie zakłada, że ​​jesteś na początku pliku, więc nigdy nie używa' GetPreamble'. Musisz jawnie użyć 'GetPreamble' lub pominąć jawnie preambułę, jeśli użyjesz' GetBytes' lub 'GetString'. –

7

Na podstawie odpowiedzi Jon Skeet, to jak ja po prostu to zrobił:

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

Należy pamiętać, że będzie to prawdopodobnie tylko pracować niezawodnie jeśli jest BOM w tablicy bajtów czytasz od. Jeśli nie, możesz zajrzeć do another StreamReader constructor overload, która pobiera parametr Encoding, abyś mógł stwierdzić, co zawiera tablica bajtów.

+0

Myślę, że możesz chcieć [przeciążenie tego konstruktora] (https://msdn.microsoft.com/en-us/library/ms143457 (v = vs.110) .aspx) zamiast tego, które pozwala określić, czy powinien on szukać BOM, aby dowiedzieć się kodowania. – drzaus

0

Wiem, że jestem trochę późno do partii, ale tutaj jest kod używam (krępuj się przystosować do C#), jeśli potrzebujesz:

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