166

如何将多行字符串拆分为行?

我知道这种方式

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

看起来有点难看,并且丢失了空行。有更好的解决方案吗?

4

11 回答 11

207
  • 如果它看起来很难看,只需删除不必要的ToCharArray调用。

  • 如果你想用\n或分割\r,你有两个选择:

    • 使用数组文字——但这会给你空行作为 Windows 风格的行尾\r\n

      var result = text.Split(new [] { '\r', '\n' });
      
    • 使用正则表达式,如 Bart 所示:

      var result = Regex.Split(text, "\r\n|\r|\n");
      
  • 如果要保留空行,为什么要明确告诉 C# 将它们丢弃?(StringSplitOptions参数) –StringSplitOptions.None改为使用。

于 2009-10-02T07:53:21.803 回答
148
using (StringReader sr = new StringReader(text)) {
    string line;
    while ((line = sr.ReadLine()) != null) {
        // do something
    }
}
于 2011-07-29T13:17:55.660 回答
70

更新:请参阅此处了解替代/异步解决方案。


这很好用并且比正则表达式更快:

input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)

重要的是"\r\n"在数组中有第一个,以便将其视为一个换行符。以上给出了与这些正则表达式解决方案中的任何一个相同的结果:

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
于 2014-08-08T04:21:09.820 回答
36

您可以使用 Regex.Split:

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

编辑:添加|\r以说明(较旧的)Mac 行终止符。

于 2009-10-02T07:53:39.180 回答
11

如果您想保留空行,只需删除 StringSplitOptions。

var result = input.Split(System.Environment.NewLine.ToCharArray());
于 2009-10-02T07:57:23.727 回答
7
string[] lines = input.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
于 2013-08-11T05:58:21.210 回答
5

我有这个另一个答案,但是这个基于杰克的答案速度明显更快可能是首选,因为它是异步工​​作的,虽然速度稍慢。

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

于 2016-12-16T03:31:23.330 回答
2

稍微扭曲,但是一个迭代器块来做到这一点:

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();
于 2009-10-02T08:08:27.297 回答
2

正确处理混合行结尾很棘手。众所周知,行终止字符可以是“换行符”(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;
}
于 2019-02-06T18:22:32.717 回答
1
    private string[] GetLines(string text)
    {

        List<string> lines = new List<string>();
        using (MemoryStream ms = new MemoryStream())
        {
            StreamWriter sw = new StreamWriter(ms);
            sw.Write(text);
            sw.Flush();

            ms.Position = 0;

            string line;

            using (StreamReader sr = new StreamReader(ms))
            {
                while ((line = sr.ReadLine()) != null)
                {
                    lines.Add(line);
                }
            }
            sw.Close();
        }



        return lines.ToArray();
    }
于 2015-08-06T00:55:53.077 回答
1

将字符串拆分为行,无需任何分配。

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


}
于 2021-01-30T14:40:44.227 回答