Encoding.UTF8.GetString не принимает во внимание преамбулу / спецификацию

В .NET я пытаюсь использовать метод Encoding.UTF8.GetString, который принимает массив байтов и преобразует его в string.

Похоже, этот метод игнорирует BOM (метка порядка байтов), которая может быть частью допустимое двоичное представление строки 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"));
}

person Ron Klein    schedule 28.07.2012    source источник


Ответы (4)


Похоже, этот метод игнорирует BOM (метку порядка байтов), которая может быть частью допустимого двоичного представления строки UTF8, и принимает ее как символ.

Не похоже, что он вообще "игнорирует" - он точно преобразует его в символ спецификации. В конце концов, вот что это такое.

Если вы хотите, чтобы ваш код игнорировал спецификации в любой преобразуемой строке, это зависит от вас ... или используйте StreamReader.

Обратите внимание, что если вы либо используете Encoding.GetBytes, за которым следует Encoding.GetString , либо используете StreamWriter, за которым следует StreamReader, обе формы либо производят, затем проглатывают, либо не производят спецификацию. Только когда вы смешиваете использование StreamWriter (которое использует Encoding.GetPreamble) с прямым вызовом Encoding.GetString, вы получаете "лишний" символ.

person Jon Skeet    schedule 28.07.2012
comment
@RonKlein Кроме того, вы можете сказать restore2 = restore2.TrimStart('\uFEFF'), чтобы удалить ведущие символы спецификации. Я также однажды задавался вопросом, почему (new UTF8Encoding(true)).GetBytes("abc") и (new UTF8Encoding(false)).GetBytes("abc") производят одинаковый результат, но, как вы, вероятно, уже знаете, GetBytes не предполагает, что вы находитесь в начале файла, поэтому он никогда не использует GetPreamble. Вы должны GetPreamble явно или явно пропустить преамбулу, если вы используете GetBytes или GetString. - person Jeppe Stig Nielsen; 29.07.2012

Основываясь на ответе Джона Скита (спасибо!), Я только что это сделал:

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

Обратите внимание, что это, вероятно, будет работать надежно только при наличии спецификации в массиве байтов, из которого вы читаете. В противном случае вы можете изучить другой StreamReader. перегрузка конструктора, которая принимает параметр Encoding, чтобы вы могли сказать, что содержит массив байтов.

person Per Lundberg    schedule 20.11.2013
comment
Я думаю, вам может понадобиться перегрузка этого конструктора, который позволяет указать, следует ли искать спецификацию для определения кодировки. - person drzaus; 13.06.2017

для тех, кто не хочет использовать потоки, я нашел довольно простое решение с помощью 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);
    }
}
person Formentz    schedule 12.12.2018

Я знаю, что опаздываю на вечеринку, но вот код, который я использую (не стесняйтесь адаптироваться к 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
person Denis    schedule 01.04.2015