【发布时间】:2010-12-03 06:04:03
【问题描述】:
如何将多行字符串拆分成行?
我知道这种方式
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
看起来有点难看,并且丢失了空行。有没有更好的解决方案?
【问题讨论】:
标签: c# string syntax multiline
如何将多行字符串拆分成行?
我知道这种方式
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
看起来有点难看,并且丢失了空行。有没有更好的解决方案?
【问题讨论】:
标签: c# string syntax multiline
将一个字符串分成几行而不进行任何分配。
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<>?
string[] lines = input.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
【讨论】:
正确处理混合行结尾很棘手。众所周知,换行符可以是“换行符”(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;
}
【讨论】:
这很好用,而且比 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},由于相同功能的回溯更少,所以模式会更快
\n\r 或 \n\n 作为不正确的单个换行符。
Hello\n\nworld\n\n 是一个边缘案例吗?很明显是一行文字,后面是空行,再后面是文字,后面是空行。
我有这个 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
【讨论】:
如果看起来很难看,只需删除不必要的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。
【讨论】:
Environment.NewLine 是不可行的。事实上,在所有可能的解决方案中,我更喜欢使用正则表达式的解决方案,因为只有这样才能正确处理所有源平台。
StringSplitOptions.RemoveEmptyEntries。
你可以使用 Regex.Split:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
编辑:添加 |\r 以说明(较旧的)Mac 行终止符。
【讨论】:
\r 作为行尾。
using (StringReader sr = new StringReader(text)) {
string line;
while ((line = sr.ReadLine()) != null) {
// do something
}
}
【讨论】:
string.Split 或Regex.Split 相比)?
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();
}
【讨论】:
略微扭曲,但需要一个迭代器块:
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();
【讨论】:
如果您想保留空行,只需删除 StringSplitOptions。
var result = input.Split(System.Environment.NewLine.ToCharArray());
【讨论】: