StreamReader和寻求

你可以使用streamreader来读取普通的文本文件,然后在保存当前位置之后在读取过程中关闭流读取器,然后再次打开streamreader并开始读取该poision吗?

如果不是我可以使用什么来完成相同的情况而不锁定文件?

像这样的东西:

var fs = File.Open(@"C:\testfile.txt", FileMode.Open, FileAccess.Read); var sr = new StreamReader(fs); Debug.WriteLine(sr.ReadLine());//Prints:firstline var pos = fs.Position; while (!sr.EndOfStream) { Debug.WriteLine(sr.ReadLine()); } fs.Seek(pos, SeekOrigin.Begin); Debug.WriteLine(sr.ReadLine());//Prints Nothing, i expect it to print SecondLine. 

@lasseespeholt

这是我试过的代码

  var position = -1; StreamReaderSE sr = new StreamReaderSE(@"c:\testfile.txt"); Debug.WriteLine(sr.ReadLine()); position = sr.BytesRead; Debug.WriteLine(sr.ReadLine()); Debug.WriteLine(sr.ReadLine()); Debug.WriteLine(sr.ReadLine()); Debug.WriteLine(sr.ReadLine()); Debug.WriteLine("Wait"); sr.BaseStream.Seek(position, SeekOrigin.Begin); Debug.WriteLine(sr.ReadLine()); 

是的你可以,看到这个:

 var sr = new StreamReader("test.txt"); sr.BaseStream.Seek(2, SeekOrigin.Begin); // Check sr.BaseStream.CanSeek first 

更新:请注意,您不一定sr.BaseStream.Position用于任何有用的内容,因为StreamReader使用缓冲区,因此它不会反映您实际读取的内容。 我想你会在找到真正的位置时遇到问题。 因为你不能只计算字符(不同的编码,因此字符长度)。 我认为最好的方法是使用FileStream自己。

更新:从这里使用TGREER.myStreamReader : http TGREER.myStreamReader此类添加了BytesRead等(与ReadLine()但显然不与其他读取方法一起使用)和然后你可以这样做:

 File.WriteAllText("test.txt", "1234\n56789"); long position = -1; using (var sr = new myStreamReader("test.txt")) { Console.WriteLine(sr.ReadLine()); position = sr.BytesRead; } Console.WriteLine("Wait"); using (var sr = new myStreamReader("test.txt")) { sr.BaseStream.Seek(position, SeekOrigin.Begin); Console.WriteLine(sr.ReadToEnd()); } 

我意识到这确实是迟来的,但我自己偶然发现了StreamReader这个令人难以置信的缺陷; 使用StreamReader时无法可靠搜索的事实。 就个人而言,我的具体需求是能够读取字符,但如果满足某个条件则“备份”; 这是我正在解析的文件格式之一的副作用。

使用ReadLine()不是一个选项,因为它只适用于非常简单的解析作业。 我必须支持可配置的记录/行分隔符序列并支持转义分隔符序列。 另外,我不想实现自己的缓冲区,所以我可以支持“备份”和转义序列; 这应该是StreamReader的工作。

此方法按需计算基础字节流中的实际位置。 它适用于UTF8,UTF-16LE,UTF-16BE,UTF-32LE,UTF-32BE和任何单字节编码(例如代码页1252,437,28591等),无论是否存在前同步码/ BOM。 此版本不适用于UTF-7,Shift-JIS或其他可变字节编码。

当我需要寻找底层流中的任意位置时,我直接设置BaseStream.Position然后调用DiscardBufferedData()以使StreamReader同步返回以进行下一个Read() / Peek()调用。

