Encoding.UTF8.GetString не учитывает преамбулу/спецификацию
В .NET я пытаюсь использовать метод Encoding.UTF8.GetString
, который принимает массив байтов и преобразует его в string
.
Похоже, что этот метод игнорирует спецификацию (байтовый порядок), который может быть частью законного двоичного представления строки 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"));
}
Ответы
Ответ 1
Похоже, что этот метод игнорирует спецификацию (Byte Order Mark), которая может быть частью законного двоичного представления строки UTF8 и принимает ее как символ.
Не похоже, что он "игнорирует" его вообще - он добросовестно преобразует его в характер спецификации. Что это, в конце концов.
Если вы хотите, чтобы ваш код игнорировал спецификацию в любой строке, которую она преобразует, это вам делать или использовать StreamReader
.
Обратите внимание, что если вы используете Encoding.GetBytes
, а затем Encoding.GetString
или используете StreamWriter
, за которым следует StreamReader
, обе формы будут либо производить, либо проглатывать, либо не создавать спецификацию. Это происходит только тогда, когда вы смешиваете с помощью StreamWriter
(который использует Encoding.GetPreamble
) с прямым вызовом Encoding.GetString
, который заканчивается символом "extra".
Ответ 2
Основываясь на ответе Джона Скита (спасибо!), вот как я это сделал:
var memoryStream = new MemoryStream(byteArray);
var s = new StreamReader(memoryStream).ReadToEnd();
Обратите внимание, что это, вероятно, будет работать только надежно, если в массиве байтов, который вы читаете, есть спецификация. Если нет, вы можете захотеть заглянуть в другой конструктор конструктора StreamReader, который принимает параметр Encoding, чтобы вы могли рассказать ему, что содержит массив байтов.
Ответ 3
Я знаю, что я опаздываю на вечеринку, но здесь код, который я использую (не стесняйтесь приспосабливаться к С#), если вам нужно:
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);
}
}