Frage

ein Beispiel (das nicht die wirkliche Leben sein könnte, aber meinen Punkt zu machen):

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

}

GetLinePosition hier ist eine imaginäre Verlängerung Methode von Stream. Ist das möglich?

Natürlich kann ich mich zählen halten, aber das ist nicht die Frage.

War es hilfreich?

Lösung

Es ist extrem einfach eine Linie Zählung Wrapper für jeden Textreader zur Verfügung zu stellen:

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++;
        }
    }
}

Nachteile (aus Gründen der Kürze):

  1. Prüft Konstruktorargument nicht für null
  2. erkennen alternative Möglichkeiten, um die Linien zu beenden. Wird mit Readline () Verhalten inkonsistent sein, wenn Dateien getrennt von rohen \ r oder \ n zu lesen.
  3. enthebt nicht "Block" -Niveau Methoden wie Read (char [], int, int), Lesen-Block, Readline, ReadToEnd. Textreader Implementierung korrekt funktioniert, da es Strecken alles andere zu lesen (); könnte jedoch eine bessere Leistung erreicht werden durch
    • diese Methoden über Routing zwingende Anrufe an _inner. anstelle von Basis.
    • Führen der auf den AdvancePosition gelesenen Zeichen. Siehe die Probe Lies-Block-Implementierung:

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

Andere Tipps

ich auf diesen Beitrag cam, während für eine Lösung für ein ähnliches Problem, wo ich benötigt, um die Stream auf bestimmte Linien zu suchen. Ich beendete zwei Erweiterungsmethoden auf die Erstellung zu erhalten und stellen die Position auf einer Stream. Es spielt eigentlich keine Zeilennummer Zahl liefern, aber in der Praxis, ich greife nur die Position vor jedem Readline () und wenn die Linie von Interesse ist, dann halte ich die Startposition später zur Einstellung wie so auf die Linie zurück zu bekommen :

var index = streamReader.GetPosition();
var line1 = streamReader.ReadLine();

streamReader.SetPosition(index);
var line2 = streamReader.ReadLine();

Assert.AreEqual(line1, line2);

und der wichtige Teil:

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

Dies funktioniert für mich ganz gut und je nach Toleranz für Reflexion unter Verwendung Es denkt, es ist eine ziemlich einfache Lösung ist.

Caveats:

  1. Während ich einige einfache Tests verschiedene System.Text.Encoding Optionen getan haben, verwenden, so ziemlich alle die Daten, die ich mit diesem konsumieren sind einfache Textdateien (ASCII).
  2. ich je nur die StreamReader.ReadLine () -Methode verwenden, und während ein kurzer Überblick über die Quelle für Stream scheint dies anzuzeigen wird immer noch funktionieren, wenn die anderen Lesemethoden verwenden, ich habe wirklich dieses Szenario nicht getestet.

Nein, nicht wirklich möglich. Das Konzept einer „Zeilennummer“ wird auf die tatsächlichen Daten zugrunde, dass nicht nur die Position ist bereits gelesen. Zum Beispiel, wenn Sie zu suchen sind (), um den Leser auf eine beliebige Position, ist es nicht, dass die Daten lesen Actuall gehen, so wäre es nicht in der Lage sein, die Zeilennummer zu bestimmen.

Der einzige Weg, dies zu tun ist, um sich Überblick zu behalten.

Nein.

Beachten Sie, dass es möglich ist, zu jeder poisition suchen Objekt mit dem zugrunde liegenden Stream (die an einem beliebigen Stelle in einem beliebigen Zeile sein könnten). Betrachten wir nun, was das für jede Zählung durch die Stream gehalten tun würde.

Sollte der Stream gehen und herauszufinden, welche Linie es nun an ist? Sollte es nur hält eine Anzahl von Zeilen zu lesen, unabhängig von ihrer Position innerhalb der Datei?

Es gibt mehr Fragen als nur diese, dass dies ein Alptraum imho zu implementieren, machen würde.

