2008-11-21 2 views
1

Пожалуйста, не стесняйтесь исправить меня, если я ошибаюсь в любой момент ...Каков наиболее эффективный способ реализации ReadLine() для бинарного потока?

Я пытаюсь прочитать файл CSV (значения, разделенные запятыми), используя классы ввода/вывода .NET. Теперь проблема заключается в том, что этот CSV-файл может содержать некоторые поля с мягкими возвратами каретки (т. Е. Одиночные \ r или \ n маркеры, а не стандартные \ r \ n, используемые в текстовых файлах для завершения строки) в некоторых полях и в стандартном текстовом режиме Класс I/O StreamReader не соблюдает стандартное соглашение и рассматривает возврат мягкой каретки, поскольку жесткие каретки возвращают, что ставит под угрозу целостность файла CSV.

Теперь использование класса BinaryReader является единственной опцией, но BinaryReader не имеет функции ReadLine(), поэтому необходимо реализовать ReadLine() самостоятельно.

Мой текущий подход считывает один символ из потока за раз и заполняет StringBuilder до получения \ r \ n (игнорируя все остальные символы, включая одиночные \ r или \ n), а затем возвращает строковое представление StringBuilder (используя ToString()).

Но мне интересно: это самый эффективный способ реализации функции ReadLine()? Пожалуйста, просветите меня.

+1

Когда вы говорите «стандартное соглашение», вы должны понимать, что это не особенно стандартно. В Unix «\ n» - это нормальный ограничитель строк, сам по себе. – 2008-11-21 13:10:52

+0

У вас действительно есть проблема с перфомансом или это типичный случай преждевременной оптимизации? ;). Я не видел, что вы упоминаете первичную проблему. – 2008-11-21 13:10:58

+0

@ Jon - Да, я знаю, спасибо. Я имел в виду стандарт на windows/dos. – 2008-11-21 13:39:13

ответ

6

Возможно, это так. В терминах порядка он проходит через каждый символ только один раз, поэтому это будет O (n) (где n - длина потока), так что это не проблема. Чтобы прочитать один символ, BinaryReader - ваш лучший выбор.

Что бы я сделать, это сделать класс

public class LineReader : IDisposable 
{ 
    private Stream stream; 
    private BinaryReader reader; 

    public LineReader(Stream stream) { reader = new BinaryReader(stream); } 

    public string ReadLine() 
    { 
     StringBuilder result = new StringBuilder(); 
     char lastChar = reader.ReadChar(); 
     // an EndOfStreamException here would propogate to the caller 

     try 
     { 
      char newChar = reader.ReadChar(); 
      if (lastChar == '\r' && newChar == '\n') 
       return result.ToString(); 

      result.Append(lastChar); 
      lastChar = newChar; 
     } 
     catch (EndOfStreamException) 
     { 
      result.Append(lastChar); 
      return result.ToString(); 
     } 
    } 

    public void Dispose() 
    { 
     reader.Close(); 
    } 
} 

Или что-то подобное.

(ВНИМАНИЕ:. Код не был проверен и предоставляется как есть, без каких-либо гарантий, явных или подразумеваемых Если эта программа окажется дефектным или уничтожить планету, вы берете на себя стоимость необходимого обслуживания, ремонта или коррекция.)

0

Как насчет простой предварительной обработки файла?

Замените мягкую каретку с чем-то уникальным.

Для записи CSV-файлов с линией в данных, это плохой дизайн.

+0

Я думаю, что сингулярные линии в CSV-данных могут быть плохой идеей, если вы находитесь на windows/dos. Этот дизайн существует уже довольно давно. Вот как это делается в Excel, например, если у вас есть разрыв строки в ячейке. (Нажмите Alt + Enter, чтобы ввести разрыв строки в ячейке) – 2008-11-21 12:55:03

0

Вы можете прочитать более крупный фрагмент за раз, unencode его в строку с помощью Encoder.GetString, а затем разбить на строки с помощью string.Split ("\ r \ n") или даже выбрать заголовок строки, используя string.Substring (0, string.IndexOf ("\ r \ n")) и остальное для обработки следующей строки. Не забудьте добавить следующую операцию чтения в свою последнюю строку из предыдущего чтения.

