如何知道文本文件中streamreader的位置(linenumber)?

一个例子(可能不是现实生活,但要说明我的观点):

public void StreamInfo(StreamReader p) { string info = string.Format( "The supplied streamreaer read : {0}\n at line {1}", p.ReadLine(), p.GetLinePosition()-1); } 

这里的GetLinePositionGetLinePosition的虚构扩展方法。 这可能吗?

当然,我可以自己计算,但这不是问题。

为任何TextReader提供行计数包装非常容易:

 public class PositioningReader : TextReader { private TextReader _inner; public PositioningReader(TextReader inner) { _inner = inner; } public override void Close() { _inner.Close(); } public override int Peek() { return _inner.Peek(); } public override int Read() { var c = _inner.Read(); if (c >= 0) AdvancePosition((Char)c); return c; } private int _linePos = 0; public int LinePos { get { return _linePos; } } private int _charPos = 0; public int CharPos { get { return _charPos; } } private int _matched = 0; private void AdvancePosition(Char c) { if (Environment.NewLine[_matched] == c) { _matched++; if (_matched == Environment.NewLine.Length) { _linePos++; _charPos = 0; _matched = 0; } } else { _matched = 0; _charPos++; } } } 

缺点(为简洁起见):

  1. 不检查null的构造函数参数
  2. 无法识别终止线路的其他方法。 读取由raw \ r或\ n分隔的文件时,与ReadLine()行为不一致。
  3. 不会覆盖“块”级方法,如Read(char [],int,int),ReadBlock,ReadLine,ReadToEnd。 TextReader实现正常工作,因为它将其他所有内容路由到Read(); 但是,可以通过实现更好的性能
    • 通过将调用路由到_inner来覆盖这些方法。 而不是基地。
    • 将读取的字符传递给AdvancePosition。 请参阅示例ReadBlock实现:

 public override int ReadBlock(char[] buffer, int index, int count) { var readCount = _inner.ReadBlock(buffer, index, count); for (int i = 0; i < readCount; i++) AdvancePosition(buffer[index + i]); return readCount; } 

我在寻找类似问题的解决方案的同时,我需要寻找StreamReader到特定的行。 我最终创建了两个扩展方法来获取和设置StreamReader上的位置。 它实际上并没有提供行号计数,但实际上,我只是抓住每个ReadLine()之前的位置,如果该行感兴趣,那么我保留起始位置以便稍后设置回到这样的行:

 var index = streamReader.GetPosition(); var line1 = streamReader.ReadLine(); streamReader.SetPosition(index); var line2 = streamReader.ReadLine(); Assert.AreEqual(line1, line2); 

