Чтение строки в C# без усечения символа разделителя строк

У меня есть строка, которую я хочу читать построчно, но мне также нужно иметь символ разделителя строк, который, к сожалению, обрезает StringReader.ReadLine (в отличие от ruby, где он хранится). Какой самый быстрый и надежный способ сделать это?

Альтернативы, о которых я думал:

  • Чтение ввода за символом и проверка на разделитель строк каждый раз
  • Использование RegExp.Split с положительным взглядом

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

РЕДАКТИРОВАТЬ: вот моя текущая реализация. Конец файла обозначается возвращением пустой строки.

StringBuilder line = new StringBuilder();
int r = _input.Read();
while (r >= 0)
{
  char c = Convert.ToChar(r);
  line.Append(c);
  if (c == '\n') break;
  if (c == '\r')
  {
    int peek = _input.Peek();
    if (peek == -1) break;
    if (Convert.ToChar(peek) != '\n') break;
  }
  r = _input.Read();
}
return line.ToString();

4 ответа

Решение

Вас беспокоит несоответствие между файлами (т.е. исходящими из Unix/Mac или Windows) или внутри файлов?

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

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


Есть другой способ сделать это, который был бы более эффективным, если бы ваш источник данных был потоком. К сожалению, это не так, как указано в вашем комментарии, поэтому вам придется сначала создать его; тем не менее, я все равно включу решение, оно может дать вам некоторое вдохновение:

public IEnumerable<int> GetLineStartIndices(string s)
{
    yield return 0;
    byte[] chars = Encoding.UTF8.GetBytes(s);
    using (MemoryStream stream = new MemoryStream(chars))
    {
        using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
        {
            while (reader.ReadLine() != null)
            {
                yield return stream.Position;
            }
        }
    }
}

Это вернет вам начальную позицию каждой новой строки. Очевидно, вы можете настроить это, чтобы сделать все, что вам нужно, то есть сделать что-то еще с реальными строками, которые вы прочитали.

Просто обратите внимание, что для создания байтового массива необходимо сделать копию строки, поэтому он действительно не подходит для очень больших строк. Это немного лучше, чем подход char-by-char, но менее подвержен ошибкам, поэтому, возможно, стоит подумать, если длина строк не в мегабайтах.

File.ReadAllText получит вам все содержимое файла. Ага. Все. Так что лучше проверяйте размер файла перед его использованием.

РЕДАКТИРОВАТЬ:

прочитайте все это, затем создайте перечислитель, который выдает построчно.

foreach(string line in Read("some.file"))
{ ... }


private IEnumerator Read(string file)
{
  string buffer = File.ReadAllText()
  for (int index=0;index<buffer.length;index++)
   {
      string line = ... logic to build a "line" here
      yield return line;
   }

   yield break;

}
        FileStream fs = new FileStream("E:\\hh.txt", FileMode.Open, FileAccess.Read);
        BinaryReader read = new BinaryReader(fs);
        byte[] ch = read.ReadBytes((int)fs.Length);
        byte[] che=new byte[(int)fs.Length];
        int size = (int)fs.Length,j=0;
        for ( int i =0; i <= (size-1); i++)
        {
            if (ch[i] != '|')
            {
                che[j] = ch[i];
                j++;
            }

        }
        richTextBox1.Text = Encoding.ASCII.GetString(che);
        read.Close();
        fs.Close();

Если вы заботитесь только о позиции: ReadLine() перемещает вас к следующей строке. Если вы храните .Position потока внизу вы можете сравнить его с .Position после следующего ReadLine(), Это длина строки, которую вы только что прочитали, плюс разделитель. Длина разделителя currentPosition - previousPosition - line.Length,

Таким образом, вы можете легко узнать, был ли это 1 или 2 байта (не зная деталей, но вы сказали, что все равно заботитесь только о позициях).

Другие вопросы по тегам