Cách nhanh nhất để đọc từng dòng tệp văn bản là gì?


319

Tôi muốn đọc một dòng tệp văn bản theo dòng. Tôi muốn biết liệu tôi có làm việc đó hiệu quả nhất có thể trong phạm vi .NET C # hay không.

Đây là những gì tôi đang cố gắng cho đến nay:

var filestream = new System.IO.FileStream(textFilePath,
                                          System.IO.FileMode.Open,
                                          System.IO.FileAccess.Read,
                                          System.IO.FileShare.ReadWrite);
var file = new System.IO.StreamReader(filestream, System.Text.Encoding.UTF8, true, 128);

while ((lineOfText = file.ReadLine()) != null)
{
    //Do something with the lineOfText
}

7
Ý Fastestbạn là từ quan điểm hiệu suất hay phát triển?
sll

1
Điều này sẽ khóa tập tin trong suốt thời gian của phương thức. Bạn có thể sử dụng File.ReadAllLines thành một mảng sau đó xử lý mảng.
Kell

17
BTW, kèm theo filestream = new FileStreamtrong using()tuyên bố để tránh các vấn đề gây phiền nhiễu có thể với khóa tập tin xử lý
SLL

Liên quan đến việc bao gồm FileStream đang sử dụng câu lệnh (), hãy xem StackOverflow về phương thức được đề xuất: StackOverflow bằng cách sử dụng bộ
đọc luồng

Tôi nghĩ ReadToEnd () nhanh hơn.
Dan Gifford

Câu trả lời:


315

Để tìm cách nhanh nhất để đọc từng dòng tệp, bạn sẽ phải thực hiện một số điểm chuẩn. Tôi đã thực hiện một số thử nghiệm nhỏ trên máy tính của mình nhưng bạn không thể ngờ rằng kết quả của tôi áp dụng cho môi trường của bạn.

Sử dụng StreamReader.ReadLine

Đây là cơ bản phương pháp của bạn. Vì một số lý do, bạn đặt kích thước bộ đệm thành giá trị nhỏ nhất có thể (128). Tăng điều này sẽ nói chung tăng hiệu suất. Kích thước mặc định là 1.024 và các lựa chọn tốt khác là 512 (kích thước cung trong Windows) hoặc 4.096 (kích thước cụm trong NTFS). Bạn sẽ phải chạy một điểm chuẩn để xác định kích thước bộ đệm tối ưu. Một bộ đệm lớn hơn - nếu không nhanh hơn - ít nhất là không chậm hơn một bộ đệm nhỏ hơn.

const Int32 BufferSize = 128;
using (var fileStream = File.OpenRead(fileName))
  using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) {
    String line;
    while ((line = streamReader.ReadLine()) != null)
      // Process line
  }

Hàm FileStreamtạo cho phép bạn chỉ định FileOptions . Ví dụ: nếu bạn đang đọc một tệp lớn liên tục từ đầu đến cuối, bạn có thể được hưởng lợi từ FileOptions.SequentialScan. Một lần nữa, điểm chuẩn là điều tốt nhất bạn có thể làm.

Sử dụng File.ReadLines

Điều này rất giống với giải pháp của riêng bạn ngoại trừ việc nó được thực hiện bằng cách sử dụng StreamReaderkích thước bộ đệm cố định là 1.024. Trên máy tính của tôi, kết quả này có hiệu suất tốt hơn một chút so với mã của bạn với kích thước bộ đệm là 128. Tuy nhiên, bạn có thể tăng hiệu suất tương tự bằng cách sử dụng kích thước bộ đệm lớn hơn. Phương pháp này được thực hiện bằng cách sử dụng khối lặp và không tiêu thụ bộ nhớ cho tất cả các dòng.

var lines = File.ReadLines(fileName);
foreach (var line in lines)
  // Process line

Sử dụng File.Read ALLLines

Điều này rất giống với phương thức trước ngoại trừ phương thức này phát triển một danh sách các chuỗi được sử dụng để tạo mảng các dòng được trả về để yêu cầu bộ nhớ cao hơn. Tuy nhiên, nó trả về String[]và không IEnumerable<String>cho phép bạn truy cập ngẫu nhiên các dòng.

