Come conoscere la posizione (numero riga) di uno streamreader in un file di testo?

StackOverflow https://stackoverflow.com/questions/829568

  •  06-07-2019
  •  | 
  •  

Domanda

un esempio (che potrebbe non essere la vita reale, ma per fare il mio punto):

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

}

GetLinePosition qui è un metodo di estensione immaginario di streamreader. È possibile?

Ovviamente potrei continuare a contare me stesso ma non è questa la domanda.

È stato utile?

Soluzione

È estremamente semplice fornire un wrapper per il conteggio delle righe per qualsiasi 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++;
        }
    }
}

Svantaggi (per brevità):

  1. Non controlla l'argomento del costruttore per null
  2. Non riconosce modi alternativi per terminare le linee. Sarà incompatibile con il comportamento di ReadLine () durante la lettura di file separati da raw \ r o \ n.
  3. Non sovrascrive i metodi "livello" di blocco " come Read (char [], int, int), ReadBlock, ReadLine, ReadToEnd. L'implementazione di TextReader funziona correttamente poiché indirizza tutto il resto a Read (); tuttavia, si potrebbero ottenere prestazioni migliori
    • sovrascrivendo questi metodi tramite l'instradamento delle chiamate a _inner. anziché base.
    • passando i caratteri letti in AdvancePosition. Vedi l'implementazione di esempio 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;
}

Altri suggerimenti

Ho cercato questo post mentre cercavo una soluzione a un problema simile in cui dovevo cercare lo StreamReader su determinate linee. Ho finito per creare due metodi di estensione per ottenere e impostare la posizione su uno StreamReader. In realtà non fornisce un conteggio del numero di riga, ma in pratica, prendo la posizione prima di ogni ReadLine () e se la linea è di interesse, quindi mantengo la posizione iniziale per l'impostazione in seguito per tornare alla riga in questo modo :

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

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

Assert.AreEqual(line1, line2);

e la parte importante:

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

Questo funziona abbastanza bene per me e, a seconda della tua tolleranza per l'uso della riflessione, pensa che sia una soluzione abbastanza semplice.

Avvertenze:

  1. Mentre ho fatto alcuni semplici test usando varie opzioni System.Text.Encoding, praticamente tutti i dati che consumo con questo sono semplici file di testo (ASCII).
  2. Uso sempre e solo il metodo StreamReader.ReadLine () e mentre una breve revisione della fonte per StreamReader sembra indicare che funzionerà ancora quando utilizzo gli altri metodi di lettura, non ho ancora testato quello scenario.

No, non proprio possibile. Il concetto di un "numero di riga" si basa sui dati effettivi già letti, non solo sulla posizione. Ad esempio, se dovessi cercare () il lettore in una posizione arbitraria, non sarà effettivamente in grado di leggere quei dati, quindi non sarebbe in grado di determinare il numero di riga.

L'unico modo per farlo è di tenerne traccia da soli.

No.

Considera che è possibile cercare qualsiasi posizione usando l'oggetto stream sottostante (che potrebbe trovarsi in qualsiasi punto di qualsiasi linea). Ora considera cosa farebbe a qualsiasi conto tenuto da StreamReader.

StreamReader dovrebbe andare a capire quale linea è ora attiva? Dovrebbe solo leggere un numero di righe, indipendentemente dalla posizione all'interno del file?

Ci sono più domande che solo queste che renderebbero questo un incubo da implementare, imho.

Ecco un ragazzo che ha implementato uno StreamReader con il metodo ReadLine () che registra la posizione del file.

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

Suppongo che uno dovrebbe ereditare da StreamReader, quindi aggiungere il metodo extra alla classe speciale insieme ad alcune proprietà (_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;
 }

Pensa che ci sia un bug minore nel codice poiché la lunghezza della stringa viene utilizzata per calcolare la posizione del file invece di utilizzare i byte effettivamente letti (Mancanza di supporto per i file codificati UTF8 e UTF16).

Sono venuto qui alla ricerca di qualcosa di semplice. Se stai solo usando ReadLine () e non ti interessa usare Seek () o altro, crea una semplice sottoclasse di 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();
    }
}

e poi lo fai nel modo normale, diciamo da un oggetto FileInfo chiamato file

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

e hai appena letto la proprietà reader.LineNumber .

I punti già fatti rispetto a BaseStream sono validi e importanti. Tuttavia, ci sono situazioni in cui vuoi leggere un testo e sapere dove ti trovi nel testo. Può ancora essere utile scriverlo come classe per facilitarne il riutilizzo.

Ho provato a scrivere una lezione del genere ora. Sembra funzionare correttamente, ma è piuttosto lento. Dovrebbe andare bene quando le prestazioni non sono cruciali (non è così lento, vedi sotto).

Uso la stessa logica per tenere traccia della posizione nel testo, indipendentemente dal fatto che tu legga un carattere alla volta, un buffer alla volta o una riga alla volta. Mentre sono sicuro che questo può essere fatto per funzionare piuttosto meglio abbandonandolo, ha reso molto più facile l'implementazione ... e, spero, seguire il codice.

Ho fatto un confronto di base delle prestazioni del metodo ReadLine (che credo sia il punto più debole di questa implementazione) con StreamReader e la differenza è quasi un ordine di grandezza. Ho ottenuto 22 MB / s usando la mia classe StreamReaderEx, ma quasi 9 volte di più usando direttamente StreamReader (sul mio laptop dotato di SSD). Sebbene possa essere interessante, non so come fare un test di lettura adeguato; magari usando 2 file identici, ognuno più grande del buffer del disco, e leggendoli alternativamente ..? Almeno il mio semplice test produce risultati coerenti quando lo eseguo più volte e indipendentemente da quale classe legge prima il file di test.

Il simbolo NewLine viene impostato per impostazione predefinita su Environment.NewLine ma può essere impostato su qualsiasi stringa di lunghezza 1 o 2. Il lettore considera solo questo simbolo come una nuova riga, che può essere un inconveniente. Almeno so che Visual Studio mi ha spinto un bel numero di volte che un file che apro " ha nuove linee incoerenti " ;.

Nota che non ho incluso la classe Guard; questa è una semplice classe di utilità e dovrebbe essere ovvia al contesto come sostituirla. Puoi anche rimuoverlo, ma perderai qualche controllo degli argomenti e quindi il codice risultante sarebbe più lontano da "corretto". Ad esempio, Guard.NotNull (s, " s ") verifica semplicemente che s non sia nullo, generando ArgumentNullException (con il nome dell'argomento " s ", quindi il secondo parametro) dovrebbe essere il caso.

Basta chiacchiere, ecco il codice:

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 
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top