10

例(これは実際の生活ではないかもしれませんが、私の主張を明確にするためです):

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の架空の拡張メソッドです。これは可能ですか?

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

4

7 に答える 7

27

StreamReaderを特定の行に探す必要がある同様の問題の解決策を探しているときに、この投稿に出くわしました。StreamReaderで位置を取得および設定するために、2つの拡張メソッドを作成することになりました。実際には行番号のカウントは提供されませんが、実際には、それぞれの前の位置を取得し、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", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
    readonly static FieldInfo byteLenField = typeof(StreamReader).GetField("byteLen", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
    readonly static FieldInfo charBufferField = typeof(StreamReader).GetField("charBuffer", BindingFlags.NonPublic | 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);
    }
}

これは私にとって非常にうまく機能し、反射を使用するための許容範囲によっては、かなり単純な解決策だと思います。

警告:

  1. さまざまなSystems.Text.Encodingオプションを使用していくつかの簡単なテストを行いましたが、これで使用するデータのほとんどは単純なテキストファイルです(ASCII)
  2. 私はこれまでこのStreamReader.ReadLine()メソッドのみを使用しており、StreamReaderのソースを簡単に確認すると、他の読み取りメソッドを使用してもこれが機能することが示されているようですが、実際にはそのシナリオをテストしていません。
于 2014-04-09T23:10:09.767 に答える
11

いいえ、実際には不可能です。「行番号」の概念は、位置だけでなく、すでに読み取られた実際のデータに基づいています。たとえば、リーダーを任意の位置にSeek()すると、実際にはそのデータが読み取られないため、行番号を判別できません。

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

于 2009-05-06T13:36:34.240 に答える
8

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. 回線を終了する別の方法を認識しません。生の\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;
}
于 2012-01-16T07:18:53.233 に答える
5

いいえ。

基になるストリームオブジェクト(任意の行の任意のポイントにある可能性があります)を使用して、任意の位置をシークできることを考慮してください。次に、StreamReaderによって保持されるカウントに対してそれがどのように機能するかを検討します。

StreamReaderを使用して、現在どの行にあるかを把握する必要がありますか?ファイル内の位置に関係なく、読み取り済みの行数を維持する必要がありますか?

これらだけでなく、これを実装するのが悪夢になるような質問がたくさんあります。

于 2009-05-06T13:37:57.547 に答える
3

これは、ファイルの位置を登録するReadLine()メソッドを使用してStreamReaderを実装した人です。

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

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でエンコードされたファイルのサポートがありません)。

于 2010-10-06T07:59:35.970 に答える
2

シンプルなものを探しにここに来ました。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ます。

于 2014-02-18T23:00:24.570 に答える
1

BaseStreamに関してすでに述べた点は、有効で重要です。ただし、テキストを読み、テキストのどこにいるかを知りたい場合があります。再利用を容易にするために、それをクラスとして作成することは依然として有用です。

私は今、そのようなクラスを書こうとしました。正しく動作しているように見えますが、かなり遅いです。パフォーマンスが重要でない場合は問題ないはずです(それほど遅くはありません。以下を参照してください)。

一度にcharを読み取るか、一度に1つのバッファーを読み取るか、または一度に1行を読み取るかに関係なく、同じロジックを使用してテキスト内の位置を追跡します。これを放棄することで、パフォーマンスをかなり向上させることができると確信していますが、実装がはるかに簡単になりました...そして、コードに従うことを願っています。

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

NewLineシンボルのデフォルトはEnvironment.NewLineですが、長さ1または2の任意の文字列に設定できます。読者はこのシンボルのみを改行と見なします。これは欠点となる可能性があります。少なくとも、Visual Studioによって、開いたファイルに「一貫性のない改行がある」というかなりの回数のプロンプトが表示されたことはわかっています。

Guardクラスは含まれていないことに注意してください。これは単純なユーティリティクラスであり、それを置き換える方法のコンテキストからはわかりにくいはずです。それを削除することもできますが、引数のチェックが失われるため、結果のコードは「正しい」とはほど遠いものになります。たとえば、Guard.NotNull(s、 "s")は、sがnullでないことを単にチェックし、その場合はArgumentNullException(引数名 "s"、したがって2番目のパラメーター)をスローします。

十分なせせらぎ、ここにコードがあります:

パブリッククラスStreamReaderEx:StreamReader
{{
    //改行文字(魔法の値-1:「未使用」)。
    int newLine1、newLine2;

    //最後に読み取られた文字は、改行記号の最初の文字であり、2文字の記号を使用しています。
    bool insideNewLine;

    //ReadLineの実装に使用されるStringBuilder。
    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);
    }


    パブリック文字列改行
    {{
        get {return "" +(char)newLine1 +(char)newLine2; }
        プライベートセット
        {{
            Guard.NotNull(value、 "value");
            Guard.Range(value.Length、1、2、 "1〜2文字の改行記号のみがサポートされています。");

            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(next);
        次に戻る;
    }


    public override int Read(char [] buffer、int index、int count)
    {{
        int n = base.Read(buffer、index、count);
        for(int i = 0; i
于 2012-01-23T13:04:18.250 に答える