Лучший способ разбить строку на строки

Как разделить многострочную строку на строки?

Я знаю этот способ

var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

выглядит немного некрасиво и теряет пустые строки. Есть ли лучшее решение?


person Konstantin Spirin    schedule 02.10.2009    source источник
comment
Возможный дубликат Самый простой способ разбить строку на новых строках в .NET?   -  person Robin Bennett    schedule 13.05.2019


Ответы (9)


  • Если это выглядит некрасиво, просто удалите ненужный вызов 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.

person Konrad Rudolph    schedule 02.10.2009
comment
Удаление ToCharArray сделает код зависимым от платформы (NewLine может быть '\ n') - person Konstantin Spirin; 02.10.2009
comment
@Kon вы должны использовать Environment.NewLine, если вас это беспокоит. Или вы имеете в виду происхождение текста, а не место исполнения? - person ; 20.01.2011
comment
@Will: на случай, если вы имели в виду меня, а не Константина: я считаю (сильно), что код синтаксического анализа должен стремиться работать на всех платформах (т.е. он также должен читать текстовые файлы, которые были закодированы на различных платформах, чем исполняющая платформа). Так что для синтаксического анализа Environment.NewLine мне не подходит. Фактически, из всех возможных решений я предпочитаю то, которое использует регулярные выражения, поскольку только оно правильно обрабатывает все исходные платформы. - person Konrad Rudolph; 20.01.2011
comment
lol не заметил сходства названий. Я полностью согласен в этом случае. - person ; 20.01.2011
comment
@Hamish Ну просто посмотрите документацию перечисления или посмотрите исходный вопрос! Это StringSplitOptions.RemoveEmptyEntries. - person Konrad Rudolph; 19.10.2011
comment
Ах, я понял, моя проблема, я искал в RegexOptions; еще не пили кофе. - person Hamish Grubijan; 19.10.2011
comment
Как насчет текста, содержащего '\ r \ n \ r \ n'. string.Split вернет 4 пустые строки, однако с '\ r \ n' он должен дать 2. Будет хуже, если '\ r \ n' и '\ r' смешаны в одном файле. - person username; 27.04.2012
comment
@SurikovPavel Используйте регулярное выражение. Это определенно предпочтительный вариант, поскольку он правильно работает с любой комбинацией окончаний строк. - person Konrad Rudolph; 28.04.2012
comment
Незначительный момент - я обычно использую дословный строковый литерал во втором аргументе Regex.Split, то есть - var result = Regex.Split(text, @"\r\n|\r|\n"); В этом случае он работает в любом случае, потому что компилятор C # интерпретирует \ n и \ r так же, как синтаксический анализатор регулярных выражений. В общем случае это может вызвать проблемы. - person Ken Clement; 16.11.2017
comment
Просто добавляю свои 2с. Поскольку OP хочет сохранить пустые строки, вы не можете написать синтаксический анализатор, который работает для любого типа среды и / или обрабатывает смешанные случаи (например, RegEx), потому что, если у вас есть '\ n \ r 'откуда вы знаете, что это один "разрыв" вместо двух, которые просто закодированы неправильно? Если второе, то это будут две пустые строки, а если первое, то только одна. Вы должны спросить, что является источником кодировок. Если источник находится на той же платформе, что и анализатор (независимо от того, на какой платформе), вы можете использовать Environment.NewLine, поскольку источник известен. - person Mark A. Donohoe; 20.08.2018
comment
@MarqueIV На это есть разные возможные ответы, и все они действительны. Один из них - ожидать и требовать согласованных текстовых файлов. Другой - не принимать "\r" как разделитель строк (потому что, давайте посмотрим правде в глаза, ни одна система не использовала это соглашение уже более десяти лет): единственные фактически используемые соглашения - это "\r\n" и "\n". Фактически, ваш пример ("\n\r") никогда нигде не был допустимым разрывом строки. Либо прочтите это как два разрыва строки, либо вызовите ошибку, но ни в коем случае не воспринимайте это как одиночный разрыв строки. - person Konrad Rudolph; 21.08.2018
comment
Перво-наперво, мой текст был опечаткой. Используйте '\ r \ n', и моя точка зрения все та же: вы не можете написать универсальный парсер в системе, если вам необходимо сохранять пустые строки. Обратите внимание: добавив ограничение, что вы не должны принимать '\ r' отдельно, и хотите использовать '\ n' только для обнаружения новых строк, с этим изменением у вас больше нет универсального парсера < / i> по сути доказывая мою точку зрения, что без таких ограничений это невозможно (легко *) сделать, и, скорее всего, это не обязательно должно быть в первую очередь. (* Он может играть с упорядочиванием RegEx и т. Д., Но это просто делает его намного медленнее.) - person Mark A. Donohoe; 21.08.2018
comment
@MarqueIV Я думаю, вы неправильно прочитали мой комментарий: поскольку "\r" никогда не используется в качестве разделителя, вы можете легко написать универсальный синтаксический анализатор, который принимает все фактически используемые разделители; Это делается простым разделением на "\r\n|\n". Нет нужды в чем-то более изысканном, чем это. Но, честно говоря, на практике нет ничего плохого в коде регулярного выражения, показанном в моем ответе, и он будет отлично работать с файлом, который смешивает разные стили разрыва строки, включая устаревший "\r". - person Konrad Rudolph; 21.08.2018
comment
Если у вас есть ввод со смешанными стилями, как вы сказали, нет способа отличить '\ n \ r' от '\ n' и '\ r', не предполагая, что никогда не будет '\ r', и когда вы делаете это предположение, вы удаляете только что упомянутое условие, вызывающее неоднозначность. Кроме того, вы в любом случае не можете сделать это предположение, поскольку существует множество встроенных аппаратных систем, которые используют '\ r'. Вот почему терминалы предоставляют вам три варианта разрыва строки. Вы должны заранее знать, что вносите свой вклад. Думаю, нам просто придется не соглашаться, и каждый будет использовать то, что работает для нас. - person Mark A. Donohoe; 21.08.2018
comment
@MarqueIV Вот почему в моем предыдущем комментарии сказано, что «на практике» это работает. Вы спорите из-за очень маловероятного случая. Да, очевидно, что такие случаи неоднозначны, но я утверждаю, что они недостаточно актуальны, чтобы заботиться о них, и эти неоднозначности в любом случае принципиально неразрешимы: стратегия синтаксического анализа нет будет работать, поскольку неоднозначность тогда находится в самих данных, не в процессе парсинга. - person Konrad Rudolph; 21.08.2018
comment
Но я считаю, что вы только что разъяснили мне мою точку зрения. Именно поэтому я просто использую Environment.NewLine по умолчанию и использую что-то вроде решения RegEx, только если вы выходите за пределы области более вероятных сценариев. Это случается, но, как говорится, гигантский тайм-киллер внедряет решения для вещей, которые могут произойти, а не для того, что происходит. Конечно, планируйте на будущее, конечно (т.е. не загоняйте себя в угол, где вы не сможете внести изменения позже), но на самом деле не реализуйте будущее, пока вам действительно не понадобится. Другими словами, я не думаю, что наши точки зрения так далеки. - person Mark A. Donohoe; 21.08.2018
comment
@MarqueIV «Именно поэтому я просто использую Environment.NewLine» - но это худшее, что вы можете сделать, потому что теперь вы начинаете ломать множество фактических файлов, тогда как мое решение ломает примерно ноль реально существующих файлов. Посмотрите, сколько современных текстовых редакторов используют для переноса строки только системный символ новой строки (подсказка: ни один из них не поддерживает). - person Konrad Rudolph; 21.08.2018
comment
Ничего не сломается, если вы никогда не планируете получать что-то, что не соответствует кодировке вашей платформы. Если вы это знаете (точно так же, как вы знаете, что, возможно, никогда не будет '\ r'), тогда вы оптимизируете свои результаты, не тратя время на то, чтобы запускать вещи через механизм RegEx, который не нужен, что может убить время - критическое приложение. Если у вас будет несколько кодировок, используйте RegEx. Вы просто не можете сделать универсального. Опять же, я не думаю, что мы спорим об одном и том же. Ты сделал свой, а я другой. Тангенциально, но не в противоречии. - person Mark A. Donohoe; 21.08.2018
comment
@MarqueIV Мне, честно говоря, трудно понять ваш вариант использования: вам не нужно выходить за пределы своей текущей платформы, чтобы встретить текстовые файлы, в которых используются другие соглашения о завершении строк. Я точно знаю, что моя текущая система содержит файлы с разными соглашениями (я редактировал один только вчера, и я знаю только о расходящихся окончаниях строк, потому что diff их пометил). Это не «планирование на будущее», это делает код надежным для работы здесь и сейчас. - person Konrad Rudolph; 21.08.2018
comment
Кроме того, сделав шаг назад, можно было бы возразить, что если вам действительно нужны пустые строки, но не применять стандарт для кодирования строк, то вы просто напрашиваетесь на проблемы. так или иначе. В конце концов, если вы пропустите пустые строки, вы можете написать универсальный синтаксический анализатор, сделав весь этот поток convo устаревшим! :) - person Mark A. Donohoe; 21.08.2018
comment
И в вашем случае я бы сказал, что «платформа» - это то, что вы используете инструменты редактирования, которые могут иметь разные окончания строк, следовательно, вы получаете свой diff. Но если вы используете известный формат, например, из другой системы, а не что-то отредактированное вручную, тогда нет необходимости планировать этот случай, и вы можете увеличить пропускную способность обработки, не используя. Опять же, мы не спорим об одном и том же!. Время и место. Если вы используете файлы, редактируемые пользователем, то я с вами на 100% согласен. Но если вы берете сгенерированные системой файлы из известной системы на той же платформе, то я придерживаюсь своего первоначального утверждения. :) - person Mark A. Donohoe; 21.08.2018
comment
@MarqueIV Нет, ничего не было покалечено. У файлов разные (но внутренне согласованные) окончания строк, потому что они были созданы разными людьми на разных платформах. И все же они попадают на мою машину. - И я хочу подчеркнуть, что мы очень спорим об одном и том же, потому что я принципиально не понимаю, где существует ваш потенциальный вариант использования. Я просто не вижу, когда было бы более полезно и с меньшим количеством проблем разделить на платформе жестко запрограммированную новую строку вместо использования моей эвристики, которая, как я (и, очевидно, многие другие), обнаружила, что она работает на 100% реальных файлы. - person Konrad Rudolph; 21.08.2018
comment
Создано разными людьми, на разных платформах. Это другой вариант использования, чем что-то, скажем, из веб-службы, где окончания строк предсказуемы и согласованы. И если эта система находится на той же платформе, вы можете использовать Environment.NewLine и сокрушить производительность RegEx. Опять же время и место. Я планирую, но не реализую решения, пока они не произойдут. Так же, как и код, повышается и продуктивность разработчика. - person Mark A. Donohoe; 21.08.2018
comment
Надеюсь, вы успокоите вас, если вы говорите, что вам нужна система, которая должна обнаруживать пустые строки, и вы берете файлы, созданные на платформах с разными окончаниями строк, и вы гарантируете, что никогда не получите '\ r' сам по себе и / или ваши окончания строк будут согласованы в одном файле (чего вы не можете, если он редактируется на машинах с двумя разными окончаниями строк и все окончания строк не обновляются), тогда я согласен ... регулярное выражение работает. Но я говорю, что если вы не можете дать эти гарантии, этого не произойдет, потому что тогда вы не сможете различать '\ n \ r', '\ n' и '\ р'. Есть смысл? - person Mark A. Donohoe; 21.08.2018
comment
Честно говоря, в этом случае ничего не будет работать, а не только RegEx, потому что в парсере нет стандарта для окончаний строк, что возвращает меня к одному из моих предыдущих пунктов, если вы говорите пустые строки важны для вас, тогда вы должны определить, что представляет собой пустая строка, иначе вы не сможете ответить на вышеуказанный вопрос (без этих других гарантий). - person Mark A. Donohoe; 21.08.2018
comment
Может помочь большая точность: невозможно написать синтаксический анализатор для обработки комбинации всех случаев, RE здесь будет обрабатывать комбинации любых двух случаев в одном файле. - person Mic; 23.09.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
person orad    schedule 08.08.2014
comment
Пожалуйста, добавьте дополнительные сведения, чтобы ваш ответ был более полезным для читателей. - person Mohit Jain; 08.08.2014
comment
Сделанный. Также добавлен тест для сравнения его производительности с решением Regex. - person orad; 08.08.2014
comment
Несколько более быстрый шаблон из-за меньшего количества откатов с той же функциональностью, если используется [\r\n]{1,2} - person ΩmegaMan; 27.02.2015
comment
@OmegaMan У него другое поведение. Он будет соответствовать \n\r или \n\n как одинарный разрыв строки, что неверно. - person orad; 28.02.2015
comment
@orad Я не буду с вами спорить, но если в данных есть переводы строк с несколькими числами ... скорее всего, с данными что-то не так; назовем это крайним случаем. - person ΩmegaMan; 28.02.2015
comment
@OmegaMan Как Hello\n\nworld\n\n крайний случай? Очевидно, что это одна строка с текстом, за которой следует пустая строка, за которой следует еще одна строка с текстом, за которой следует пустая строка. - person Brandin; 09.08.2015

