【问题标题】:Bytes consumed by StreamReaderStreamReader 消耗的字节数
【发布时间】:2009-04-10 11:56:28
【问题描述】:

有没有办法知道 StreamReader 使用了多少字节流?

我有一个项目,我们需要读取一个文件,该文件有一个文本标题,后跟二进制数据的开头。我最初尝试读取这个文件是这样的:

private int _dataOffset;
void ReadHeader(string path) 
{
    using (FileStream stream = File.OpenRead(path)) 
    {
        StreamReader textReader = new StreamReader(stream);

        do 
        {
            string line = textReader.ReadLine();
            handleHeaderLine(line);
        } while(line != "DATA") // Yes, they used "DATA" to mark the end of the header

        _dataOffset = stream.Position;
    }
}

private byte[] ReadDataFrame(string path, int frameNum) 
{
    using (FileStream stream = File.OpenRead(path)) 
    {
        stream.Seek(_dataOffset + frameNum * cbFrame, SeekOrigin.Begin);

        byte[] data = new byte[cbFrame];
        stream.Read(data, 0, cbFrame);

        return data;
    }
    return null;
}

问题是当我将_dataOffset 设置为stream.Position 时,我得到了StreamReader 已读取到的位置,而不是标题的末尾。我一想到这是有道理的,但我仍然需要能够知道标头的结尾在哪里,而且我不确定是否有办法做到这一点并且仍然可以利用 StreamReader。

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    您可以通过多种方式了解StreamReader 实际返回了多少字节(而不是从流中读取),恐怕它们都不太简单。

    1. 获取textReader.CurrentEncoding.GetByteCount(totalLengthOfAllTextRead)的结果,然后在流中寻找到这个位置。
    2. 使用一些反射技巧来检索StreamReader对象的私有变量的值,该值对应于内部缓冲区中的当前字节位置(与流不同 - 通常在后面,但不超过等于课程)。从 .NET Reflector 来看,这个变量似乎被命名为 bytePos
    3. 根本不用StreamReader,而是实现基于StreamBinaryReader的自定义ReadLine函数(BinaryReader保证永远不会比您要求的更远) .此自定义函数必须逐个字符地从流中读取,因此您实际上必须使用低级 Decoder 对象(除非编码是 ASCII/ANSI,在这种情况下,由于单字节,事情会更简单一些编码)。

    选项 1 将是我想象中效率最低的(因为您正在有效地重新编码刚刚解码的文本),选项 3 最难实现,尽管可能是最优雅的。我可能会建议不要使用丑陋的反射黑客(选项 2),即使它看起来很诱人,它是最直接的解决方案并且只需要几行代码。 (老实说,StreamReader 类确实应该通过公共属性公开这个变量,但可惜它没有。)所以最后,这取决于你,但方法 1 或 3 应该可以很好地完成这项工作够了……

    希望对您有所帮助。

    【讨论】:

      【解决方案2】:

      所以数据是utf8(StreamReader的默认编码)。这是一种多字节编码,因此不建议使用 IndexOf。你可以:

      Encoding.UTF8.GetByteCount(string)
      

      到目前为止,在您的数据上,为缺少的行结尾添加 1 或 2 个字节。

      【讨论】:

      • 如果我使用字符串的字节数,这正是我所关心的。我不确定要为线路终结器添加多少。
      • 这不行,有一些字节,用来存储技术信息,如果你这样算的话会漏掉。例如。 ——文件开头有3个字节,说明这个文件是unicode编码的。
      【解决方案3】:

      如果您需要计算字节数,我会使用 BinaryReader。您可以根据需要获取结果并对其进行转换,但我发现其当前位置的想法更可靠(因为它以二进制形式读取,因此不受字符集问题的影响)。

      【讨论】:

        【解决方案4】:

        所以你的最后一行包含 'DATA' + 未知数量的数据字节。您可以通过使用 IndexOf() 和最后读取的行来提取位置。然后重新调整stream.Position。

        但我不确定在这种情况下您是否应该使用 ReadLine()。也许在到达“DATA”标记之前逐字节读取会更好。

        【讨论】:

        • 嗯,这当然是我的后备立场,我只是想在实施之前看看是否有更好的方法。
        【解决方案5】:

        换行符很容易识别,无需先解码流(除了一些很少用于文本文件的编码,如 EBCDIC、UTF-16、UTF-32),因此您可以将每一行读取为字节然后解码整行:

        using (FileStream stream = File.OpenRead(path)) {
           List<byte> buffer = new List<byte>();
           bool hasCr = false;
           bool done = false;
           while (!done) {
              int b = stream.ReadByte();
              if (b == -1) throw new IOException("End of file reached in header.");
              if (b == 13) {
                 hasCr = true;
              } else if (b == 10 && hasCr) {
                 string line = Encoding.UTF8.GetString(buffer.ToArray(), 0, buffer.Count);
                 if (line == "DATA") {
                    done = true;
                 } else {
                    HandleHeaderLine(line);
                 }
                 buffer.Clear();
                 hasCr = false;
              } else {
                 if (hasCr) buffer.Add(13);
                 hasCr = false;
                 buffer.Add((byte)b);
              }
           }
           _dataOffset = stream.Position;
        }
        

        您当然可以继续读取数据,而不是关闭流并再次打开它。

        【讨论】:

        • 此方法仅适用于 ASCII/ANSI 编码。对于其他编码,你真的应该使用解码器,正如我在我的帖子中详述的那样。此外,使用 List 效率会非常低。
        • 是的,它不适用于一些不常见的编码,我将添加一个关于此的内容。 List 使用字节数组进行存储,因此没有什么是非常低效的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-22
        • 2012-01-26
        相关资源
        最近更新 更多