Como saber a posição (número da linha) de um StreamReader em um arquivo de texto?

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

  •  06-07-2019
  •  | 
  •  

Pergunta

um exemplo (que pode não ser a vida real, mas para fazer o meu ponto):

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

}

GetLinePosition aqui é um método de extensão imaginária de StreamReader. Isso é possível?

É claro que eu poderia manter contar-me, mas isso não é a questão.

Foi útil?

Solução

É extremamente fácil para fornecer um invólucro linha de contagem para qualquer 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++;
        }
    }
}

As desvantagens (por uma questão de brevidade):

  1. não verifica argumento construtor para nula
  2. não reconhece caminhos alternativos para encerrar as linhas. Serão inconsistentes com o comportamento ReadLine () quando leitura de arquivos separados por \ r cru ou \ n.
  3. Não substituir "bloquear" métodos -Level como Read (char [], int, int), ReadBlock, ReadLine, ReadToEnd. implementação TextReader funciona corretamente, uma vez que as rotas de tudo para Read (); no entanto, um melhor desempenho poderia ser alcançado por
    • substituir esses métodos via roteamento de chamadas para _inner. em vez de base.
    • passando os caracteres lidos ao AdvancePosition. Veja a implementação ReadBlock amostra:

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

Outras dicas

eu cam através deste post, enquanto procura uma solução para um problema semelhante onde eu precisava para buscar o StreamReader para determinadas linhas. I acabou criando dois métodos de extensão para obter e definir a posição em um StreamReader. Na verdade, não fornecem uma linha de contagem de número, mas, na prática, eu só pegar a posição antes de cada ReadLine () e se a linha é de interesse, então eu mantenho a posição de início para definir mais tarde para voltar para a linha como assim :

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

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

Assert.AreEqual(line1, line2);

e a parte mais 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);
    }
}

Isso funciona muito bem para mim e, dependendo da sua tolerância para o uso de reflexão Ele acha que é uma solução bastante simples.

Advertências:

  1. Embora eu tenha feito alguns testes simples usando várias opções System.Text.Encoding, praticamente todos os dados que consomem com este são arquivos de texto simples (ASCII).
  2. eu só usar o método StreamReader.ReadLine () e enquanto uma breve revisão da fonte de StreamReader parece indicar este trabalho vai ainda ao usar outros métodos de leitura, eu realmente não tenho testado esse cenário.

Não, não é realmente possível. O conceito de uma "linha" é baseada nos dados reais que já foram lidos, e não apenas a posição. Por exemplo, se você fosse para Seek () o leitor a uma posição arbitrária, não é actuall vai ler esses dados, para que ele não seria capaz de determinar o número da linha.

A única maneira de fazer isso é manter o controle de si mesmo.

Não.

Considere que é possível buscar a qualquer poisition usando o objeto de fluxo subjacente (que poderia ser em qualquer ponto em qualquer linha). Agora, considere o que faria para qualquer contagem mantida pelo StreamReader.

Caso o movimento StreamReader e descobrir qual linha é agora em diante? Deve apenas manter um número de linhas lidas, independentemente da posição dentro do arquivo?

Há mais perguntas do que apenas estes que faria este um pesadelo para implementar, imho.

Aqui é um cara que implementou um StreamReader com ReadLine () que posição do arquivo registros.

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

Eu acho que se deve herdar de StreamReader, e depois adicionar o método extra para a classe especial, juntamente com algumas propriedades (_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;
 }

Pense há um pequeno bug no código como o comprimento da corda é usada para posição do arquivo calcular em vez de usar os bytes reais ler (falta de suporte para UTF8 e UTF16 arquivos codificados).

Eu vim aqui à procura de algo simples. Se você está apenas usando ReadLine () e não se preocupam com usando Seek () ou qualquer coisa, basta fazer uma subclasse simples 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();
    }
}

e então você faz isso da maneira normal, digamos, de um FileInfo objeto de arquivo chamado

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

e você acabou de ler a propriedade reader.LineNumber.

Os pontos já realizados em relação ao BaseStream são válidos e importantes. No entanto, existem situações em que você deseja ler um texto e saber onde no texto que você é. Ele ainda pode ser útil para escrever isso como uma classe para tornar mais fácil a reutilização.

Eu tentei escrever essa classe um agora. Parece que funciona corretamente, mas é bastante lento. Ele deve estar bem quando o desempenho não é crucial (não é que lento, veja abaixo).

Eu uso a mesma lógica para rastrear posição no texto, independentemente se você ler um caractere de cada vez, um buffer de cada vez, ou uma linha de cada vez. Enquanto eu tenho certeza que isso pode ser feito para executar em vez melhor, abandonando isso, ele fez muito mais fácil de implementar ... e, espero, a seguir o código.

Eu fiz uma comparação de desempenho muito básico do método ReadLine (que eu acredito que é o ponto mais fraco desta implementação) para StreamReader, ea diferença é quase uma ordem de magnitude. Eu tenho 22 MB / s usando minha classe StreamReaderEx, mas quase 9 vezes mais usando StreamReader diretamente (no meu laptop equipado SSD). Enquanto isso poderia ser interessante, eu não sei como fazer um teste de leitura adequada; talvez usando 2 arquivos idênticos, cada um maior do que o buffer de disco, e lê-los alternadamente ..? Pelo menos o meu teste simples produz resultados consistentes quando eu executá-lo várias vezes, e independentemente da classe lê o arquivo de teste primeiro.

Os padrões símbolo NewLine para Environment.NewLine mas pode ser configurado para qualquer cadeia de comprimento 1 ou 2. O leitor considera apenas este símbolo como uma nova linha, que pode ser uma desvantagem. Pelo menos eu sei Visual Studio levou-me um bom número de vezes que um arquivo abro "tem novas linhas inconsistentes".

Por favor note que eu não ter incluído a classe Guarda; esta é uma classe utilitário simples e deve ser obvoius do contexto como substituí-lo. Você pode até mesmo removê-lo, mas você perderia alguns testes argumento e, portanto, o código resultante seria mais longe "correta". Por exemplo, Guard.NotNull (s, "s") simplesmente verifica que é s não é nulo, jogando um ArgumentNullException (com nome argumento "s", daí o segundo parâmetro) se for o caso.

babble suficiente, aqui está o código:

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 
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top