Вы можете использовать Regex.Split:

string[] tokens = Regex.Split(input, @"\r?\n|\r");

Изменить: добавлено |\r для учета (старых) терминаторов строк Mac.

person Bart Kiers    schedule 02.10.2009
comment
Однако это не будет работать с текстовыми файлами в стиле OS X, поскольку они используют только \r в качестве окончания строки. - person Konrad Rudolph; 02.10.2009
comment
@Konrad Rudolph: AFAIK, '\ r' использовалось в очень старых системах MacOS и почти никогда больше не встречается. Но если OP должен это учитывать (или, если я ошибаюсь), то регулярное выражение, конечно, можно легко расширить для его учета: \ r? \ N | \ r - person Bart Kiers; 02.10.2009
comment
@Bart: Я не думаю, что вы ошибаетесь, но я неоднократно сталкивался со всеми возможными окончаниями строк в своей карьере программиста. - person Konrad Rudolph; 02.10.2009
comment
@ Конрад, наверное, ты прав. Думаю, лучше перестраховаться, чем сожалеть. - person Bart Kiers; 02.10.2009
comment
Меньше обратного поиска и та же функциональность с [\r\n]{1,2} - person ΩmegaMan; 27.02.2015
comment
@ ΩmegaMan: при этом будут потеряны пустые строки, например \ п \ п. - person Mike Rosoft; 21.03.2019

