Как узнать положение (linenumber) streamа-streamа в текстовом файле?

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

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

GetLinePosition – это воображаемый метод расширения streamreader. Это возможно?

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

Очень просто предоставить оболочку для подсчета строк для любого 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. Не проверяет аргумент конструктора для null
  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; } 

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

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

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

Нет.

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

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

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

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

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

Я предполагаю, что нужно наследовать от StreamReader, а затем добавить дополнительный метод к специальному classу вместе с некоторыми свойствами (_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. // ///  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).

Я приехал сюда искать что-то простое. Если вы просто используете ReadLine () и не заботитесь об использовании Seek () или чего-либо еще, просто создайте простой подclass 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 .

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

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

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

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

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

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

Достаточно лепет, вот код:

 Открытый class StreamReaderEx: StreamReader
 {
     // Символы NewLine (магическое значение -1: «не используется»).
     int newLine1, newLine2;

     // Последний символ был первым символом символа NewLine. И мы используем двухсимвольный символ.
     bool insideNewLine;

     // StringBuilder используется для реализации ReadLine.
     StringBuilder lineBuilder = new StringBuilder ();


     public StreamReaderEx (строка, строка newLine = "\ r \ n"): base (путь)
     {
         инициализации (строки);
     }


     public StreamReaderEx (stream s, строка newLine = "\ r \ n"): base (s)
     {
         инициализации (строки);
     }


     публичная строка NewLine
     {
         get {return "" + (char) newLine1 + (char) newLine2;  }
         частный набор
         {
             Guard.NotNull (значение, «значение»);
             Guard.Range (значение.Length, 1, 2, "Поддерживается только 1 - 2 символа символов NewLine.");

             newLine1 = значение [0];
             newLine2 = (значение.Length == 2? value [1]: -1);
         }
     }


     public int LineNumber {get;  частный набор;  }
     public int LinePosition {get;  частный набор;  }


     public override int Read ()
     {
         int next = base.Read ();
         trackTextPosition (далее);
         return next;
     }


     public override int Read (char [] buffer, int index, int count)
     {
         int n = base.Read (буфер, индекс, счетчик);
         для (int i = 0; i 
Давайте будем гением компьютера.