2009-05-06 6 views
8

例(それは現実の生活ではないかもしれませんが、私のポイントを作るために):ここテキストファイルのストリームリーダーの位置(linenumber)を知るには?

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

} 

GetLinePositionは、StreamReaderをの仮想延長方法です。 これは可能ですか?

もちろん、私は自分自身を数えることができますが、それは問題ではありません。

答えて

7

任意た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. ヌル
  2. のコンストラクタ引数をチェックしません
  3. 行を終了する別の方法を認識しません。 raw \ rまたは\ nで区切られたファイルを読み取るときにReadLine()の動作と矛盾します。
  4. 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; 
} 
+0

これは 'Seek'の使用についても説明していません。 –

10

いいえ、実際はありません。 「行番号」の概念は、位置だけでなく、すでに読み取られている実際のデータに基づいています。たとえば、読者を任意の位置にシークしようとすると、そのデータを読み取ることができず、行番号を特定できなくなります。

これを実行する唯一の方法は、自分で追跡することです。

+1

+1私はちょうど速く入力することができたら私の前にいました:) –

4

は、(任意の行の任意の点であってもよい)、基礎となるストリームオブジェクトを使用して、任意poisitionにシークすることが可能だと考えてみましょう。 ここで、StreamReaderによって保持されている数にどのような影響があるかを考えてみましょう。

StreamReaderを実行して、現在どの行にいるのか把握する必要がありますか? ファイル内の位置に関係なく、いくつかの行を読み取るだけですか?

imhoの実装に悪夢を抱かせる以上の疑問があります。

+2

+1の原因は、入力速度に依存しないはずです.-) – Peter

+0

一方、すでに述べられていることを繰り返すのか? (このポスターを言ってはいませんが、一般的には可能でしょう!) –

+0

@ダグ:あまり繰り返さない、同時に言ったように。 。 。ジンクス! (今は、ダグを買ってみたいですか?) –

3

ファイルの位置を登録するReadLine()メソッドでStreamReaderを実装した人がいます。

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

私は1つがStreamReaderを継承し、その後、いくつかのプロパティ(_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. 
// 
/// <include file='doc\myStreamReader.uex' path='docs/doc[@for="myStreamReader.ReadLine"]/*' /> 
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エンコードファイルのサポートの欠落)を使用する代わりに、文字列の長さがファイルの位置を計算するために使用されます。

1

すでにBaseStreamに関して作られたポイントは、有効かつ重要です。しかし、テキストを読んで、テキストのどこにいるかを知りたいという状況があります。再利用を容易にするためのクラスとして書くことは依然として有効です。

私はそのようなクラスを今書きました。それは正しく動作するようですが、それはむしろ遅いです。パフォーマンスが重要ではない場合は、それは問題ありません(で、が遅い、下記を参照)。

一度に1つの文字、一度に1つのバッファ、または一度に1つの行を読み込んでも、同じロジックを使用してテキストの位置を追跡します。これを放棄することで、これをより良く実行することができると確信していますが、実装するのがずっと簡単になりました。コードを守ることを願っています。

私はReadLineメソッド(この実装の最も弱い点だと思います)とStreamReaderの非常に基本的なパフォーマンス比較を行いました。その違いはほぼ桁違いです。私はクラスStreamReaderExを使って22 MB/sを得ましたが、StreamReaderを直接使って(SSDを搭載したラップトップでは)ほぼ9倍でした。面白いかもしれませんが、私は適切な読書テストをする方法を知らない。多分ディスクバッファよりも大きい2つの同一のファイルを使用して、それらを交互に読みます。少なくとも私の単純なテストでは、何回か実行すると一貫した結果が得られ、どのクラスがテストファイルを最初に読み込んでも問題はありません。

デフォルトでは、NewLineシンボルはEnvironment.NewLineに設定されますが、長さが1または2の任意の文字列に設定することができます。リーダーはこのシンボルのみを改行と見なします。少なくとも私は、Visual Studioが私に開いたファイルに「改行が矛盾している」というかなりの回数のメッセージを表示していることを知っています。

Guardクラスは含まれていません。これはシンプルなユーティリティクラスであり、コンテキストからオブジェクトを置き換える方法はobvoiusでなければなりません。あなたはそれを削除することもできますが、引数のチェックを失うことになり、結果として得られるコードは "正しい"ものから遠くなるでしょう。たとえば、Guard.NotNull(s、 "s")は、sがnullでないかどうかを単純にチェックし、ArgumentNullException(引数名 "s"、したがって2番目のパラメータ)を投げます。

十分なせせらぎ、ここでのコードは次のとおりです。

 

public class StreamReaderEx : StreamReader 
{ 
    // NewLine characters (magic value -1: "not used"). 
    int newLine1, newLine2; 

    // The last character read was the first character of the NewLine symbol AND we are using a two-character symbol. 
    bool insideNewLine; 

    // StringBuilder used for ReadLine implementation. 
    StringBuilder lineBuilder = new StringBuilder(); 


    public StreamReaderEx(string path, string newLine = "\r\n") : base(path) 
    { 
     init(newLine); 
    } 


    public StreamReaderEx(Stream s, string newLine = "\r\n") : base(s) 
    { 
     init(newLine); 
    } 


    public string NewLine 
    { 
     get { return "" + (char)newLine1 + (char)newLine2; } 
     private set 
     { 
      Guard.NotNull(value, "value"); 
      Guard.Range(value.Length, 1, 2, "Only 1 to 2 character NewLine symbols are supported."); 

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


    public int LineNumber { get; private set; } 
    public int LinePosition { get; private set; } 


    public override int Read() 
    { 
     int next = base.Read(); 
     trackTextPosition(next); 
     return next; 
    } 


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

偉大な、私のコードはちょうど途中でカットオフだった。誰かが興味を持っているかどうかを確認する機会を取る。もしそうなら、私に知らせて、私は残りを掲示します。 –

3

私はシンプルなものを探してここに来ました。あなただけのReadLineメソッドを()を使用していると(シーク)か何か使って気にしないのであれば、単に

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(); 
    } 
} 

StreamReaderを

の簡単なサブクラスを作り、その後、FileInfoオブジェクトから言って、それを通常の方法を作ります名前付きファイル

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

あなたはちょうどreader.LineNumberプロパティを読んでいます。

+0

良い答えですが、 'ReadLine'があなたが呼んでいる_only_メソッドである場合にのみ動作することを明確にすべきです。 –

13

私はStreamReaderを特定の行に探す必要がある同様の問題の解決策を探しています。私は2つの拡張メソッドを作成し、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); 
    } 
} 

警告:ほとんどすべて私がこれを消費するデータの

私は様々なSystem.Text.Encodingオプションを使用していくつかの簡単なテストを行っているが
  1. は、単純なテキストファイル(ASCII)です。
  2. 私はこれまでStreamReader.ReadLine()メソッドを使用していましたが、StreamReaderのソースを簡単に見直している間に他の読み取りメソッドを使用してもこのメソッドが動作することを示しているようですが、
+0

'System.Text.Encoding.UTF8'で動作します。 – CrazyIvan1974

関連する問題