6

通常,要从字节流中读取字符,您使用 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

4 回答 4

10

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

于 2012-07-26T14:48:14.113 回答
6

感谢理查德,我现在有了一个可以工作的无限流阅读器。正如他解释的那样,诀窍是使用 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();
    }
}
于 2012-07-26T15:07:15.380 回答
2

我不明白你为什么不使用流阅读器的 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();
    }
}
于 2012-07-26T22:54:23.017 回答
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...
        }
    }
于 2018-10-02T09:47:00.217 回答