而重要的部分:

 public static class StreamReaderExtensions { readonly static FieldInfo charPosField = typeof(StreamReader).GetField("charPos", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | BindingFlags.DeclaredOnly); readonly static FieldInfo byteLenField = typeof(StreamReader).GetField("byteLen", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | BindingFlags.DeclaredOnly); readonly static FieldInfo charBufferField = typeof(StreamReader).GetField("charBuffer", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | BindingFlags.DeclaredOnly); public static long GetPosition(this StreamReader reader) { //shift position back from BaseStream.Position by the number of bytes read //into internal buffer. int byteLen = (int)byteLenField.GetValue(reader); var position = reader.BaseStream.Position - byteLen; //if we have consumed chars from the buffer we need to calculate how many //bytes they represent in the current encoding and add that to the position. int charPos = (int)charPosField.GetValue(reader); if (charPos > 0) { var charBuffer = (char[])charBufferField.GetValue(reader); var encoding = reader.CurrentEncoding; var bytesConsumed = encoding.GetBytes(charBuffer, 0, charPos).Length; position += bytesConsumed; } return position; } public static void SetPosition(this StreamReader reader, long position) { reader.DiscardBufferedData(); reader.BaseStream.Seek(position, SeekOrigin.Begin); } } 

这对我来说非常好,并且取决于你对使用reflection的容忍度它认为这是一个相当简单的解决方案。

注意事项:

  1. 虽然我使用各种System.Text.Encoding选项进行了一些简单的测试,但我使用的几乎所有数据都是简单的文本文件(ASCII)。
  2. 我只使用StreamReader.ReadLine()方法,虽然简要回顾StreamReader的源代码似乎表明这在使用其他读取方法时仍然有效,但我还没有真正测试过这种情况。

不,不太可能。 “行号”的概念基于已经读取的实际数据,而不仅仅是位置。 例如,如果您要将读取器Seek()移到任意位置,那么它不会实际读取该数据,因此无法确定行号。

唯一的方法是自己跟踪它。

没有。

考虑到可以使用底层流对象(可以在任何行中的任何点)寻找任何poisition。 现在考虑一下StreamReader保留的任何计数会做什么。

StreamReader应该去找出它现在在哪条线上吗? 它是否应该只读取多行,而不管文件中的位置如何?

imho,还有更多的问题不仅仅是让这成为一个噩梦。

这是一个用ReadLine()方法实现StreamReader的人,它注册文件位置。

http://www.daniweb.com/forums/thread35078.html

我想应该从StreamReaderinheritance,然后将额外的方法添加到特殊类以及一些属性(_lineLength + _bytesRead):

  // Reads a line. A line is defined as a sequence of characters followed by // a carriage return ('\r'), a line feed ('\n'), or a carriage return // immediately followed by a line feed. The resulting string does not // contain the terminating carriage return and/or line feed. The returned // value is null if the end of the input stream has been reached. // ///  public override String ReadLine() { _lineLength = 0; //if (stream == null) // __Error.ReaderClosed(); if (charPos == charLen) { if (ReadBuffer() == 0) return null; } StringBuilder sb = null; do { int i = charPos; do { char ch = charBuffer[i]; int EolChars = 0; if (ch == '\r' || ch == '\n') { EolChars = 1; String s; if (sb != null) { sb.Append(charBuffer, charPos, i - charPos); s = sb.ToString(); } else { s = new String(charBuffer, charPos, i - charPos); } charPos = i + 1; if (ch == '\r' && (charPos < charLen || ReadBuffer() > 0)) { if (charBuffer[charPos] == '\n') { charPos++; EolChars = 2; } } _lineLength = s.Length + EolChars; _bytesRead = _bytesRead + _lineLength; return s; } i++; } while (i < charLen); i = charLen - charPos; if (sb == null) sb = new StringBuilder(i + 80); sb.Append(charBuffer, charPos, i); } while (ReadBuffer() > 0); string ss = sb.ToString(); _lineLength = ss.Length; _bytesRead = _bytesRead + _lineLength; return ss; } 

认为代码中存在一个小错误,因为字符串的长度用于计算文件位置而不是使用读取的实际字节数(缺少对UTF8和UTF16编码文件的支持)。

我来这里寻找简单的东西。 如果您只是使用ReadLine()并且不关心使用Seek()或其他任何东西,那么只需创建一个简单的StreamReader子类

 class CountingReader : StreamReader { private int _lineNumber = 0; public int LineNumber { get { return _lineNumber; } } public CountingReader(Stream stream) : base(stream) { } public override string ReadLine() { _lineNumber++; return base.ReadLine(); } } 

然后你从正常的方式开始,比如来自名为file的FileInfo对象

 CountingReader reader = new CountingReader(file.OpenRead()) 

你只需阅读reader.LineNumber属性。

已经针对BaseStream做出的分数是有效且重要的。 但是,在某些情况下,您需要阅读文本并知道文本的位置。 将其编写为类以使其易于重用仍然是有用的。

我现在试着写这样一堂课。 它似乎工作正常,但它相当慢。 当性能不是至关重要时(它不是那么慢,见下文)应该没问题。

无论您是一次读取一个字符,一次读取一个缓冲区,还是一次读取一行,我都使用相同的逻辑来跟踪文本中的位置。 虽然我确信通过放弃这个可以让它更好地执行,但它使实现起来更容易……而且,我希望,遵循代码。

我对ReadLine方法(我相信这是该实现中最薄弱的一点)与StreamReader进行了非常基本的性能比较,差异几乎是一个数量级。 我使用StreamReaderEx类获得了22 MB / s,但使用StreamReader直接使用了近9倍(在我配备SSD的笔记本电脑上)。 虽然它可能很有趣,但我不知道如何进行正确的阅读测试; 可能使用2个相同的文件,每个文件都大于磁盘缓冲区,并交替读取它们。 当我多次运行它时,至少我的简单测试会产生一致的结果,而不管哪个类首先读取测试文件。

NewLine符号默认为Environment.NewLine,但可以设置为长度为1或2的任何字符串。阅读器仅将此符号视为换行符,这可能是一个缺点。 至少我知道Visual Studio已经提示我很多次我打开的文件“有不一致的换行符”。

请注意,我没有包括Guard类; 这是一个简单的实用程序类,它应该是从上下文如何替换它的obvoius。 你甚至可以删除它,但是你会丢失一些参数检查,因此产生的代码将远离“正确”。 例如,Guard.NotNull(s,“s”)只是检查s是否为空,抛出ArgumentNullException(参数名称为“s”,因此第二个参数)应该是这种情况。

够唠叨,这是代码:


公共类StreamReaderEx:StreamReader
 {
     // NewLine字符(魔术值-1:“未使用”)。
     int newLine1,newLine2;

     //读取的最后一个字符是NewLine符号的第一个字符,我们使用的是两个字符的符号。
     bool insideNewLine;

     // StringBuilder用于ReadLine实现。
     StringBuilder lineBuilder = new StringBuilder();


     public StreamReaderEx(string path,string newLine =“\ r \ n”):base(path)
     {
        的init(换行);
     }


     public StreamReaderEx(Stream s,string newLine =“\ r \ n”):base(s)
     {
        的init(换行);
     }


    公共字符串NewLine
     {
         get {return“”+(char)newLine1 +(char)newLine2;  }
        私人套装
         {
             Guard.NotNull(值,“值”);
             Guard.Range(value.Length,1,2,“仅支持1到2个字符的NewLine符号。”);

             newLine1 = value [0];
             newLine2 =(value.Length == 2?value [1]: -  1);
         }
     }


     public int LineNumber {get; 私人集;  }
     public int LinePosition {get; 私人集;  }


     public override int Read()
     {
         int next = base.Read();
         trackTextPosition(下);
        返回;
     }


     public override int Read(char [] buffer,int index,int count)
     {
         int n = base.Read(buffer,index,count);
         for(int i = 0; i