将字符串拆分为行的最佳方法
Posted
技术标签:
【中文标题】将字符串拆分为行的最佳方法【英文标题】:Best way to split string into lines 【发布时间】:2010-12-03 06:04:03 【问题描述】:如何将多行字符串拆分成行?
我知道这种方式
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
看起来有点难看,并且丢失了空行。有没有更好的解决方案?
【问题讨论】:
Easiest way to split a string on newlines in .NET?的可能重复 【参考方案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;
【讨论】:
有趣!是否应该实现IEnumerable<>
?【参考方案2】:
string[] lines = input.Split(new[] '\r', '\n' , StringSplitOptions.RemoveEmptyEntries);
【讨论】:
【参考方案3】:正确处理混合行结尾很棘手。众所周知,换行符可以是“换行符”(ASCII 10、\n
、\x0A
、\u000A
)、“回车”(ASCII 13、\r
、\x0D
、\u000D
),或它们的某种组合。回到 DOS,Windows 使用两个字符序列 CR-LF \u000D\u000A
,所以这个组合应该只发出一行。 Unix 使用单个 \u000A
,而非常旧的 Mac 使用单个 \u000D
字符。在单个文本文件中处理这些字符的任意混合的标准方法如下:
\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;
【讨论】:
【参考方案4】:更新:请参阅 here 了解替代/异步解决方案。
这很好用,而且比 Regex 更快:
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
【讨论】:
请添加更多详细信息,以使您的答案对读者更有用。 完成。还添加了一个测试来比较其与正则表达式解决方案的性能。 如果使用[\r\n]1,2
,由于相同功能的回溯更少,所以模式会更快
@OmegaMan 这有一些不同的行为。它将匹配 \n\r
或 \n\n
作为不正确的单个换行符。
@OmegaMan Hello\n\nworld\n\n
是一个边缘案例吗?很明显是一行文字,后面是空行,再后面是文字,后面是空行。【参考方案5】:
我有这个 other answer,但是这个基于 Jack 的 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
【讨论】:
我想知道这是否是因为您实际上并没有检查枚举器的结果,因此它没有被执行。可惜我懒得查了。 是的,确实如此!!当您将 .ToList() 添加到两个调用时,StringReader 解决方案实际上更慢!在我的机器上是 6.74s 与 5.10s 这是有道理的。我仍然更喜欢这种方法,因为它可以让我异步获取行。 也许您应该删除其他答案上的“更好的解决方案”标题并编辑这个...【参考方案6】:如果看起来很难看,只需删除不必要的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
。
【讨论】:
删除 ToCharArray 将使代码特定于平台(NewLine 可以是 '\n') @Will:如果您指的是我而不是 Konstantin:我相信(强烈)解析代码应该努力在所有平台上工作(即还应该读取在不同平台上编码的文本文件而不是执行平台)。所以对于解析,就我而言,Environment.NewLine
是不可行的。事实上,在所有可能的解决方案中,我更喜欢使用正则表达式的解决方案,因为只有这样才能正确处理所有源平台。
@Hamish 好吧,只需查看枚举的文档,或查看原始问题!这是StringSplitOptions.RemoveEmptyEntries
。
包含'\r\n\r\n'的文本怎么样。 string.Split 将返回 4 个空行,但是使用 '\r\n' 它应该给出 2 个。如果 '\r\n' 和 '\r' 混合在一个文件中,情况会变得更糟。
@SurikovPavel 使用正则表达式。这绝对是首选变体,因为它适用于任何行尾组合。【参考方案7】:
你可以使用 Regex.Split:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
编辑:添加 |\r
以说明(较旧的)Mac 行终止符。
【讨论】:
但这不适用于 OS X 样式的文本文件,因为这些文件仅使用\r
作为行尾。
@Konrad Rudolph:AFAIK,'\r' 曾在非常古老的 MacOS 系统上使用,几乎再也没有遇到过。但是,如果 OP 需要考虑它(或者如果我弄错了),那么当然可以很容易地扩展正则表达式来解释它:\r?\n|\r
@Bart:我不认为你弄错了,但我在程序员的职业生涯中反复遇到过所有可能的行尾。
@Konrad,你可能是对的。我猜,安全总比抱歉好。
@ΩmegaMan:那会丢失空行,例如\n\n.【参考方案8】:
using (StringReader sr = new StringReader(text))
string line;
while ((line = sr.ReadLine()) != null)
// do something
【讨论】:
根据我的主观意见,这是最干净的方法。 在性能方面有什么想法(与string.Split
或Regex.Split
相比)?【参考方案9】:
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();
【讨论】:
【参考方案10】:略微扭曲,但需要一个迭代器块:
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();
【讨论】:
【参考方案11】:如果您想保留空行,只需删除 StringSplitOptions。
var result = input.Split(System.Environment.NewLine.ToCharArray());
【讨论】:
NewLine 可以是 '\n' 并且输入文本可以包含 "\n\r"。以上是关于将字符串拆分为行的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章