Как узнать положение (номер белья) потокового ридера в текстовом файле? - PullRequest
8 голосов
/ 06 мая 2009

пример (это может быть не реальная жизнь, но, чтобы подчеркнуть):

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

}

GetLinePosition вот мнимый метод расширения потокового ридера. Возможно ли это?

Конечно, я мог бы продолжать считать себя, но это не вопрос.

Ответы [ 7 ]

21 голосов
/ 10 апреля 2014

Я просматривал этот пост, ища решение аналогичной проблемы, где мне нужно было искать StreamReader для определенных строк. Я закончил тем, что создал два метода расширения, чтобы получить и установить позицию в StreamReader. На самом деле он не обеспечивает счетчик номеров строк, но на практике я просто беру позицию перед каждой строкой 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", 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);
    }
}

Для меня это довольно неплохо, и в зависимости от вашей терпимости к использованию отражения. Думаю, это довольно простое решение.

Предостережения:

  1. Несмотря на то, что я провел несколько простых испытаний с использованием различных опций System.Text.Encoding, почти все данные, которые я использую для этого, представляют собой простые текстовые файлы (ASCII).
  2. Я только когда-либо использую метод StreamReader.ReadLine (), и хотя краткий обзор источника для StreamReader, кажется, указывает, что это будет работать при использовании других методов чтения, я на самом деле не тестировал этот сценарий.
11 голосов
/ 06 мая 2009

Нет, не совсем возможно. Концепция «номера строки» основана на фактических данных, которые уже были прочитаны, а не только на позиции. Например, если вы хотите, чтобы Seek () считыватель находился в произвольном положении, эти данные не будут считываться, поэтому он не сможет определить номер строки.

Единственный способ сделать это - следить за этим самостоятельно.

7 голосов
/ 16 января 2012

Для любого 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. Не проверяет аргумент конструктора для нуля
  2. Не распознает альтернативные способы завершения строк. Будет несовместимо с поведением ReadLine () при чтении файлов, разделенных raw \ r или \ n.
  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;
}
5 голосов
/ 06 мая 2009

номер

Учтите, что можно искать в любой записи, используя базовый объект потока (который может находиться в любой точке любой строки). Теперь рассмотрим, что это будет делать с любым счетчиком, хранящимся в StreamReader.

Должен ли StreamReader пойти и выяснить, на какой линии он сейчас находится? Должен ли он просто читать несколько строк независимо от позиции в файле?

Есть больше вопросов, чем просто те, которые сделают это кошмаром для реализации, имхо.

3 голосов
/ 06 октября 2010

Вот парень, который реализовал StreamReader с методом ReadLine (), который регистрирует положение файла.

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).

2 голосов
/ 19 февраля 2014

Я пришел сюда в поисках чего-то простого. Если вы просто используете 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();
    }
}

и затем вы делаете это обычным способом, скажем, из объекта FileInfo с именем file

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

и вы только что прочитали свойство reader.LineNumber.

1 голос
/ 23 января 2012

Баллы, уже сделанные в отношении BaseStream, являются действительными и важными. Тем не менее, существуют ситуации, в которых вы хотите прочитать текст и знать, где в тексте вы находитесь. Все еще может быть полезно записать это как класс, чтобы облегчить его повторное использование.

Я пытался написать такой класс сейчас. Кажется, работает правильно, но довольно медленно. Это должно быть хорошо, когда производительность не имеет решающего значения (это не , что медленно, см. Ниже).

Я использую ту же логику для отслеживания положения в тексте независимо от того, читаете ли вы символ за раз, один буфер за раз или одну строку за раз. Хотя я уверен, что если отказаться от этого, то это можно будет сделать для более эффективной работы, это значительно упростило реализацию ... и, я надеюсь, следовать коду.

Я сделал очень простое сравнение производительности метода ReadLine (который я считаю самым слабым местом этой реализации) с StreamReader, и разница почти на порядок. Я получил 22 МБ / с, используя мой класс StreamReaderEx, но почти в 9 раз больше, если использовать StreamReader напрямую (на моем ноутбуке с SSD). Хотя это может быть интересно, я не знаю, как правильно провести тест на чтение; может быть, использовать 2 одинаковых файла, каждый больше, чем размер буфера диска, и читать их попеременно ... По крайней мере, мой простой тест дает последовательные результаты, когда я запускаю его несколько раз, и независимо от того, какой класс читает файл теста первым.

Символом NewLine по умолчанию является Environment.NewLine, но может быть задана любая строка длиной 1 или 2. Читатель рассматривает только этот символ как символ новой строки, что может быть недостатком. По крайней мере, я знаю, что Visual Studio довольно много раз подсказывала мне, что открываемый мной файл "содержит несовместимые символы новой строки".

Обратите внимание, что я не включил класс гвардии; это простой служебный класс, и из контекста должно быть понятно, как его заменить. Вы можете даже удалить его, но вы потеряете некоторую проверку аргументов, и, следовательно, полученный код будет дальше от «правильного». Например, Guard.NotNull (s, "s") просто проверяет, что s не равно NULL, и выдает исключение ArgumentNullException (с именем аргумента "s", следовательно, вторым параметром), если это так.

Хватит болтать, вот код:


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 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...