Comment connaître la position (numéro de ligne) d'un lecteur de flux dans un fichier texte?

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

  •  06-07-2019
  •  | 
  •  

Question

un exemple (cela pourrait ne pas être la vraie vie, mais pour bien faire comprendre mon point):

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

}

GetLinePosition voici une méthode d'extension imaginaire de streamreader. Est-ce possible?

Bien sûr, je pourrais continuer à me compter, mais ce n'est pas la question.

Était-ce utile?

La solution

Il est extrêmement facile de fournir un wrapper de comptage de lignes pour tout 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++;
        }
    }
}

Inconvénients (par souci de brièveté):

  1. Ne vérifie pas l'argument du constructeur pour null
  2. Ne reconnaît pas les autres moyens de terminer les lignes. Sera incompatible avec le comportement de ReadLine () lors de la lecture de fichiers séparés par raw \ r ou \ n.
  3. Ne remplace pas les méthodes de niveau "blocage" telles que Read (char [], int, int), ReadBlock, ReadLine, ReadToEnd. L'implémentation de TextReader fonctionne correctement car elle achemine tout le reste vers Read (); cependant, une meilleure performance pourrait être obtenue en
    • écrasant ces méthodes via les appels de routage vers _inner. au lieu de base.
    • transmettre les caractères lus à AdvancePosition. Voir l'exemple d'implémentation 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;
}

Autres conseils

J'ai parcouru ce post en cherchant une solution à un problème similaire dans lequel je devais rechercher le StreamReader sur des lignes particulières. J'ai fini par créer deux méthodes d'extension pour obtenir et définir la position sur un StreamReader. Il ne fournit pas réellement un compte de nombre de lignes, mais dans la pratique, je saisis simplement la position avant chaque ReadLine () et si la ligne est intéressante, je garde la position de départ pour la configuration ultérieure afin de revenir à la ligne comme si :

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

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

Assert.AreEqual(line1, line2);

et la partie 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);
    }
}

Cela fonctionne assez bien pour moi et dépend de votre tolérance à l’utilisation de la réflexion. C’est une solution assez simple.

Mises en garde:

  1. Bien que j'aie fait quelques tests simples en utilisant diverses options System.Text.Encoding, la quasi-totalité des données que je consomme avec cela sont de simples fichiers texte (ASCII).
  2. Je n'utilise que la méthode StreamReader.ReadLine () et, bien qu'un bref examen du code source de StreamReader semble indiquer que cela fonctionnera toujours avec les autres méthodes de lecture, je n'ai pas vraiment testé ce scénario.

Non, pas vraiment possible. Le concept de " numéro de ligne " est basé sur les données réelles qui ont déjà été lues, pas seulement la position. Par exemple, si vous cherchiez () le lecteur dans une position arbitraire, il ne lirait pas réellement ces données, il ne pourrait donc pas déterminer le numéro de ligne.

La seule façon de le faire est de garder une trace de vous-même.

Non.

Considérez qu’il est possible de rechercher n’importe quelle activité en utilisant l’objet flux sous-jacent (qui peut se trouver à n’importe quel point de n’importe quelle ligne). Maintenant, réfléchissez à ce que cela ferait pour tout décompte conservé par StreamReader.

Le StreamReader doit-il aller et déterminer quelle ligne il est maintenant? Devrait-il simplement garder un certain nombre de lignes lues, quelle que soit la position dans le fichier?

Il y a plus de questions que celles-ci qui en feraient un cauchemar à mettre en œuvre, à mon humble avis.

Voici un gars qui a implémenté une méthode StreamReader avec ReadLine () qui enregistre la position du fichier.

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

Je suppose qu'il faut hériter de StreamReader, puis ajouter la méthode supplémentaire à la classe spéciale ainsi que certaines propriétés (_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;
 }

Pensez qu’il existe un bogue mineur dans le code car la longueur de la chaîne est utilisée pour calculer la position du fichier au lieu d’utiliser les octets lus (absence de prise en charge des fichiers codés UTF8 et UTF16).

Je suis venu ici pour chercher quelque chose de simple. Si vous utilisez seulement ReadLine () et que vous n’aurez pas à utiliser Seek () ou quoi que ce soit, créez simplement une sous-classe de 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();
    }
}

et ensuite vous faites comme d'habitude, disons à partir d'un objet FileInfo nommé fichier

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

et vous venez de lire la propriété reader.LineNumber .

Les remarques déjà faites concernant BaseStream sont valables et importantes. Cependant, il existe des situations dans lesquelles vous souhaitez lire un texte et savoir où vous vous trouvez. Il peut toujours être utile d’écrire cela en tant que classe pour faciliter sa réutilisation.

J'ai essayé d'écrire une telle classe maintenant. Cela semble fonctionner correctement, mais c'est plutôt lent. Cela devrait aller si les performances ne sont pas cruciales (ce n'est pas que , voir ci-dessous).

J'utilise la même logique pour suivre la position dans le texte, que vous lisiez un caractère à la fois, un tampon à la fois ou une ligne à la fois. Même si je suis sûr que cela peut être fait pour que cela fonctionne plutôt mieux en abandonnant cela, cela a été beaucoup plus facile à mettre en œuvre ... et, j'espère, à suivre le code.

J'ai comparé de manière très simple les performances de la méthode ReadLine (ce qui, à mon avis, est le point le plus faible de cette implémentation) avec StreamReader, et la différence est presque d'un ordre de grandeur. J'ai eu 22 Mo / s en utilisant ma classe StreamReaderEx, mais près de 9 fois plus en utilisant StreamReader directement (sur mon ordinateur portable équipé d'un SSD). Bien que cela puisse être intéressant, je ne sais pas comment faire un test de lecture approprié; peut-être en utilisant 2 fichiers identiques, chacun plus volumineux que le tampon de disque, et en les lisant en alternance ..? Au moins, mon test simple produit des résultats cohérents lorsque je l'exécute plusieurs fois et quelle que soit la classe qui lit le fichier de test en premier.

Le symbole NewLine est défini par défaut sur Environment.NewLine mais peut être défini sur toute chaîne de longueur 1 ou 2. Le lecteur considère uniquement ce symbole comme une nouvelle ligne, ce qui peut être un inconvénient. Au moins, je sais que Visual Studio m’a souvent indiqué qu’un fichier que j’ouvrais "contient des nouvelles lignes incohérentes".

Veuillez noter que je n’ai pas inclus la classe de garde; c'est une classe d'utilitaire simple et il devrait être obvoius du contexte comment la remplacer. Vous pouvez même le supprimer, mais vous perdriez certains arguments et le code obtenu serait donc plus éloigné de "correct". Par exemple, Guard.NotNull (s, "s") vérifie simplement que s n'est pas nul, en lançant une exception ArgumentNullException (avec le nom d'argument "s", d'où le deuxième paramètre), le cas échéant.

Assez bavardé, voici le 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 
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top