Cách tốt nhất để chia chuỗi thành dòng


143

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?



1
Tôi thích giải pháp này, tôi không biết làm thế nào để làm cho nó dễ dàng hơn. Tất nhiên, tham số thứ hai loại bỏ trống rỗng.
NappingRợi

Câu trả lời:


172
  • Nếu nó trông xấu, chỉ cần loại bỏ các ToCharArraycuộc gọi không cần thiết .

  • Nếu bạn muốn chia theo một \nhoặ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? ( StringSplitOptionstham số) - sử dụng StringSplitOptions.Nonethay thế.


2
Xóa ToCharArray sẽ làm cho mã nền tảng cụ thể (NewLine có thể là '\ n')
Konstantin Spirin

1
@ Will: nếu bạn đề cập đến tôi thay vì Konstantin: tôi tin rằng ( mạnh mẽ ) rằng mã phân tích nên cố gắng hoạt động trên tất cả các nền tảng (nghĩa là nó cũng nên đọc các tệp văn bản được mã hóa trên các nền tảng khác với nền tảng thực thi ). Vì vậy, để phân tích cú pháp, Environment.NewLinelà 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.
Konrad Rudolph

2
@ Hamish Vâng, chỉ cần nhìn vào tài liệu của enum, hoặc xem trong câu hỏi ban đầu! Đó là StringSplitOptions.RemoveEmptyEntries.
Konrad Rudolph

8
Làm thế nào về văn bản có chứa '\ r \ n \ r \ n'. chuỗi.Split sẽ trả về 4 dòng trống, tuy nhiên với '\ r \ n' thì nó sẽ cho 2. Sẽ tệ hơn nếu '\ r \ n' và '\ r' được trộn lẫn trong một tệp.
tên người dùng

1
@SurikovPavel Sử dụng biểu thức chính quy. Đó chắc chắn là biến thể ưa thích, vì nó hoạt động chính xác với bất kỳ kết hợp dòng kết thúc nào.
Konrad Rudolph

134
using (StringReader sr = new StringReader(text)) {
    string line;
    while ((line = sr.ReadLine()) != null) {
        // do something
    }
}

12
Đây là cách tiếp cận sạch nhất, theo ý kiến ​​chủ quan của tôi.
primo

5
Bất kỳ ý tưởng về hiệu suất (so với string.Splithoặc Regex.Split)?
Uwe Keim

52

Cập nhật: Xem ở đây để có giải pháp thay thế / không đồng bộ.


Đ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

Vui lòng thêm một số chi tiết để làm cho câu trả lời của bạn hữu ích hơn cho độc giả.
Mohit Jain

Làm xong. Cũng đã thêm một thử nghiệm để so sánh hiệu suất của nó với giải pháp Regex.
orad

Một số mẫu nhanh hơn do ít quay lại với cùng chức năng nếu một người sử dụng[\r\n]{1,2}
ΩmegaMan

@OmegaMan Điều đó có một số hành vi khác nhau. Nó sẽ khớp \n\rhoặc \n\nlà ngắt dòng đơn không đúng.
orad

3
@OmegaMan Làm thế nào là Hello\n\nworld\n\nmộ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.
Brandin

36

Bạn có thể sử dụng Regex.Split:

string[] tokens = Regex.Split(input, @"\r?\n|\r");

Chỉnh sửa: được thêm |\rvào tài khoản cho các đầu cuối dòng Mac (cũ hơn).


Điều này sẽ không hoạt động trên các tệp văn bản kiểu OS X, vì chúng chỉ sử dụng \rlàm kết thúc dòng.
Konrad Rudolph

2
@Konrad Rudolph: AFAIK, '\ r' đã được sử dụng trên các hệ thống MacOS rất cũ và gần như không bao giờ gặp phải nữa. Nhưng nếu OP cần tính đến nó (hoặc nếu tôi nhầm), thì regex có thể dễ dàng được mở rộng để tính đến nó tất nhiên: \ r? \ N | \ r
Bart Kiers

@Bart: Tôi không nghĩ bạn nhầm, nhưng tôi đã nhiều lần gặp phải tất cả các kết thúc dòng có thể trong sự nghiệp là một lập trình viên.
Konrad Rudolph

@Konrad, có lẽ bạn đúng. An toàn tốt hơn xin lỗi, tôi đoán.
Bart Kiers

1
@ MegaMan: Điều đó sẽ mất các dòng trống, ví dụ \ n \ n.
Mike Rosoft

9

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());

2
NewLine có thể là '\ n' và văn bản đầu vào có thể chứa "\ n \ r".
Konstantin Spirin

4

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


Tôi tự hỏi liệu điều này có phải là do bạn không thực sự kiểm tra kết quả của điều tra viên, và do đó nó không được thực thi. Thật không may, tôi quá lười để kiểm tra.
James Holwell

Vâng, nó thực sự là !! Khi bạn thêm .ToList () vào cả hai cuộc gọi, giải pháp StringReader thực sự chậm hơn! Trên máy của tôi là 6,74 giây so với 5,10
JCH2k

Điều đó có ý nghĩa. Tôi vẫn thích phương pháp này vì nó cho phép tôi có được các dòng không đồng bộ.
orad

Có lẽ bạn nên xóa tiêu đề "giải pháp tốt hơn" cho câu trả lời khác của mình và chỉnh sửa tiêu đề này ...
JCH2k

4
string[] lines = input.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

2

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();

1
    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();
    }

1

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 \u000Amáy Mac đơn và rất cũ sử dụng một \u000Dký 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:

  • mỗi và mọi nhân vật CR hoặc LF nên bỏ qua dòng tiếp theo NGOẠI TRỪ ...
  • ... Nếu một CR ngay lập tức được theo sau bởi LF ( \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)
  • Dòng cuối cùng phải được trả về ngay cả khi nó không có CR và LF.

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 StringReaderthể 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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.