Làm thế nào để bạn chia chuỗi nhiều dòng thành dòng?
Tôi biết cách này
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
trông hơi xấu xí và mất đi những dòng trống rỗng. Có một giải pháp tốt hơn?
Làm thế nào để bạn chia chuỗi nhiều dòng thành dòng?
Tôi biết cách này
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
trông hơi xấu xí và mất đi những dòng trống rỗng. Có một giải pháp tốt hơn?
Câu trả lời:
Nếu nó trông xấu, chỉ cần loại bỏ các ToCharArray
cuộc gọi không cần thiết .
Nếu bạn muốn chia theo một \n
hoặc \r
, bạn có hai tùy chọn:
Sử dụng một mảng bằng chữ - nhưng điều này sẽ cung cấp cho bạn các dòng trống cho các kết thúc dòng kiểu Windows \r\n
:
var result = text.Split(new [] { '\r', '\n' });
Sử dụng một biểu thức chính quy, như được chỉ định bởi Bart:
var result = Regex.Split(text, "\r\n|\r|\n");
Nếu bạn muốn giữ các dòng trống, tại sao bạn lại nói rõ ràng với C # để vứt chúng đi? ( StringSplitOptions
tham số) - sử dụng StringSplitOptions.None
thay thế.
Environment.NewLine
là không có gì xa như tôi quan tâm. Trong thực tế, trong tất cả các giải pháp có thể tôi thích giải pháp sử dụng biểu thức chính quy vì chỉ có xử lý chính xác tất cả các nền tảng nguồn.
StringSplitOptions.RemoveEmptyEntries
.
Điều này hoạt động rất tốt và nhanh hơn Regex:
input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)
Điều quan trọng là phải có "\r\n"
đầu tiên trong mảng để nó được thực hiện dưới dạng ngắt một dòng. Ở trên cho kết quả tương tự như một trong các giải pháp Regex sau:
Regex.Split(input, "\r\n|\r|\n")
Regex.Split(input, "\r?\n|\r")
Ngoại trừ việc Regex trở nên chậm hơn khoảng 10 lần. Đây là bài kiểm tra của tôi:
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")
);
Đầu ra:
00:00: 03,8527616
00:00: 31.8017726
00:00: 32.5557128
và đây là Phương pháp mở rộng:
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);
}
}
Sử dụng:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
[\r\n]{1,2}
\n\r
hoặc \n\n
là ngắt dòng đơn không đúng.
Hello\n\nworld\n\n
một trường hợp cạnh? Nó rõ ràng là một dòng có văn bản, theo sau là một dòng trống, tiếp theo là một dòng khác với văn bản, theo sau là một dòng trống.
Bạn có thể sử dụng Regex.Split:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
Chỉnh sửa: được thêm |\r
vào tài khoản cho các đầu cuối dòng Mac (cũ hơn).
\r
làm kết thúc dòng.
Nếu bạn muốn giữ các dòng trống, chỉ cần xóa StringSplitOptions.
var result = input.Split(System.Environment.NewLine.ToCharArray());
Tôi đã có câu trả lời khác nhưng câu trả lời này, dựa trên câu trả lời của Jack , có thể được ưu tiên nhanh hơn đáng kể vì nó hoạt động không đồng bộ, mặc dù chậm hơn một chút.
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;
}
}
}
}
Sử dụng:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
Kiểm tra:
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()
);
Đầu ra:
00:00: 03.9603894
00:00: 00.0029996
00:00: 04.8221971
Hơi xoắn, nhưng một khối lặp để làm điều đó:
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);
}
Sau đó bạn có thể gọi:
var result = input.Lines().ToArray();
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();
}
Thật khó khăn để xử lý kết thúc dòng hỗn hợp đúng cách. Như chúng ta đã biết, các nhân vật chấm dứt dòng có thể được "Line Feed" (ASCII 10, \n
, \x0A
, \u000A
), "Vận chuyển Return" (ASCII 13, \r
, \x0D
, \u000D
), hoặc một số sự kết hợp của họ. Quay trở lại với DOS, Windows sử dụng chuỗi hai ký tự CR-LF \u000D\u000A
, vì vậy sự kết hợp này chỉ nên phát ra một dòng duy nhất. Unix sử dụng một \u000A
máy Mac đơn và rất cũ sử dụng một \u000D
ký tự. Cách tiêu chuẩn để xử lý các hỗn hợp tùy ý của các ký tự này trong một tệp văn bản như sau:
\u000D\u000A
) thì hai cái này cùng nhau bỏ qua chỉ một dòng.String.Empty
là đầu vào duy nhất không trả về dòng nào (bất kỳ ký tự nào cũng yêu cầu ít nhất một dòng)Quy tắc trước mô tả hành vi của StringReader.ReadLine và các hàm liên quan và hàm được hiển thị bên dưới tạo ra kết quả giống hệt nhau. Đây là một chức năng ngắt dòng C # hiệu quả , thực hiện nghiêm túc các hướng dẫn này để xử lý chính xác bất kỳ chuỗi tùy ý hoặc kết hợp CR / LF nào. Các dòng liệt kê không chứa bất kỳ ký tự CR / LF nào. Các dòng trống được bảo quản và trả lại như 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));
}
Lưu ý: Nếu bạn không bận tâm đến việc tạo một StringReader
thể hiện cho mỗi cuộc gọi, bạn có thể sử dụng mã C # 7 sau đây để thay thế. Như đã lưu ý, trong khi ví dụ trên có thể hiệu quả hơn một chút, cả hai chức năng này đều cho kết quả chính xác như nhau.
public static IEnumerable<String> Lines(this String s)
{
using (var tr = new StringReader(s))
while (tr.ReadLine() is String L)
yield return L;
}