Как не Стоит Читать Строку из Потока UTF-8
Пост возник после код ревью. Следующий код читает из потока строку в кодировке UTF-8. В реальном контексте код сложнее, здесь только суть:
string ReadString(Stream stream)Проблема в том, что в некоторых случаях возвращаемая строка отличается от закодированной. Например, иногда смайлик превращается в 4 неизвестных символа:
{
var sb = new StringBuilder();
var buf = new byte[4096];
int readCnt;
while ((readCnt = stream.Read(buf)) > 0)
{
var s = Encoding.UTF8
.GetString(buffer, 0, readCnt);
sb.Append(s);
}
return sb.ToString();
}
В потоке: 😊UTF-8 может использовать от 1 до 4 байт для представления символа, но метод
В результате: ????
Stream.Read может возвращать байты от 1 до buffer.Length. Это означает, что буфер может содержать неполный символ UTF-8. В результате Encoding.UTF8.GetString может иметь недопустимую строку UTF-8 для преобразования, поскольку последний символ в буфере может быть неполным. Следующий код имитирует это поведение:var bytes = Encoding.UTF8.GetBytes("😊");
// bytes = new byte[4] { 240, 159, 152, 138 }
var sb = new StringBuilder();
// Симулируем побайтовое чтение
for (var i = 0; i < bytes.Length; i++)
{
sb.Append(Encoding.UTF8.GetString(bytes, i, 1));
}
Console.WriteLine(sb.ToString());
// "????" вместо "😊"
Encoding.UTF8.GetBytes(sb.ToString());
// new byte[12] { 239, 191, 189, 239, 191, 189, 239, 191, 189, 239, 191, 189 }
Есть несколько способов исправить код.Один из них - преобразовать массив байт в строку только тогда, когда у вас есть все данные:
string ReadString(Stream stream)Вы также можете обернуть поток в
{
using var ms = new MemoryStream();
var buf = new byte[4096];
int readCnt;
while ((readCnt = stream.Read(buf)) > 0)
ms.Write(buf, 0, readCnt);
return
Encoding.UTF8.GetString(ms.ToArray());
}
StreamReader с правильной кодировкой:string ReadString(Stream stream)Источник: https://www.meziantou.net/how-not-to-read-a-string-from-an-utf-8-stream.htm
{
using var sr =
new StreamReader(stream, Encoding.UTF8);
return sr.ReadToEnd();
}