3

我正在尝试一次以几行的块读取一个(小型)文件,并且我需要返回到特定块的开头。

问题是,在第一次调用

streamReader.ReadLine();

streamReader.BaseStream.Position属性设置为文件的末尾!现在我假设在后台完成了一些缓存,但我希望这个属性能够反映从该文件中使用的字节数。是的,该文件不止一行:-)

例如,ReadLine()再次调用将(自然)返回文件中的下一行,该行不是从之前报告的位置开始的streamReader.BaseStream.Position

如何找到第一行结束的实际位置,以便稍后返回?

我只能考虑通过添加 ReadLine() 返回的字符串的长度来手动进行簿记,但即使在这里也有一些警告:

  • ReadLine() 去除可能具有可变长度的换行符(是'\n'?是“\r\n”吗?等等)
  • 我不确定这是否适用于可变长度字符

...所以现在看来​​我唯一的选择是重新考虑如何解析文件,所以我不必倒带。

如果有帮助,我会像这样打开我的文件:

using (var reader = new StreamReader(
        new FileStream(
                       m_path, 
                       FileMode.Open, 
                       FileAccess.Read, 
                       FileShare.ReadWrite)))
{...}

有什么建议么?

4

4 回答 4

4

如果您需要读取行,并且需要返回之前的块,为什么不将您读取的行存储在 List 中?这应该很容易。

您不应该依赖于根据字符串的长度计算以字节为单位的长度 - 因为您提到自己的原因:多字节字符、换行符等。

于 2010-05-28T16:48:32.093 回答
4

我做了一个类似的实现,我需要快速访问一个非常大的文本文件中的第 n 行。

正如您所料,指向文件末尾的原因streamReader.BaseStream.Position是它具有内置缓冲区。

通过计算从每个调用中读取的字节数进行簿记ReadLine()将适用于大多数纯文本文件。但是,我遇到过在文本文件中混合有控制字符(不可打印的字符)的情况。计算的字节数是错误的,导致我的程序此后无法找到正确的位置。

我的最终解决方案是自己实现行阅​​读器。到目前为止效果很好。这应该给出一些想法:

using (FileStream fs = new FileStream(filePath, FileMode.Open))
{
    int ch;
    int currentLine = 1, offset = 0;

    while ((ch = fs.ReadByte()) >= 0)
    {
        offset++;

        // This covers all cases: \r\n and only \n (for UNIX files)
        if (ch == 10)
        {
            currentLine++;

            // ... do sth such as log current offset with line number
        }
    }
}

并返回记录的偏移量:

using (FileStream fs = new FileStream(filePath, FileMode.Open))
{
    fs.Seek(yourOffset, SeekOrigin.Begin);
    TextReader tr = new StreamReader(fs);

    string line = tr.ReadLine();
}

另请注意,已经内置 FileStream了缓冲机制。

于 2010-05-28T17:04:39.877 回答
2

StreamReader不是为这种用法而设计的,所以如果这是你需要的,我怀疑你必须为FileStream.

于 2010-05-28T16:46:33.663 回答
1

已接受答案的一个问题是,如果 ReadLine() 遇到异常,例如由于日志框架在您 ReadLine() 时暂时正确锁定文件,那么您将不会将该行“保存”到列表中,因为它从未返回一条线。如果您捕获此异常,您将无法再次重试 ReadLine(),因为 StreamReaders 的内部状态和缓冲区已从上一次 ReadLine() 搞砸了,您只会返回部分行,并且您不能忽略该断线并寻求正如OP发现的那样回到它的开头。

如果你想到达真正的可搜索位置,那么你需要使用反射来获取 StreamReaders 私有变量,这些变量允许你计算它在它自己的缓冲区内的位置。格兰杰的解决方案在这里看到:StreamReader 和 seek,应该工作。或者做其他相关问题中的其他答案所做的事情:创建您自己的 StreamReader 以公开真正的可搜索位置(此链接中的此答案:Tracking the position of line of a streamreader)。这是我在处理 StreamReader 和寻找时遇到的仅有的两个选项,出于某种原因,这决定完全消除在几乎所有情况下寻找的可能性。

编辑:我使用了格兰杰的解决方案并且它有效。请确保您按以下顺序进行:GetActualPosition(),然后将 BaseStream.Position 设置为该位置,然后确保调用 DiscardBufferedData(),最后您可以调用 ReadLine(),您将获得从该位置开始的完整行方法中给出。

于 2015-11-06T18:25:46.170 回答