如何从无限字节流中读取UTF-8字符 - C#

时间:2012-07-26 14:42:29

标签: c# stream

通常,要从字节流中读取字符,请使用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();
    }
}

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

4 个答案:

答案 0 :(得分:10)

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

答案 1 :(得分:5)

感谢理查德,我现在有了一个无限流读者。正如他解释的那样,诀窍是使用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();
    }
}

答案 2 :(得分:1)

我不明白你为什么不使用流阅读器的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();
    }
}

答案 3 :(得分:0)

迈克, 我也发现您的解决方案也很适合我的情况。但是我注意到,有时需要四个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...
        }
    }