คุณจะแยกสตริงหลายบรรทัดเป็นเส้นได้อย่างไร
ฉันรู้แบบนี้
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
ดูน่าเกลียดเล็กน้อยและสูญเสียบรรทัดว่าง มีวิธีแก้ปัญหาที่ดีกว่านี้ไหม?
คุณจะแยกสตริงหลายบรรทัดเป็นเส้นได้อย่างไร
ฉันรู้แบบนี้
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
ดูน่าเกลียดเล็กน้อยและสูญเสียบรรทัดว่าง มีวิธีแก้ปัญหาที่ดีกว่านี้ไหม?
คำตอบ:
หากดูเหมือนน่าเกลียดเพียงแค่ลบ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:
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
[\r\n]{1,2}
\n\r
หรือแบ่ง\n\n
เป็นบรรทัดเดียวซึ่งไม่ถูกต้อง
Hello\n\nworld\n\n
กรณีขอบเป็นอย่างไร เห็นได้ชัดว่ามีหนึ่งบรรทัดพร้อมข้อความตามด้วยบรรทัดว่างแล้วตามด้วยบรรทัดอื่นที่มีข้อความตามด้วยบรรทัดว่าง
คุณสามารถใช้ Regex.Split:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
แก้ไข: เพิ่ม|\r
ไปยังบัญชีสำหรับ (ตัวเก่า) Mac terminator
\r
เป็นเพียงการสิ้นสุดบรรทัด
ถ้าคุณต้องการเก็บบรรทัดว่างไว้ให้ลบ StringSplitOptions
var result = input.Split(System.Environment.NewLine.ToCharArray());
ฉันมีนี้คำตอบอื่น ๆแต่อันนี้ขึ้นอยู่กับแจ็คคำตอบ , อย่างมีนัยสำคัญได้เร็วขึ้นอาจจะเป็นที่ต้องการเนื่องจากมันทำงานแบบไม่พร้อมแม้จะเล็กน้อยช้า
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
string[] lines = input.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
บิดเล็กน้อย แต่มีตัววนซ้ำทำหน้าที่:
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();
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();
}
มันยากที่จะจัดการกับจุดสิ้นสุดของเส้นผสมอย่างเหมาะสม ที่เรารู้ว่าตัวละครเลิกจ้างบรรทัดสามารถ "สายฟีด" (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;
}