var lines = File.ReadAllLines(fileName);
for (var i = 0; i < lines.Length; i += 1) {
  var line = lines[i];
  // Process line
}

Sử dụng String.Split

Phương pháp này chậm hơn đáng kể, ít nhất là trên các tệp lớn (được thử nghiệm trên tệp 511 KB), có thể do cách String.Splittriển khai. Nó cũng phân bổ một mảng cho tất cả các dòng tăng bộ nhớ cần thiết so với giải pháp của bạn.

using (var streamReader = File.OpenText(fileName)) {
  var lines = streamReader.ReadToEnd().Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
  foreach (var line in lines)
    // Process line
}

Đề nghị của tôi là sử dụng File.ReadLinesvì nó sạch sẽ và hiệu quả. Nếu bạn yêu cầu các tùy chọn chia sẻ đặc biệt (ví dụ: bạn sử dụng FileShare.ReadWrite), bạn có thể sử dụng mã của riêng mình nhưng bạn nên tăng kích thước bộ đệm.


1
Cảm ơn vì điều này - việc bạn đưa vào tham số kích thước bộ đệm trên hàm tạo của StreamReader thực sự hữu ích. Tôi đang phát trực tuyến từ API S3 của Amazon và sử dụng kích thước bộ đệm phù hợp sẽ tăng tốc đáng kể kết hợp với ReadLine ().
Richard K.

Tôi không hiểu Về lý thuyết, phần lớn thời gian dành cho việc đọc tệp sẽ là thời gian tìm kiếm trên đĩa và chi phí quản lý các luồng điều khiển, giống như những gì bạn làm với File.ReadLines. File.ReadLines, mặt khác, được cho là đọc mọi thứ của tệp vào bộ nhớ trong một lần. Làm thế nào nó có thể tồi tệ hơn trong hiệu suất?
h9uest

2
Tôi không thể nói về hiệu suất tốc độ nhưng có một điều chắc chắn: nó còn tệ hơn nhiều về mức tiêu thụ bộ nhớ. Nếu bạn phải xử lý các tệp rất lớn (ví dụ GB), điều này rất quan trọng. Thậm chí nhiều hơn nếu nó có nghĩa là nó phải trao đổi bộ nhớ. Về mặt tốc độ, bạn có thể thêm rằng ReadAllLine cần đọc TẤT CẢ các dòng TRƯỚC KHI trả lại kết quả xử lý trì hoãn. Trong một số tình huống, IMPRESSION của tốc độ quan trọng hơn tốc độ thô.
bkqc

Nếu bạn đọc luồng dưới dạng mảng byte Nó sẽ đọc tệp nhanh hơn từ 20% ~ 80% (từ các thử nghiệm tôi đã làm). Những gì bạn cần là lấy mảng byte và chuyển đổi nó thành chuỗi. Đó là cách tôi đã làm: Để đọc sử dụng stream.Read () Bạn có thể tạo một vòng lặp để làm cho nó đọc thành từng đoạn. Sau khi nối toàn bộ nội dung vào một mảng byte (sử dụng System.Buffer.BlockCopy ), bạn sẽ cần chuyển đổi các byte thành chuỗi: Encoding.Default.GetString (byteContent, 0, byteContent.Ldrops - 1) .Split (chuỗi mới [ ] {"\ r \ n", "\ r", "\ n"}, StringSplitOptions.None);
Kim Lage

200

Nếu bạn đang sử dụng .NET 4, chỉ cần sử dụng File.ReadLinestất cả cho bạn. Tôi nghi ngờ đó là nhiều giống như bạn, ngoại trừ nó cũng có thể sử dụng FileOptions.SequentialScanvà một bộ đệm lớn hơn (128 dường như rất nhỏ).


Một lợi ích khác của ReadLines()nó là lười biếng nên hoạt động tốt với LINQ.
stt106

35

Mặc dù File.ReadAllLines()là một trong những cách đơn giản nhất để đọc tệp, nhưng nó cũng là một trong những cách chậm nhất.

Nếu bạn chỉ muốn đọc các dòng trong một tệp mà không làm gì nhiều, theo các điểm chuẩn này , cách nhanh nhất để đọc tệp là phương pháp cũ:

using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
               //do minimal amount of work here
        }
}

