在C#中从流中读取单个UTF8字符

时间:2016-11-17 17:06:41

标签: c# utf-8 stream

我希望从Stream或BinaryReader中读取下一个UTF8字符。不起作用的事情:

BinaryReader :: ReadChar - 这会抛出一个3或4个字节的字符。由于它返回一个两字节结构,因此没有选择。

BinaryReader :: ReadChars - 如果你要求它读取1个字符并且它遇到3或4个字节的字符,这将抛出。如果您要求多个字符读取超过1个字符,则会读取多个字符。

StreamReader :: Read - 这需要知道要读取多少字节,但UTF8字符中的字节数是可变的。

我的代码似乎有效:

    private char[] ReadUTF8Char(Stream s)
    {
        byte[] bytes = new byte[4];
        var enc = new UTF8Encoding(false, true);
        if (1 != s.Read(bytes, 0, 1))
            return null;
        if (bytes[0] <= 0x7F) //Single byte character
        {
            return enc.GetChars(bytes, 0, 1);
        }
        else
        {
            var remainingBytes =
                ((bytes[0] & 240) == 240) ? 3 : (
                ((bytes[0] & 224) == 224) ? 2 : (
                ((bytes[0] & 192) == 192) ? 1 : -1
            ));
            if (remainingBytes == -1)
                return null;
            s.Read(bytes, 1, remainingBytes);
            return enc.GetChars(bytes, 0, remainingBytes + 1);
        }
    }

显然,这有点乱,有点特定于UTF8。是否有更优雅,更少自定义,更易于阅读的解决方案?

1 个答案:

答案 0 :(得分:0)

我知道这个问题有点旧,但这是另一种解决方案。它的性能不如OPs解决方案(我也更喜欢),但它只使用builtin-utf8功能,而不知道utf8编码内部。

private static char ReadUTF8Char(Stream s)
{
    if (s.Position >= s.Length)
        throw new Exception("Error: Read beyond EOF");

    using (BinaryReader reader = new BinaryReader(s, Encoding.Unicode, true))
    {
        int numRead = Math.Min(4, (int)(s.Length - s.Position));
        byte[] bytes = reader.ReadBytes(numRead);
        char[] chars = Encoding.UTF8.GetChars(bytes);

        if (chars.Length == 0)
            throw new Exception("Error: Invalid UTF8 char");

        int charLen = Encoding.UTF8.GetByteCount(new char[] { chars[0] });

        s.Position += (charLen - numRead);

        return chars[0];
    }
}

传递给BinaryReader的构造函数的编码无关紧要。我不得不使用这个版本的构造函数来打开流。如果您已经有二进制阅读器,可以使用它:

private static char ReadUTF8Char(BinaryReader reader)
{
    var s = reader.BaseStream;

    if (s.Position >= s.Length)
        throw new Exception("Error: Read beyond EOF");

    int numRead = Math.Min(4, (int)(s.Length - s.Position));
    byte[] bytes = reader.ReadBytes(numRead);
    char[] chars = Encoding.UTF8.GetChars(bytes);

    if (chars.Length == 0)
        throw new Exception("Error: Invalid UTF8 char");

    int charLen = Encoding.UTF8.GetByteCount(new char[] { chars[0] });

    s.Position += (charLen - numRead);

    return chars[0];
}