我正在尝试一次读取几行中的(小型)文件,我需要返回到特定块的开头。
问题是,在第一次调用
之后streamReader.ReadLine();
streamReader.BaseStream.Position
属性设置为文件末尾!现在我假设在后台完成了一些缓存,但我希望这个属性能够反映 I 从该文件中使用的字节数。是的,该文件有多行:-)
例如,再次调用ReadLine()
将(自然地)返回文件中的下一行,该行不会从streamReader.BaseStream.Position
之前报告的位置开始。
如何找到第一行结束的实际位置,以便稍后返回?
我只能通过添加ReadLine()返回的字符串长度来考虑手动执行簿记,但即使在这里也有一些注意事项:
...所以现在似乎我唯一的选择是重新思考我如何解析文件,所以我不必倒带。
如果有帮助,我打开我的文件:
using (var reader = new StreamReader(
new FileStream(
m_path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite)))
{...}
有什么建议吗?
答案 0 :(得分:4)
如果您需要读取行,并且需要返回上一个块,为什么不将您在行列中读取的行存储?这应该很容易。
你不应该依赖于根据字符串的长度计算长度(以字节为单位) - 由于你自己提到的原因:多字节字符,换行字符等。
答案 1 :(得分: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();
}
另请注意,已存在缓冲机制built into FileStream
。
答案 2 :(得分:2)
StreamReader
不适用于此类用法,因此,如果您需要这些,我怀疑您必须为FileStream
编写自己的包装。
答案 3 :(得分:1)
接受答案的一个问题是,如果ReadLine()遇到异常,比如由于日志记录框架在ReadLine()时暂时锁定文件,那么你将没有那条线"已保存&#34 ;进入列表,因为它从未返回一行。如果您捕获此异常,则无法再次重试ReadLine(),因为StreamReaders内部状态和缓冲区从最后一个ReadLine()中被搞砸,并且您将只返回返回的一部分行,并且您不能忽略该断行并寻求当OP发现时,回到它的开头。
如果你想到达真正的可搜索位置,那么你需要使用反射来获取StreamReaders私有变量,这些变量允许你计算它在自己的缓冲区中的位置。格兰杰在这里看到的解决方案:StreamReader and seeking,应该可行。或者做其他相关问题中的其他答案:创建自己的StreamReader,公开真正的可搜索位置(此链接中的答案:Tracking the position of the line of a streamreader)。这是我在处理StreamReader和寻求时遇到的唯一两个选项,由于某种原因决定完全消除在几乎所有情况下寻求的可能性。
编辑:我使用了格兰杰的解决方案并且有效。请确保按顺序执行:GetActualPosition(),然后将BaseStream.Position设置为该位置,然后确保调用DiscardBufferedData(),最后可以调用ReadLine(),然后从位置开始获取完整的行在方法中给出。