Tuy nhiên, nếu bạn phải làm rất nhiều với mỗi dòng, thì bài viết này kết luận rằng cách tốt nhất là sau đây (và sẽ nhanh hơn khi phân bổ trước một chuỗi [] nếu bạn biết bạn sẽ đọc bao nhiêu dòng):

AllLines = new string[MAX]; //only allocate memory here

using (StreamReader sr = File.OpenText(fileName))
{
        int x = 0;
        while (!sr.EndOfStream)
        {
               AllLines[x] = sr.ReadLine();
               x += 1;
        }
} //Finished. Close the file

//Now parallel process each line in the file
Parallel.For(0, AllLines.Length, x =>
{
    DoYourStuff(AllLines[x]); //do your work here
});


5

Có một chủ đề hay về vấn đề này trong câu hỏi Stack Overflow Có phải 'lợi nhuận hoàn trả' chậm hơn so với lợi nhuận của "trường học cũ" không?.

Nó nói rằng:

ReadAllLines tải tất cả các dòng vào bộ nhớ và trả về một chuỗi []. Tất cả đều tốt và tốt nếu tập tin nhỏ. Nếu tệp lớn hơn sẽ phù hợp với bộ nhớ, bạn sẽ hết bộ nhớ.

ReadLines, mặt khác, sử dụng lợi nhuận lợi nhuận để trả về một dòng tại một thời điểm. Với nó, bạn có thể đọc bất kỳ tập tin kích thước. Nó không tải toàn bộ tập tin vào bộ nhớ.

Giả sử bạn muốn tìm dòng đầu tiên có chứa từ "foo", rồi thoát. Sử dụng ReadAllLines, bạn sẽ phải đọc toàn bộ tệp vào bộ nhớ, ngay cả khi "foo" xảy ra trên dòng đầu tiên. Với ReadLines, bạn chỉ đọc một dòng. Cái nào sẽ nhanh hơn?


4

Nếu kích thước tệp không lớn, thì sẽ nhanh hơn để đọc toàn bộ tệp và tách nó sau đó

var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                              StringSplitOptions.RemoveEmptyEntries);

6
File.ReadAllLines()
jgauffin

@jgauffin Tôi không biết đằng sau việc triển khai tệp.ReadAlllines () nhưng tôi nghĩ rằng nó có bộ đệm hạn chế và bộ đệm fileReadtoEnd nên lớn hơn, do đó, số lượng truy cập vào tệp sẽ bị giảm theo cách này và thực hiện chuỗi. kích thước tệp trường hợp không lớn là nhanh hơn nhiều truy cập vào tệp.
Saeed Amiri

Tôi nghi ngờ rằng File.ReadAllLinescó một kích thước bộ đệm cố định kể từ khi kích thước tập tin được biết đến.
jgauffin

1
@jgauffin: Trong .NET 4.0 File.ReadAllLinestạo một danh sách và thêm vào danh sách này trong một vòng lặp bằng cách sử dụng StreamReader.ReadLine(với sự phân bổ tiềm năng của mảng bên dưới). Phương pháp này sử dụng kích thước bộ đệm mặc định là 1024. Tránh StreamReader.ReadToEndphần phân tích cú pháp dòng và kích thước bộ đệm có thể được đặt trong hàm tạo nếu muốn.
Martin Liversage

Sẽ rất hữu ích khi định nghĩa "LỚN" liên quan đến kích thước tệp.
Paul

2

Nếu bạn có đủ bộ nhớ, tôi đã tìm thấy một số hiệu suất tăng bằng cách đọc toàn bộ tệp vào luồng bộ nhớ , sau đó mở trình đọc luồng trên đó để đọc các dòng. Miễn là bạn thực sự có kế hoạch đọc toàn bộ tập tin, điều này có thể mang lại một số cải tiến.


1
File.ReadAllLinesCó vẻ là một lựa chọn tốt hơn sau đó.
jgauffin

2

Bạn không thể nhận được bất kỳ nhanh hơn nếu bạn muốn sử dụng API hiện có để đọc các dòng. Nhưng đọc các phần lớn hơn và tìm thủ công từng dòng mới trong bộ đệm đọc có thể sẽ nhanh hơn.

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.