【问题标题】:How do you read UTF-8 characters from an infinite byte stream - C#如何从无限字节流中读取 UTF-8 字符 - C#
【发布时间】:2018-10-02 17:47:00
【问题描述】:

通常,要从字节流中读取字符,您使用 StreamReader。在此示例中,我正在从无限流中读取由 '\r' 分隔的记录。

using(var reader = new StreamReader(stream, Encoding.UTF8))
{
    var messageBuilder = new StringBuilder();
    var nextChar = 'x';
    while (reader.Peek() >= 0)
    {
        nextChar = (char)reader.Read()
        messageBuilder.Append(nextChar);

        if (nextChar == '\r')
        {
            ProcessBuffer(messageBuilder.ToString());
            messageBuilder.Clear();
        }
    }
}

问题在于 StreamReader 的内部缓冲区很小,因此如果代码等待“记录结束”分隔符(在本例中为“\r”),它必须等到 StreamReader 的内部缓冲区被刷新(通常因为更多的字节已经到达)。

这种替代实现适用于单字节 UTF-8 字符,但对于多字节字符会失败。

int byteAsInt = 0;
var messageBuilder = new StringBuilder();
while ((byteAsInt = stream.ReadByte()) != -1)
{
    var nextChar = Encoding.UTF8.GetChars(new[]{(byte) byteAsInt});
    Console.Write(nextChar[0]);
    messageBuilder.Append(nextChar);

    if (nextChar[0] == '\r')
    {
        ProcessBuffer(messageBuilder.ToString());
        messageBuilder.Clear();
    }
}

如何修改此代码以使其适用于多字节字符?

【问题讨论】:

  • 不应该将标题修改为多字节或UTF-16字符而不是UTF-8吗?似乎具有误导性。
  • @TimS。 UTF-8 字符可以多于一个字节。
  • @TimS。你是什​​么意思?多字节 UTF-8 字符不会自动变为 UTF-16 字符。 Wiki.
  • UTF-8 字符可以是多字节en.wikipedia.org/wiki/Utf-8
  • @MikeHadlow 啊,感谢您的更正和信息。我没有意识到 UTF-8 可以包含多字节字符。

标签: c# stream


【解决方案1】:

而不是设计用于转换完整缓冲区的Encoding.UTF8.GetChars,获取Decoder 的实例并重复调用其成员方法GetChars,这将利用Decoder 的内部缓冲区来处理部分多从一个调用结束到下一个调用的字节序列。

【讨论】:

  • 谢谢理查德,效果很好。请参阅我的实施答案。
【解决方案2】:

感谢 Richard,我现在有了一个可以工作的无限流阅读器。正如他解释的那样,诀窍是使用 Decoder 实例并调用其 GetChars 方法。我已经用多字节日语文本对其进行了测试,效果很好。

int byteAsInt = 0;
var messageBuilder = new StringBuilder();
var decoder = Encoding.UTF8.GetDecoder();
var nextChar = new char[1];

while ((byteAsInt = stream.ReadByte()) != -1)
{
    var charCount = decoder.GetChars(new[] {(byte) byteAsInt}, 0, 1, nextChar, 0);
    if(charCount == 0) continue;

    Console.Write(nextChar[0]);
    messageBuilder.Append(nextChar);

    if (nextChar[0] == '\r')
    {
        ProcessBuffer(messageBuilder.ToString());
        messageBuilder.Clear();
    }
}

【讨论】:

    【解决方案3】:

    我不明白您为什么不使用流阅读器的 ReadLine 方法。但是,如果有充分的理由不这样做,那么在我看来,在解码器上重复调用 GetChars 是低效的。为什么不利用 '\r' 的字节表示不能成为多字节序列的一部分这一事实呢? (多字节序列中的字节必须大于 127,即它们具有最高位设置。)

    var messageBuilder = new List<byte>();
    
    int byteAsInt;
    while ((byteAsInt = stream.ReadByte()) != -1)
    {
        messageBuilder.Add((byte)byteAsInt);
    
        if (byteAsInt == '\r')
        {
            var messageString = Encoding.UTF8.GetString(messageBuilder.ToArray());
            Console.Write(messageString);
            ProcessBuffer(messageString);
            messageBuilder.Clear();
        }
    }
    

    【讨论】:

    • 等等,你是不是认真地说在解码器上调用GetChars效率低下,同时逐字节读取流,将其放入字节列表中,然后从该列表中构建一个字节数组并致电Encoding.GetString?好像你错过了小一个的大性能问题:) ...哦,我看到 OP 做了同样的事情。没关系。
    【解决方案4】:

    迈克, 我发现您的解决方案也非常适合我的情况。但我注意到有时需要四个 GetChar() 调用来确定要返回的字符。这意味着 charCount 为 2,而我的 nextChar 缓冲区大小为 1。所以我收到错误“输出字符缓冲区太小,无法包含解码字符,编码 Unicode 回退 System.Text.DecoderReplacementFallback。”

    我将代码更改为:

        // ...
        var nextChar = new char[4];  // 2 might suffice
    
        for (var i = startPos; i < bytesRead; i++)
        {
            int charCount;
            //...
            charCount = decoder.GetChars(buffer, i, 1, nextChar, 0);
    
            if (charCount == 0)
            {
                bytesSkipped++;
                continue;
            }
    
            for (int ic = 0; ic < charCount; ic++)
            {
                char c = nextChar[ic];
                charPos++;
    
                // Process character here...
            }
        }
    

    【讨论】:

      猜你喜欢
      • 2015-07-26
      • 2011-10-23
      • 1970-01-01
      • 2017-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-20
      • 1970-01-01
      相关资源
      最近更新 更多