Bagaimana Anda membagi string multi-baris menjadi garis?
Saya tahu cara ini
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
terlihat agak jelek dan kehilangan garis kosong. Apakah ada solusi yang lebih baik?
Bagaimana Anda membagi string multi-baris menjadi garis?
Saya tahu cara ini
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
terlihat agak jelek dan kehilangan garis kosong. Apakah ada solusi yang lebih baik?
Jawaban:
Jika terlihat jelek, hapus saja ToCharArraypanggilan yang tidak perlu .
Jika Anda ingin membagi dengan salah satu \natau \r, Anda punya dua opsi:
Gunakan array literal - tetapi ini akan memberi Anda baris kosong untuk akhiran bergaya Windows \r\n:
var result = text.Split(new [] { '\r', '\n' });Gunakan ekspresi reguler, seperti yang ditunjukkan oleh Bart:
var result = Regex.Split(text, "\r\n|\r|\n");Jika Anda ingin mempertahankan baris kosong, mengapa Anda secara eksplisit meminta C # untuk membuangnya? ( StringSplitOptionsparameter) - gunakan StringSplitOptions.Nonesaja.
Environment.NewLineadalah jalan keluar sejauh yang saya ketahui. Bahkan, dari semua solusi yang mungkin saya lebih suka yang menggunakan ekspresi reguler karena hanya itu yang menangani semua platform sumber dengan benar.
StringSplitOptions.RemoveEmptyEntries.
Ini berfungsi dengan baik dan lebih cepat daripada Regex:
input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)
Penting untuk memiliki yang "\r\n"pertama dalam array sehingga diambil sebagai satu baris. Di atas memberikan hasil yang sama dengan salah satu dari solusi Regex ini:
Regex.Split(input, "\r\n|\r|\n")
Regex.Split(input, "\r?\n|\r")
Kecuali bahwa Regex ternyata sekitar 10 kali lebih lambat. Inilah tes saya:
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")
);
Keluaran:
00:00: 03.8527616
00:00: 31.8017726
00:00: 32.5557128
dan inilah Metode Extension:
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);
}
}
Pemakaian:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
[\r\n]{1,2}
\n\ratau \n\nsebagai pemecah baris tunggal yang tidak benar.
Hello\n\nworld\n\nkasus tepi? Itu jelas satu baris dengan teks, diikuti oleh baris kosong, diikuti oleh baris lain dengan teks, diikuti oleh baris kosong.
Anda bisa menggunakan Regex. Letakkan:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
Sunting: ditambahkan |\rke akun untuk terminator garis Mac (lama).
\rsebagai akhir baris.
Jika Anda ingin menjaga baris kosong cukup hapus StringSplitOptions.
var result = input.Split(System.Environment.NewLine.ToCharArray());
Saya punya jawaban lain ini tetapi yang ini, berdasarkan jawaban Jack , secara signifikan lebih cepat mungkin lebih disukai karena ia bekerja secara serempak, walaupun sedikit lebih lambat.
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;
}
}
}
}
Pemakaian:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
Uji:
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()
);
Keluaran:
00:00: 03.9603894
00.00: 00.0029996
00:00: 04.8221971
Sedikit terpelintir, tetapi blok iterator untuk melakukannya:
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);
}
Anda kemudian dapat menelepon:
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();
}
Sangat sulit untuk menangani ujung garis campuran dengan benar. Seperti kita ketahui, karakter garis terminasi dapat "Pakan Line" (ASCII 10, \n, \x0A, \u000A), "Carriage Return" (ASCII 13, \r, \x0D, \u000D), atau beberapa kombinasi dari mereka. Kembali ke DOS, Windows menggunakan urutan dua karakter CR-LF \u000D\u000A, jadi kombinasi ini hanya akan memancarkan satu baris. Unix menggunakan satu \u000A, dan sangat lama Mac menggunakan satu \u000Dkarakter. Cara standar untuk memperlakukan campuran karakter ini secara acak dalam satu file teks adalah sebagai berikut:
\u000D\u000A) maka keduanya bersama - sama hanya melewati satu baris.String.Empty adalah satu-satunya input yang tidak menghasilkan baris (karakter apa pun memerlukan setidaknya satu baris)Aturan sebelumnya menjelaskan perilaku StringReader.ReadLine dan fungsi terkait, dan fungsi yang ditunjukkan di bawah ini menghasilkan hasil yang identik. Ini adalah fungsi melanggar garis C # yang efisien yang dengan patuh mengimplementasikan pedoman ini untuk menangani urutan atau kombinasi arbitrer / CR / LF yang sewenang-wenang. Baris yang disebutkan tidak mengandung karakter CR / LF. Baris kosong dipertahankan dan dikembalikan sebagai 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));
}
Catatan: Jika Anda tidak keberatan overhead menciptakan StringReader instance pada setiap panggilan, Anda dapat menggunakan kode C # 7 berikut . Seperti disebutkan, sementara contoh di atas mungkin sedikit lebih efisien, kedua fungsi ini menghasilkan hasil yang sama persis.
public static IEnumerable<String> Lines(this String s)
{
using (var tr = new StringReader(s))
while (tr.ReadLine() is String L)
yield return L;
}