С# StreamReader, "ReadLine" для пользовательских разделителей - программирование
Подтвердить что ты не робот

С# StreamReader, "ReadLine" для пользовательских разделителей

Каков наилучший способ иметь функциональность метода StreamReader.ReadLine(), но с пользовательскими (String) разделителями?

Я хотел бы сделать что-то вроде:

String text;
while((text = myStreamReader.ReadUntil("my_delim")) != null)
{
   Console.WriteLine(text);
}

Я попытался сделать свой собственный с помощью Peek() и StringBuilder, но он слишком неэффективен. Я ищу предложения или, возможно, решение с открытым исходным кодом.

Спасибо.

Edit

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

4b9b3361

Ответ 1

Я решил, что опубликую свое собственное решение. Кажется, что он работает очень хорошо, и код относительно прост. Не стесняйтесь комментировать.

public static String ReadUntil(this StreamReader sr, String delim)
{
    StringBuilder sb = new StringBuilder();
    bool found = false;

    while (!found && !sr.EndOfStream)
    {
       for (int i = 0; i < delim.Length; i++)
       {
           Char c = (char)sr.Read();
           sb.Append(c);

           if (c != delim[i])
               break;

           if (i == delim.Length - 1)
           {
               sb.Remove(sb.Length - delim.Length, delim.Length);
               found = true;
           }
        }
     }

     return sb.ToString();
}

Ответ 2

Этот код должен работать для любого разделителя строк.

public static IEnumerable<string> ReadChunks(this TextReader reader, string chunkSep)
{
    var sb = new StringBuilder();

    var sepbuffer = new Queue<char>(chunkSep.Length);
    var sepArray = chunkSep.ToCharArray();

    while (reader.Peek() >= 0)
    {
        var nextChar = (char)reader.Read();
        if (nextChar == chunkSep[sepbuffer.Count])
        {
            sepbuffer.Enqueue(nextChar);
            if (sepbuffer.Count == chunkSep.Length)
            {
                yield return sb.ToString();
                sb.Length = 0;
                sepbuffer.Clear();
            }
        }
        else
        {
            sepbuffer.Enqueue(nextChar);
            while (sepbuffer.Count > 0)
            {
                sb.Append(sepbuffer.Dequeue());
                if (sepbuffer.SequenceEqual(chunkSep.Take(sepbuffer.Count)))
                    break;
            }
        }
    }
    yield return sb.ToString() + new string(sepbuffer.ToArray());
}

Отказ от ответственности:

Я сделал небольшое тестирование на этом и на самом деле медленнее, чем метод ReadLine, но я подозреваю, что это связано с вызовом enqueue/dequeue/sequenceEqual, который можно избежать в методе ReadLine (поскольку разделитель всегда \r\n).

Опять же, я сделал несколько тестов, и он должен работать, но не воспринимайте его как идеальный и не стесняйтесь исправить его.;)

Ответ 3

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

    public static IEnumerable<string> Split(this Stream stream, string delimiter, StringSplitOptions options)
    {
        var buffer = new char[_bufffer_len];
        StringBuilder output = new StringBuilder();
        int read;
        using (var reader = new StreamReader(stream))
        {
            do
            {
                read = reader.ReadBlock(buffer, 0, buffer.Length);
                output.Append(buffer, 0, read);

                var text = output.ToString();
                int id = 0, total = 0;
                while ((id = text.IndexOf(delimiter, id)) >= 0)
                {
                    var line = text.Substring(total, id - total);
                    id += delimiter.Length;
                    if (options != StringSplitOptions.RemoveEmptyEntries || line != string.Empty)
                        yield return line;
                    total = id;
                }
                output.Remove(0, total);
            }
            while (read == buffer.Length);
        }

        if (options != StringSplitOptions.RemoveEmptyEntries || output.Length > 0)
            yield return output.ToString();
    }

... и вы можете просто переключиться на разделители char, если необходимо, просто замените

while ((id = text.IndexOf(delimiter, id)) >= 0)

... с

while ((id = text.IndexOfAny(delimiters, id)) >= 0)

id++ вместо id+= и подпись this Stream stream, StringSplitOptions options, params char[] delimiters)

... также удаляет пустой и т.д.
надеюсь, что это поможет

Ответ 4

    public static String ReadUntil(this StreamReader streamReader, String delimiter)
    {
        StringBuilder stringBuilder = new StringBuilder();

        while (!streamReader.EndOfStream)
        {
            stringBuilder.Append(value: (Char) streamReader.Read());

            if (stringBuilder.ToString().EndsWith(value: delimiter))
            {
                stringBuilder.Remove(stringBuilder.Length - delimiter.Length, delimiter.Length);
                break;
            }
        }

        return stringBuilder.ToString();
    }