将字符串拆分为行的最佳方法


143

如何将多行字符串分成几行?

我知道这边

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

看起来有点丑陋,而且没有空线。有更好的解决方案吗?



1
我喜欢这种解决方案,我不知道如何使它变得更容易。第二个参数当然会删除容器。
NappingRabbit

Answers:


172
  • 如果看起来很丑,只需删除不必要的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改用。


2
删除ToCharArray将使代码特定于平台(NewLine可以为'\ n')
Konstantin Spirin,2009年

1
@Will:不太可能是您指的是我而不是Konstantin:我坚信(强烈)解析代码应努力在所有平台上工作(即,它还应读取在与执行平台不同的平台上编码的文本文件))。因此,Environment.NewLine就我而言,解析是不可行的。实际上,在所有可能的解决方案中,我更喜欢使用正则表达式的解决方案,因为只有这样才能正确处理所有源平台。
康拉德·鲁道夫

2
@Hamish好吧,只需查看枚举的文档,或查看原始问题!是StringSplitOptions.RemoveEmptyEntries
康拉德·鲁道夫

8
包含'\ r \ n \ r \ n'的文本怎么样。string.Split将返回4个空行,但是使用'\ r \ n'应该给出2。如果将'\ r \ n'和'\ r'混合在一个文件中,情况会更糟。
用户名

1
@SurikovPavel使用正则表达式。这绝对是首选的变体,因为它可以与行尾的任何组合一起正常工作。
康拉德·鲁道夫2012年

134
using (StringReader sr = new StringReader(text)) {
    string line;
    while ((line = sr.ReadLine()) != null) {
        // do something
    }
}

12
在我主观上,这是最干净的方法。
2013年

5
关于性能(相对于string.SplitRegex.Split)有什么想法吗?
Uwe Keim

52

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


这很好用,并且比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

请添加更多详细信息,以使您的答案对读者更有用。
Mohit Jain 2014年

做完了 还添加了一项测试,以将其性能与Regex解决方案进行比较。
orad 2014年

稍快,由于具有相同的功能少回溯模式,如果一个用途[\r\n]{1,2}
ΩmegaMan

@OmegaMan这有一些不同的行为。它将匹配\n\r\n\n作为单个换行符,这是不正确的。
orad 2015年

3
@OmegaMan Hello\n\nworld\n\n边缘情况如何?显然,这是一行文本,然后是空行,然后是另一行文本,然后是空行。
布兰丁

36

您可以使用Regex.Split:

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

编辑:添加|\r到帐户(旧)Mac行终止符。


但是,这将不适用于OS X样式的文本文件,因为这些文件仅用\r作行尾。
Konrad Rudolph

2
@Konrad Rudolph:AFAIK,'\ r'用在非常老的MacOS系统上,几乎再也没有遇到过。但是,如果OP需要考虑它(或者我弄错了),那么可以很容易地将正则表达式扩展为考虑它:\ r?\ n | \ r
Bart Kiers

@Bart:我不认为你错了,但我已经多次遇到的所有可能的行结束我的职业生涯,作为一个程序员。
Konrad Rudolph

@Konrad,你可能是对的。我想,安全胜过遗憾。
巴特·基尔斯

1
@ΩmegaMan:这将丢失空白行,例如\ n \ n。
Mike Rosoft


4

我有这个其他的答案,但是这一次,根据杰克的答案是显著较快可能是首选,因为它以异步方式工作,虽然速度稍慢。

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


我确实想知道这是否是因为您实际上没有检查枚举数的结果,因此没有执行该枚举数。不幸的是,我懒得检查。
詹姆斯·霍尔威尔

是的,实际上是!!在两个调用中都添加.ToList()时,StringReader解决方案实际上要慢一些!在我的机器上,它是6.74s和5.10s
JCH2k

这就说得通了。我仍然更喜欢这种方法,因为它使我可以异步获取行。
orad

也许您应该删除其他答案上的“更好的解决方案”标头,然后编辑该答案...
JCH2k

4
string[] lines = input.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

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();

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

1

正确处理混合行的结尾很棘手。正如我们所知,该行结束符可以是“换行”(ASCII 10, ,\n\x0A),“\u000A回车”(ASCII 13, ,\r\x0D),\u000D或者它们的某种组合。回到DOS,Windows使用两个字符的序列CR-LF \u000D\u000A,因此这种组合只发出一行。Unix使用\u000A单个\u000D字符,非常老的Mac使用单个字符。在单个文本文件中处理这些字符的任意混合的标准方法如下:

  • 每个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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.