С# StreamReader, "ReadLine" для пользовательских разделителей
Каков наилучший способ иметь функциональность метода StreamReader.ReadLine()
, но с пользовательскими (String) разделителями?
Я хотел бы сделать что-то вроде:
String text;
while((text = myStreamReader.ReadUntil("my_delim")) != null)
{
Console.WriteLine(text);
}
Я попытался сделать свой собственный с помощью Peek()
и StringBuilder
, но он слишком неэффективен. Я ищу предложения или, возможно, решение с открытым исходным кодом.
Спасибо.
Edit
Я должен был разъяснить это ранее... Я видел этот ответ, однако я бы предпочел не читать весь файл в памяти.
Ответы
Ответ 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();
}