+0

Базовый поток уже буферизует чтение более крупными кусками, не так ли? – configurator 2008-11-21 12:49:59

0

Ваш подход звучит отлично. Одним из способов повышения эффективности вашего метода может быть сохранение каждой строки при ее создании в обычной строке (т. Е. Не в StringBuilder), а затем добавление всей строки в ваш StringBuilder. См. this article для дальнейшего объяснения. StringBuilder не является лучшим выбором здесь.

Это, вероятно, будет иметь значение мало.

1

Для этого вам может потребоваться использовать соединение ODBC/OleDB. Если вы укажете источник данных соединения oledb в каталог, содержащий файлы csv, вы можете запросить его, как если бы каждый CSV был таблицей.
проверить http://www.connectionstrings.com/?carrier=textfile>connectionstrings.com для правильной строки подключения

0

Вот более быстрая альтернатива с поддержкой кодирования. Он расширяет BinaryReader, поэтому вы можете использовать его как для чтения, так и для двоичных фрагментов, а также для StreamReader, например ReadLine, непосредственно для двоичного потока.

public class LineReader : BinaryReader 
{ 
    private Encoding _encoding; 
    private Decoder _decoder; 

    const int bufferSize = 1024; 
    private char[] _LineBuffer = new char[bufferSize]; 

    public LineReader(Stream stream, int bufferSize, Encoding encoding) 
     : base(stream, encoding) 
    { 
     this._encoding = encoding; 
     this._decoder = encoding.GetDecoder(); 
    } 

    public string ReadLine() 
    { 
     int pos = 0; 

     char[] buf = new char[2]; 

     StringBuilder stringBuffer = null; 
     bool lineEndFound = false; 

     while(base.Read(buf, 0, 2) > 0) 
     { 
      if (buf[1] == '\r') 
      { 
       // grab buf[0] 
       this._LineBuffer[pos++] = buf[0]; 
       // get the '\n' 
       char ch = base.ReadChar(); 
       Debug.Assert(ch == '\n'); 

       lineEndFound = true; 
      } 
      else if (buf[0] == '\r') 
      { 
       lineEndFound = true; 
      }      
      else 
      { 
       this._LineBuffer[pos] = buf[0]; 
       this._LineBuffer[pos+1] = buf[1]; 
       pos += 2; 

       if (pos >= bufferSize) 
       { 
        stringBuffer = new StringBuilder(bufferSize + 80); 
        stringBuffer.Append(this._LineBuffer, 0, bufferSize); 
        pos = 0; 
       } 
      } 

      if (lineEndFound) 
      { 
       if (stringBuffer == null) 
       { 
        if (pos > 0) 
         return new string(this._LineBuffer, 0, pos); 
        else 
         return string.Empty; 
       } 
       else 
       { 
        if (pos > 0) 
         stringBuffer.Append(this._LineBuffer, 0, pos); 
        return stringBuffer.ToString(); 
       } 
      } 
     } 

     if (stringBuffer != null) 
     { 
      if (pos > 0) 
       stringBuffer.Append(this._LineBuffer, 0, pos); 
      return stringBuffer.ToString(); 
     } 
     else 
     { 
      if (pos > 0) 
       return new string(this._LineBuffer, 0, pos); 
      else 
       return null; 
     } 
    } 

} 
1

Здесь метод расширения для класса BinaryReader:

using System.IO; 
using System.Text; 

public static class BinaryReaderExtension 
{ 
    public static string ReadLine(this BinaryReader reader) 
    { 
     if (reader.IsEndOfStream()) 
      return null; 

     StringBuilder result = new StringBuilder(); 
     char character; 
     while(!reader.IsEndOfStream() && (character = reader.ReadChar()) != '\n') 
      if (character != '\r' && character != '\n') 
       result.Append(character); 

     return result.ToString(); 
    } 

    public static bool IsEndOfStream(this BinaryReader reader) 
    { 
     return reader.BaseStream.Position == reader.BaseStream.Length; 
    } 
} 

я не испытывал в любых условиях, но этот код работал для меня.

Смежные вопросы