Hier ist ein Mann, der einen Stream mit Readline () Methode implementiert, die Dateiposition registriert.

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

Ich denke, man sollte von Stream erbt und dann die zusätzliche Methode, um die Sonderklasse hinzufügen, zusammen mit einigen Eigenschaften (_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;
 }

Denken Sie sich ein kleiner Fehler im Code ist als die Länge der Zeichenfolge verwendet wird Dateiposition zu berechnen, anstatt das tatsächliche Bytes der Verwendung lesen (Unterstützung Mangelnde für UTF8 und UTF16 kodierten Dateien).

Ich habe hier auf der Suche nach etwas einfach. Wenn Sie nur mit Readline () und kümmern sich nicht um seek () oder etwas, nur eine einfache Unterklasse von Stream machen

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

und dann machen Sie die normale Art und Weise, sagen wir von einem Fileinfo-Objekt namens Datei

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

und Sie nur die reader.LineNumber Eigenschaft lesen.

Die Punkte bereits in Bezug auf die Base sind gültig und wichtig. Es gibt jedoch Situationen, in denen Sie einen Text lesen möchten und wissen, wo in dem Text, den Sie sind. Es kann immer noch nützlich sein, dass als eine Klasse zu schreiben, um es einfach wieder zu verwenden.

Ich habe versucht nun, eine solche Klasse zu schreiben. Es scheint, richtig zu arbeiten, aber es ist ziemlich langsam. Es sollte in Ordnung sein, wenn die Leistung nicht entscheidend ist (es ist nicht , die langsam, siehe unten).

Ich benutze die gleiche Logik Position unabhängig in dem Text zu verfolgen, wenn Sie ein Zeichen zu einem Zeitpunkt, zu lesen, einen Puffer zu einem Zeitpunkt, oder eine Zeile zu einem Zeitpunkt. Während ich sicher bin, kann dies durch den Verzicht auf diese eher eine bessere Leistung gemacht werden, es machte es viel einfacher zu implementieren ... und, wie ich hoffe, den Code zu folgen.

Ich habe eine sehr grundlegende Performance Vergleich der Readline-Methode (was ich glaube der schwächste Punkt dieser Implementierung ist) zu Stream, und der Unterschied ist fast eine Größenordnung. Ich habe 22 MB / s meine Klasse StreamReaderEx verwenden, aber fast 9-mal so viel mit Stream direkt (auf meinem SSD ausgestatteten Laptop). Während es interessant sein könnte, weiß ich nicht, wie man einen richtigen Lesetest zu machen; vielleicht mit zwei identischen Dateien, die jeweils größer als die Plattenpuffer und liest sie abwechselnd ..? Zumindest mein einfacher Test konsistente Ergebnisse, wenn ich es mehrmals ausgeführt werden, und unabhängig davon, welche Klasse liest die Testdatei zuerst.

Das NewLine Symbol standardmäßig Environment.NewLine sondern kann auf jede beliebige Zeichenfolge mit einer Länge von 1 oder 2. Der Leser hält nur dieses Symbol als eine neue Zeile gesetzt werden, was ein Nachteil sein kann. Wenigstens weiß ich Visual Studio mir eine faire einige Male aufgefordert hat, dass eine Datei, die ich offen „inkonsistent Zeilenumbrüche hat“.

Bitte beachten Sie, dass ich nicht den Schutz Klasse enthalten haben; dies ist eine einfache Utility-Klasse, und es sollte aus dem Kontext obvoius sein, wie es zu ersetzen. Sie können es sogar entfernen, aber Sie würden eine Überprüfung der Argumente verlieren und damit der resultierende Code wäre weiter von „richtig“. Zum Beispiel Guard.NotNull (s, „s“) prüft einfach, dass ist s nicht null ist, ein Argument werfen (mit dem Argumente Namen „s“, daher der zweite Parameter) sollte es der Fall sein.

Genug Geschwätz, hier ist der Code:

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 
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top