BaseStream.Position提醒:不要随意设置BaseStream.Position 。 如果你将一个字符二等分,你将使下一个Read()无效,对于UTF-16 / -32,你也会使这个方法的结果无效。

 public static long GetActualPosition(StreamReader reader) { System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetField; // The current buffer of decoded characters char[] charBuffer = (char[])reader.GetType().InvokeMember("charBuffer", flags, null, reader, null); // The index of the next char to be read from charBuffer int charPos = (int)reader.GetType().InvokeMember("charPos", flags, null, reader, null); // The number of decoded chars presently used in charBuffer int charLen = (int)reader.GetType().InvokeMember("charLen", flags, null, reader, null); // The current buffer of read bytes (byteBuffer.Length = 1024; this is critical). byte[] byteBuffer = (byte[])reader.GetType().InvokeMember("byteBuffer", flags, null, reader, null); // The number of bytes read while advancing reader.BaseStream.Position to (re)fill charBuffer int byteLen = (int)reader.GetType().InvokeMember("byteLen", flags, null, reader, null); // The number of bytes the remaining chars use in the original encoding. int numBytesLeft = reader.CurrentEncoding.GetByteCount(charBuffer, charPos, charLen - charPos); // For variable-byte encodings, deal with partial chars at the end of the buffer int numFragments = 0; if (byteLen > 0 && !reader.CurrentEncoding.IsSingleByte) { if (reader.CurrentEncoding.CodePage == 65001) // UTF-8 { byte byteCountMask = 0; while ((byteBuffer[byteLen - numFragments - 1] >> 6) == 2) // if the byte is "10xx xxxx", it's a continuation-byte byteCountMask |= (byte)(1 << ++numFragments); // count bytes & build the "complete char" mask if ((byteBuffer[byteLen - numFragments - 1] >> 6) == 3) // if the byte is "11xx xxxx", it starts a multi-byte char. byteCountMask |= (byte)(1 << ++numFragments); // count bytes & build the "complete char" mask // see if we found as many bytes as the leading-byte says to expect if (numFragments > 1 && ((byteBuffer[byteLen - numFragments] >> 7 - numFragments) == byteCountMask)) numFragments = 0; // no partial-char in the byte-buffer to account for } else if (reader.CurrentEncoding.CodePage == 1200) // UTF-16LE { if (byteBuffer[byteLen - 1] >= 0xd8) // high-surrogate numFragments = 2; // account for the partial character } else if (reader.CurrentEncoding.CodePage == 1201) // UTF-16BE { if (byteBuffer[byteLen - 2] >= 0xd8) // high-surrogate numFragments = 2; // account for the partial character } } return reader.BaseStream.Position - numBytesLeft - numFragments; } 

当然,这使用Reflection来获取私有变量,因此存在风险。 但是,此方法适用于.Net 2.0,3.0,3.5,4.0,4.0.3,4.5,4.5.1,4.5.2,4.6和4.6.1。 除了这个风险,唯一的另一个关键假设是底层字节缓冲区是一个byte[1024] ; 如果Microsoft以错误的方式更改它,则该方法会因UTF-16 / -32而中断。

这已针对填充了Ažテ𣘺 (10字节: 0x41 C5 BE E3 83 86 F0 A3 98 BA )的UTF-8文件和填充A𐐷的UTF-16文件(6字节: 0x41 00 01 D8 37 DC )进行了测试。 关键在于沿着byte[1024]边界强制分割字符,它们可以是所有不同的方式。

更新(2013-07-03) :我修复了该方法,该方法最初使用了其他答案中的破解代码。 此版本已针对包含需要使用代理项对的字符的数据进行了测试。 数据被放入3个文件中,每个文件具有不同的编码; 一个UTF-8,一个UTF-16LE和一个UTF-16BE。

更新(2016-02) :处理二等分字符的唯一正确方法是直接解释基础字节。 UTF-8处理得当,UTF-16 / -32工作(给定byteBuffer的长度)。

来自MSDN:

StreamReader设计用于特定编码的字符输入,而Stream类设计用于字节输入和输出。 使用StreamReader从标准文本文件中读取信息行。

在涉及StreamReader大多数示例中,您将看到使用ReadLine()逐行读取。 Seek方法来自Stream类,它主要用于读取或处理字节数据。

如果您只想在文本流中搜索起始位置,我将此扩展添加到StreamReader,以便我可以确定应该在哪里进行流编辑。 当然,这是基于字符作为逻辑的递增方面,但就我的目的而言,它非常有用,可以根据字符串模式在基于文本/ ASCII的文件中获取位置。 然后,您可以使用该位置作为读取的起点,编写一个新文件,在起始点之前排除数据。

可以将流中返回的位置提供给Seek以从基于文本的流读取中的该位置开始。 有用。 我测试过了。 但是,在匹配算法期间匹配非ASCII Unicode字符时可能会出现问题。 这是基于美国英语和相关的角色页面。

基础知识:它逐字符地扫描文本流,仅通过流向前查找顺序字符串模式(与字符串参数匹配)。 一旦模式与字符串参数不匹配(即前进,char by char),它将重新开始(从当前位置)尝试获得匹配,char-by-char。 如果在流中找不到匹配,它最终将退出。 如果找到匹配,则它将返回流中的当前“字符”位置,而不是StreamReader.BaseStream.Position,因为该位置在前面,基于StreamReader的缓冲。

如注释中所示,此方法将影响StreamReader的位置,并将在方法结束时将其设置回开头(0)。 应使用StreamReader.BaseStream.Seek运行此扩展返回的位置。

注意:此扩展返回的位置也可以在处理文本文件时使用BinaryReader.Seek作为起始位置。 为了这个目的,我实际上使用了这个逻辑,在丢弃PJL头信息之后将PostScript文件重写回磁盘,使文件成为GhostScript可以使用的“正确的”PostScript可读文件。 🙂

在PostScript中搜索的字符串(在PJL标题之后)是:“%!PS-”,后跟“Adobe”和版本。

 public static class StreamReaderExtension { ///  /// Searches from the beginning of the stream for the indicated /// . Once found, returns the position within the stream /// that the pattern begins at. ///  /// The string pattern to search for in the stream. /// If  is found in the stream, then the start position /// within the stream of the pattern; otherwise, -1. /// Please note: this method will change the current stream position of this instance of /// . When it completes, the position of the reader will /// be set to 0. public static long FindSeekPosition(this StreamReader reader, string pattern) { if (!string.IsNullOrEmpty(pattern) && reader.BaseStream.CanSeek) { try { reader.BaseStream.Position = 0; reader.DiscardBufferedData(); StringBuilder buff = new StringBuilder(); long start = 0; long charCount = 0; List matches = new List(pattern.ToCharArray()); bool startFound = false; while (!reader.EndOfStream) { char chr = (char)reader.Read(); if (chr == matches[0] && !startFound) { startFound = true; start = charCount; } if (startFound && matches.Contains(chr)) { buff.Append(chr); if (buff.Length == pattern.Length && buff.ToString() == pattern) { return start; } bool reset = false; if (buff.Length > pattern.Length) { reset = true; } else { string subStr = pattern.Substring(0, buff.Length); if (buff.ToString() != subStr) { reset = true; } } if (reset) { buff.Length = 0; startFound = false; start = 0; } } charCount++; } } finally { reader.BaseStream.Position = 0; reader.DiscardBufferedData(); } } return -1; } } 

由于底层缓冲发生,FileStream.Position(或等效地,StreamReader.BaseStream.Position)通常会提前 – 可能是提前 – TextReader位置。

如果可以确定文本文件中如何处理换行符,则可以根据行长度和行尾字符添加读取的字节数。

 File.WriteAllText("test.txt", "1234" + System.Environment.NewLine + "56789"); long position = -1; long bytesRead = 0; int newLineBytes = System.Environment.NewLine.Length; using (var sr = new StreamReader("test.txt")) { string line = sr.ReadLine(); bytesRead += line.Length + newLineBytes; Console.WriteLine(line); position = bytesRead; } Console.WriteLine("Wait"); using (var sr = new StreamReader("test.txt")) { sr.BaseStream.Seek(position, SeekOrigin.Begin); Console.WriteLine(sr.ReadToEnd()); } 

对于更复杂的文本文件编码,您可能需要比这更好,但它对我有用。