2009-05-06 3 views
8

пример (который не может быть в реальной жизни, но чтобы сделать мою точку):Как узнать положение (linenumber) потока-потока в текстовом файле?

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. Возможно ли это?

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

ответ

7

Это очень легко, чтобы обеспечить прямой подсчет обертку для любого 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. Не переопределяет методы «block», такие как 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; 
} 
+0

Это также не учитывает использование 'Seek'. –

10

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

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

+1

+1 вы получили в передо мной, теперь, если я мог бы просто печатать быстрее :) –

4

Номер

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

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

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

+2

+1 причина набирает репутацию не должна зависеть от скорости набора текста ;-) – Peter

+0

С другой стороны, мы должны получить РЭП повторяя то, что уже было сказано? (НЕ сказал, что этот плакат сделал, но в целом это было бы возможно!) –

+0

@ The Dag: Не так много повторяется, как сказано в то же время. , , Сглазить! (BTW, ди-я хочу купить даг?) –

3

Вот парень, который реализовал 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; 
} 

Think есть небольшая ошибка в коде, поскольку длина строки используется для вычисления позиции файла вместо использования фактического чтения байтов (Отсутствует поддержка кодированных файлов UTF8 и UTF16).

1

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

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

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

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

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

Обратите внимание, что я не включил класс Guard; это простой класс утилиты, и он должен быть obvoius из контекста, как его заменить. Вы даже можете удалить его, но вы потеряете некоторые проверки аргументов, и, следовательно, полученный код будет дальше от «правильного». Например, Guard.NotNull (s, «s») просто проверяет, что s не является нулевым, выбрасывая 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 
+0

О, отлично, мой код был просто отключен посередине. Я воспользуюсь возможностью, чтобы узнать, интересуется ли кто-нибудь; если да, дайте мне знать, и я отправлю оставшуюся часть. –

3

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

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

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

+0

Хороший ответ, но вы должны уточнить, что это будет работать, только если 'ReadLine' - это метод _only_, который вы вызываете. –

13

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

Работает с 'System.Text.Encoding.UTF8' – CrazyIvan1974