Как разделить многострочную строку на строки?
Я знаю этот способ
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
выглядит немного некрасиво и теряет пустые строки. Есть ли лучшее решение?
Как разделить многострочную строку на строки?
Я знаю этот способ
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
выглядит немного некрасиво и теряет пустые строки. Есть ли лучшее решение?
Если это выглядит некрасиво, просто удалите ненужный вызов ToCharArray
.
Если вы хотите разделить на \n
или \r
, у вас есть два варианта:
Используйте литерал массива - но это даст вам пустые строки для окончаний строк в стиле Windows \r\n
:
var result = text.Split(new [] { '\r', '\n' });
Используйте регулярное выражение, как указывает Барт:
var result = Regex.Split(text, "\r\n|\r|\n");
Если вы хотите сохранить пустые строки, почему вы явно указываете C # выбросить их? (Параметр StringSplitOptions
) - используйте вместо него StringSplitOptions.None
.
Environment.NewLine
мне не подходит. Фактически, из всех возможных решений я предпочитаю то, которое использует регулярные выражения, поскольку только оно правильно обрабатывает все исходные платформы.
- person Konrad Rudolph; 20.01.2011
StringSplitOptions.RemoveEmptyEntries
.
- person Konrad Rudolph; 19.10.2011
Regex.Split
, то есть - var result = Regex.Split(text, @"\r\n|\r|\n");
В этом случае он работает в любом случае, потому что компилятор C # интерпретирует \ n и \ r так же, как синтаксический анализатор регулярных выражений. В общем случае это может вызвать проблемы.
- person Ken Clement; 16.11.2017
"\r"
как разделитель строк (потому что, давайте посмотрим правде в глаза, ни одна система не использовала это соглашение уже более десяти лет): единственные фактически используемые соглашения - это "\r\n"
и "\n"
. Фактически, ваш пример ("\n\r"
) никогда нигде не был допустимым разрывом строки. Либо прочтите это как два разрыва строки, либо вызовите ошибку, но ни в коем случае не воспринимайте это как одиночный разрыв строки.
- person Konrad Rudolph; 21.08.2018
"\r"
никогда не используется в качестве разделителя, вы можете легко написать универсальный синтаксический анализатор, который принимает все фактически используемые разделители; Это делается простым разделением на "\r\n|\n"
. Нет нужды в чем-то более изысканном, чем это. Но, честно говоря, на практике нет ничего плохого в коде регулярного выражения, показанном в моем ответе, и он будет отлично работать с файлом, который смешивает разные стили разрыва строки, включая устаревший "\r"
.
- person Konrad Rudolph; 21.08.2018
diff
их пометил). Это не «планирование на будущее», это делает код надежным для работы здесь и сейчас.
- person Konrad Rudolph; 21.08.2018
Это отлично работает и быстрее, чем Regex:
input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)
Важно, чтобы "\r\n"
было первым в массиве, чтобы он воспринимался как разрыв одной строки. Вышеупомянутое дает те же результаты, что и любое из этих решений Regex:
Regex.Split(input, "\r\n|\r|\n")
Regex.Split(input, "\r?\n|\r")
За исключением того, что Regex оказывается примерно в 10 раз медленнее. Вот мой тест:
Action<Action> measure = (Action func) => {
var start = DateTime.Now;
for (int i = 0; i < 100000; i++) {
func();
}
var duration = DateTime.Now - start;
Console.WriteLine(duration);
};
var input = "";
for (int i = 0; i < 100; i++)
{
input += "1 \r2\r\n3\n4\n\r5 \r\n\r\n 6\r7\r 8\r\n";
}
measure(() =>
input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)
);
measure(() =>
Regex.Split(input, "\r\n|\r|\n")
);
measure(() =>
Regex.Split(input, "\r?\n|\r")
);
Вывод:
00:00:03.8527616
00:00:31.8017726
00:00:32.5557128
а вот метод расширения:
public static class StringExtensionMethods
{
public static IEnumerable<string> GetLines(this string str, bool removeEmptyLines = false)
{
return str.Split(new[] { "\r\n", "\r", "\n" },
removeEmptyLines ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None);
}
}
Использование:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
[\r\n]{1,2}
- person ΩmegaMan; 27.02.2015
\n\r
или \n\n
как одинарный разрыв строки, что неверно.
- person orad; 28.02.2015
Hello\n\nworld\n\n
крайний случай? Очевидно, что это одна строка с текстом, за которой следует пустая строка, за которой следует еще одна строка с текстом, за которой следует пустая строка.
- person Brandin; 09.08.2015
Вы можете использовать Regex.Split:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
Изменить: добавлено |\r
для учета (старых) терминаторов строк Mac.
\r
в качестве окончания строки.
- person Konrad Rudolph; 02.10.2009
[\r\n]{1,2}
- person ΩmegaMan; 27.02.2015
Если вы хотите сохранить пустые строки, просто удалите StringSplitOptions.
var result = input.Split(System.Environment.NewLine.ToCharArray());
У меня был другой ответ, но этот, основанный на answer, значительно быстрее может быть предпочтительнее, поскольку он работает асинхронно, хотя и немного медленнее.
public static class StringExtensionMethods
{
public static IEnumerable<string> GetLines(this string str, bool removeEmptyLines = false)
{
using (var sr = new StringReader(str))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if (removeEmptyLines && String.IsNullOrWhiteSpace(line))
{
continue;
}
yield return line;
}
}
}
}
Использование:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
Тест:
Action<Action> measure = (Action func) =>
{
var start = DateTime.Now;
for (int i = 0; i < 100000; i++)
{
func();
}
var duration = DateTime.Now - start;
Console.WriteLine(duration);
};
var input = "";
for (int i = 0; i < 100; i++)
{
input += "1 \r2\r\n3\n4\n\r5 \r\n\r\n 6\r7\r 8\r\n";
}
measure(() =>
input.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)
);
measure(() =>
input.GetLines()
);
measure(() =>
input.GetLines().ToList()
);
Вывод:
00:00:03.9603894
00:00:00.0029996
00:00:04.8221971
Немного скрученный, но блок итератора для этого:
public static IEnumerable<string> Lines(this string Text)
{
int cIndex = 0;
int nIndex;
while ((nIndex = Text.IndexOf(Environment.NewLine, cIndex + 1)) != -1)
{
int sIndex = (cIndex == 0 ? 0 : cIndex + 1);
yield return Text.Substring(sIndex, nIndex - sIndex);
cIndex = nIndex;
}
yield return Text.Substring(cIndex + 1);
}
Затем вы можете позвонить:
var result = input.Lines().ToArray();
Сложно правильно обрабатывать смешанные окончания строк. Как мы знаем, символы завершения строки могут быть «переводом строки» (ASCII 10, \n
, \x0A
, \u000A
), «возвратом каретки» (ASCII 13, \r
, \x0D
, \u000D
) или их комбинацией. Возвращаясь к DOS, Windows использует двухсимвольную последовательность CR-LF \u000D\u000A
, поэтому эта комбинация должна выдавать только одну строку. В Unix используется один символ \u000A
, а в очень старых компьютерах Mac использовался один символ \u000D
. Стандартный способ обработки произвольных сочетаний этих символов в одном текстовом файле следующий:
\u000D\u000A
), то эти два вместе пропускают только одну строку.String.Empty
- единственный вход, который не возвращает строк (любой символ влечет за собой хотя бы одну строку)Предыдущее правило описывает поведение StringReader.ReadLine и связанных функций, а функция, показанная ниже, дает идентичные результаты. Это эффективная функция разрыва строки C #, которая должным образом реализует эти рекомендации для правильной обработки любой произвольной последовательности или комбинации CR / LF. Пронумерованные строки не содержат символов CR / LF. Пустые строки сохраняются и возвращаются как String.Empty
.
/// <summary>
/// Enumerates the text lines from the string.
/// ⁃ Mixed CR-LF scenarios are handled correctly
/// ⁃ String.Empty is returned for each empty line
/// ⁃ No returned string ever contains CR or LF
/// </summary>
public static IEnumerable<String> Lines(this String s)
{
int j = 0, c, i;
char ch;
if ((c = s.Length) > 0)
do
{
for (i = j; (ch = s[j]) != '\r' && ch != '\n' && ++j < c;)
;
yield return s.Substring(i, j - i);
}
while (++j < c && (ch != '\r' || s[j] != '\n' || ++j < c));
}
Примечание. Если вы не возражаете против накладных расходов на создание StringReader
экземпляра при каждом вызове, вы можете вместо этого использовать следующий код C # 7. Как уже отмечалось, хотя приведенный выше пример может быть немного более эффективным, обе эти функции дают одинаковые результаты.
public static IEnumerable<String> Lines(this String s)
{
using (var tr = new StringReader(s))
while (tr.ReadLine() is String L)
yield return L;
}
Разделить строку на строки без выделения.
public static LineEnumerator GetLines(this string text) {
return new LineEnumerator( text.AsSpan() );
}
internal ref struct LineEnumerator {
private ReadOnlySpan<char> Text { get; set; }
public ReadOnlySpan<char> Current { get; private set; }
public LineEnumerator(ReadOnlySpan<char> text) {
Text = text;
Current = default;
}
public LineEnumerator GetEnumerator() {
return this;
}
public bool MoveNext() {
if (Text.IsEmpty) return false;
var index = Text.IndexOf( '\n' ); // \r\n or \n
if (index != -1) {
Current = Text.Slice( 0, index + 1 );
Text = Text.Slice( index + 1 );
return true;
} else {
Current = Text;
Text = ReadOnlySpan<char>.Empty;
return true;
}
}
}
IEnumerable<>
?
- person Konstantin Spirin; 01.02.2021
string.Split
или Regex.Split
)?
- person Uwe Keim; 25.01.2019