Если вы хотите сохранить пустые строки, просто удалите StringSplitOptions.

var result = input.Split(System.Environment.NewLine.ToCharArray());
person Jonas Elfström    schedule 02.10.2009
comment
NewLine может быть '\ n', а вводимый текст может содержать \ n \ r. - person Konstantin Spirin; 02.10.2009

У меня был другой ответ, но этот, основанный на 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

person orad    schedule 16.12.2016
comment
Мне действительно интересно, не потому ли, что вы на самом деле не проверяете результаты перечислителя, и, следовательно, он не выполняется. К сожалению, мне лень проверять. - person James Holwell; 19.10.2017
comment
Да, это действительно так !! Когда вы добавляете .ToList () к обоим вызовам, решение StringReader действительно работает медленнее! На моей машине это 6,74 с против 5,10 с. - person JCH2k; 02.11.2017
comment
В этом есть смысл. Я по-прежнему предпочитаю этот метод, потому что он позволяет мне получать строки асинхронно. - person orad; 06.11.2017
comment
Возможно, вам следует удалить заголовок лучшего решения в другом ответе и отредактировать этот ... - person JCH2k; 06.11.2017

Немного скрученный, но блок итератора для этого:

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();
person JDunkerley    schedule 02.10.2009

Сложно правильно обрабатывать смешанные окончания строк. Как мы знаем, символы завершения строки могут быть «переводом строки» (ASCII 10, \n, \x0A, \u000A), «возвратом каретки» (ASCII 13, \r, \x0D, \u000D) или их комбинацией. Возвращаясь к DOS, Windows использует двухсимвольную последовательность CR-LF \u000D\u000A, поэтому эта комбинация должна выдавать только одну строку. В Unix используется один символ \u000A, а в очень старых компьютерах Mac использовался один символ \u000D. Стандартный способ обработки произвольных сочетаний этих символов в одном текстовом файле следующий:

  • каждый символ CR или LF должен переходить на следующую строку ИСКЛЮЧАЯ ...
  • ... если за CR сразу следует LF (\u000D\u000A), то эти два вместе пропускают только одну строку.
  • String.Empty - единственный вход, который не возвращает строк (любой символ влечет за собой хотя бы одну строку)
  • Последняя строка должна быть возвращена, даже если в ней нет ни CR, ни LF.

Предыдущее правило описывает поведение 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;
}
person Glenn Slayden    schedule 06.02.2019

Разделить строку на строки без выделения.

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;
        }
    }


}
person Denis535    schedule 30.01.2021
comment
Интересный! Следует ли реализовать IEnumerable<>? - person Konstantin Spirin; 01.02.2021

person    schedule
comment
На мой субъективный взгляд, это наиболее чистый подход. - person primo; 21.10.2013
comment
Есть идеи относительно производительности (по сравнению с string.Split или Regex.Split)? - person Uwe Keim; 25.01.2019