2010-01-29 14 views
70

Tôi có nhiệm vụ đáng yêu để xử lý các tệp lớn đang được tải vào trình chỉnh sửa tập lệnh của ứng dụng (giống như VBA cho sản phẩm nội bộ của chúng tôi cho các macro nhanh). Hầu hết các tệp có dung lượng khoảng 300-400   KB. Nhưng khi họ vượt quá 100   MB, quy trình có một thời gian khó khăn (như bạn mong đợi).Đọc các tệp văn bản lớn có dòng trong C#

Điều gì xảy ra là tệp được đọc và đẩy vào RichTextBox sau đó được điều hướng - đừng lo lắng quá nhiều về phần này.

Các nhà phát triển người viết mã ban đầu chỉ đơn giản là sử dụng một StreamReader và làm

[Reader].ReadToEnd() 

mà có thể mất nhiều thời gian để hoàn thành.

Nhiệm vụ của tôi là chia nhỏ mã này, đọc nó thành các phần vào bộ đệm và hiển thị thanh tiến trình có tùy chọn hủy bỏ nó.

Một số giả thiết:

  • Hầu hết các tập tin sẽ được 30-40   MB
  • Nội dung của tập tin là văn bản (không nhị phân), một số là định dạng Unix, một số là hệ điều hành DOS.
  • Khi nội dung được truy xuất, chúng tôi sẽ tìm ra trình kết thúc nào được sử dụng.
  • Không ai quan tâm một khi nó được tải thời gian cần để hiển thị trong hộp văn bản richtextbox. Nó chỉ là tải ban đầu của văn bản.

Bây giờ cho các câu hỏi sau:

  • Tôi có thể chỉ cần sử dụng StreamReader, sau đó kiểm tra tài sản Chiều dài (vì thế ProgressMax) và ban hành một đọc cho một kích thước bộ đệm và lặp thông qua trong một thời gian vòng lặp KHI bên trong một nhân viên nền, do đó, nó không chặn các chủ đề giao diện người dùng chính? Sau đó quay trở lại trình xây dựng chuỗi thành chuỗi chính sau khi hoàn tất.
  • Nội dung sẽ đi đến một StringBuilder. thế nào tôi có thể khởi tạo StringBuilder với kích thước của luồng nếu độ dài có sẵn?

Đây có phải là những ý tưởng hay không? Tôi đã có một vài vấn đề trong quá khứ với việc đọc nội dung từ các luồng, bởi vì nó sẽ luôn luôn bỏ lỡ vài byte cuối cùng hoặc một cái gì đó, nhưng tôi sẽ hỏi một câu hỏi nếu đây là trường hợp.

+26

Tập lệnh tập lệnh 30-40MB? Thánh cá thu! Tôi ghét phải viết mã xem lại ... – dthorpe

+0

Chỉ là một vài dòng mã. Xem thư viện này tôi đang sử dụng để đọc các tập tin lớn hơn 25GB và nhiều hơn nữa. https://github.com/Agenty/FileReader/ – Vicky

Trả lời

6

Sử dụng nhân viên nền và chỉ đọc một số dòng giới hạn. Đọc thêm chỉ khi người dùng cuộn.

Và cố gắng không bao giờ sử dụng ReadToEnd(). Đó là một trong những chức năng mà bạn nghĩ "tại sao họ lại làm nó?"; đó là một helper script kiddies' mà đi tốt với những điều nhỏ nhặt, nhưng như bạn thấy, nó hút cho các tập tin lớn ...

Những kẻ nói cho bạn để sử dụng cần StringBuilder để đọc MSDN thường xuyên hơn:

Performance cân nhắc
Các phương thức Concat và AppendFormat đều ghép nối dữ liệu mới với đối tượng String hoặc StringBuilder hiện có. Một hoạt động nối đối tượng String luôn tạo một đối tượng mới từ chuỗi hiện có và dữ liệu mới.Một đối tượng StringBuilder duy trì một bộ đệm để thích ứng với dữ liệu mới. Dữ liệu mới được nối vào cuối bộ đệm nếu phòng có sẵn; nếu không, một bộ đệm mới, lớn hơn được cấp phát, dữ liệu từ bộ đệm ban đầu được sao chép vào bộ đệm mới, sau đó dữ liệu mới được nối vào bộ đệm mới. Hiệu suất của hoạt động nối cho đối tượng String hoặc StringBuilder phụ thuộc vào tần suất phân bổ bộ nhớ xảy ra.
Một hoạt động nối chuỗi luôn phân bổ bộ nhớ, trong khi thao tác ghép nối StringBuilder chỉ cấp phát bộ nhớ nếu bộ đệm đối tượng StringBuilder quá nhỏ để chứa dữ liệu mới. Do đó, lớp String là thích hợp hơn cho một hoạt động nối nếu một số lượng cố định của các đối tượng String được nối. Trong trường hợp đó, các phép nối nối riêng lẻ thậm chí có thể được kết hợp thành một thao tác đơn lẻ bởi trình biên dịch. Một đối tượng StringBuilder thích hợp hơn cho một phép nối nối nếu một số chuỗi tùy ý được nối; ví dụ, nếu một vòng lặp nối một chuỗi ngẫu nhiên các chuỗi đầu vào của người dùng.

Điều đó có nghĩa là phân bổ bộ nhớ của ổ đĩa cứng, hoạt động như bộ nhớ RAM, nhưng ổ đĩa cứng rất chậm.

Tùy chọn StringBuilder có vẻ tốt cho những người sử dụng hệ thống với tư cách là người dùng đơn, nhưng khi bạn có hai hoặc nhiều người dùng đọc tệp lớn cùng lúc, bạn gặp sự cố.

+0

xa các bạn rất nhanh! không may vì cách hoạt động của macro mà toàn bộ luồng cần được tải. Như tôi đã đề cập, đừng lo lắng về phần richtext. Tải ban đầu của nó, chúng tôi đang muốn cải thiện. –

+0

để bạn có thể làm việc trong các phần, đọc dòng X đầu tiên, áp dụng macro, đọc dòng X thứ hai, áp dụng macro ... nếu bạn giải thích macro này làm gì, chúng tôi có thể giúp bạn chính xác hơn – Tufo

2

Bạn có thể nên sử dụng các tệp ánh xạ bộ nhớ xử lý here .. Hỗ trợ tệp ánh xạ bộ nhớ sẽ nằm trong .NET 4 (Tôi nghĩ ... Tôi nghe nói thông qua người khác nói về nó) wrapper trong đó sử dụng p/gọi để thực hiện công việc tương tự ..

Edit: Xem ở đây trên MSDN cho cách thức hoạt động, đây là blog entry chỉ ra làm thế nào nó được thực hiện trong .NET sắp tới 4 khi nói ra như phát hành. Các liên kết tôi đã đưa ra trước đó là một wrapper xung quanh pinvoke để đạt được điều này. Bạn có thể ánh xạ toàn bộ tệp vào bộ nhớ và xem nó như một cửa sổ trượt khi cuộn qua tệp.

4

Hãy xem đoạn mã sau. Bạn đã đề cập đến Most files will be 30-40 MB. Này tuyên bố để đọc 180   MB trong 1,4 giây trên bộ xử lý Intel Quad Core:

private int _bufferSize = 16384; 

private void ReadFile(string filename) 
{ 
    StringBuilder stringBuilder = new StringBuilder(); 
    FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read); 

    using (StreamReader streamReader = new StreamReader(fileStream)) 
    { 
     char[] fileContents = new char[_bufferSize]; 
     int charsRead = streamReader.Read(fileContents, 0, _bufferSize); 

     // Can't do much with 0 bytes 
     if (charsRead == 0) 
      throw new Exception("File is 0 bytes"); 

     while (charsRead > 0) 
     { 
      stringBuilder.Append(fileContents); 
      charsRead = streamReader.Read(fileContents, 0, _bufferSize); 
     } 
    } 
} 

Original Article

+3

Các loại kiểm tra này nổi tiếng không đáng tin cậy. Bạn sẽ đọc dữ liệu từ bộ nhớ cache của tệp hệ thống khi bạn lặp lại kiểm tra. Đó là ít nhất một đơn đặt hàng của cường độ nhanh hơn so với một thử nghiệm thực tế mà đọc dữ liệu ra khỏi đĩa. Tệp 180 MB không thể mất ít hơn 3 giây. Khởi động lại máy của bạn, chạy thử nghiệm một lần cho số thực. –

+6

dòng stringBuilder.Append có khả năng nguy hiểm, bạn cần thay thế nó bằng stringBuilder.Append (fileContents, 0, charsRead); để đảm bảo bạn không thêm đầy đủ 1024 ký tự ngay cả khi luồng đã kết thúc trước đó. –

5

này nên là đủ để giúp bạn bắt đầu.

class Program 
{   
    static void Main(String[] args) 
    { 
     const int bufferSize = 1024; 

     var sb = new StringBuilder(); 
     var buffer = new Char[bufferSize]; 
     var length = 0L; 
     var totalRead = 0L; 
     var count = bufferSize; 

     using (var sr = new StreamReader(@"C:\Temp\file.txt")) 
     { 
      length = sr.BaseStream.Length;    
      while (count > 0) 
      {      
       count = sr.Read(buffer, 0, bufferSize); 
       sb.Append(buffer, 0, count); 
       totalRead += count; 
      }     
     } 

     Console.ReadKey(); 
    } 
} 
+3

Tôi sẽ di chuyển "var buffer = new char [1024]" ra khỏi vòng lặp: nó không cần thiết để tạo ra một bộ đệm mới mỗi lần. Chỉ cần đặt nó trước khi "trong khi (đếm> 0)". –

14

Bạn nói rằng bạn đã được yêu cầu hiển thị thanh tiến trình trong khi tệp lớn đang tải. Có phải vì người dùng thực sự muốn xem chính xác% tải tệp hay chỉ vì họ muốn có phản hồi trực quan rằng có điều gì đó đang xảy ra?

Nếu điều sau là đúng, thì giải pháp trở nên đơn giản hơn nhiều. Chỉ cần thực hiện reader.ReadToEnd() trên một chuỗi nền và hiển thị thanh tiến trình loại marquee thay vì một thanh tiến trình thích hợp.

Tôi nâng cao điểm này vì theo kinh nghiệm của tôi, điều này thường xảy ra. Khi bạn đang viết một chương trình xử lý dữ liệu, thì người dùng chắc chắn sẽ quan tâm đến một con số hoàn chỉnh%, nhưng đối với bản cập nhật giao diện người dùng đơn giản nhưng chậm, họ có nhiều khả năng chỉ muốn biết rằng máy tính không bị hỏng.:-)

+2

Nhưng người dùng có thể hủy cuộc gọi ReadToEnd không? –

+0

@Tim, được phát hiện tốt. Trong trường hợp đó, chúng ta quay lại vòng lặp 'StreamReader'. Tuy nhiên, nó sẽ vẫn đơn giản hơn vì không cần đọc trước để tính toán chỉ báo tiến trình. –

1

Một iterator có thể là hoàn hảo cho công việc này:

public static IEnumerable<int> LoadFileWithProgress(string filename, StringBuilder stringData) 
{ 
    const int charBufferSize = 4096; 
    using (FileStream fs = File.OpenRead(filename)) 
    { 
     using (BinaryReader br = new BinaryReader(fs)) 
     { 
      long length = fs.Length; 
      int numberOfChunks = Convert.ToInt32((length/charBufferSize)) + 1; 
      double iter = 100/Convert.ToDouble(numberOfChunks); 
      double currentIter = 0; 
      yield return Convert.ToInt32(currentIter); 
      while (true) 
      { 
       char[] buffer = br.ReadChars(charBufferSize); 
       if (buffer.Length == 0) break; 
       stringData.Append(buffer); 
       currentIter += iter; 
       yield return Convert.ToInt32(currentIter); 
      } 
     } 
    } 
} 

Bạn có thể gọi nó bằng cách sử dụng sau đây:

string filename = "C:\\myfile.txt"; 
StringBuilder sb = new StringBuilder(); 
foreach (int progress in LoadFileWithProgress(filename, sb)) 
{ 
    // Update your progress counter here! 
} 
string fileData = sb.ToString(); 

Khi tập tin được tải, các iterator sẽ trở lại số tiến trình từ 0 đến 100, bạn có thể sử dụng số này để cập nhật thanh tiến trình của mình. Khi vòng lặp kết thúc, StringBuilder sẽ chứa nội dung của tệp văn bản.

Ngoài ra, vì bạn muốn văn bản, chúng tôi chỉ có thể sử dụng BinaryReader để đọc các ký tự, đảm bảo bộ đệm của bạn xếp hàng chính xác khi đọc bất kỳ ký tự nhiều byte nào (UTF-8, UTF-16, v.v.).

Điều này được thực hiện mà không cần sử dụng các tác vụ nền, chủ đề hoặc máy trạng thái tùy chỉnh phức tạp.

142

Bạn có thể cải thiện tốc độ đọc bằng cách sử dụng một BufferedStream, như thế này:

using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 
using (BufferedStream bs = new BufferedStream(fs)) 
using (StreamReader sr = new StreamReader(bs)) 
{ 
    string line; 
    while ((line = sr.ReadLine()) != null) 
    { 

    } 
} 

tháng 3 năm 2013 CẬP NHẬT

Gần đây tôi đã viết mã cho việc đọc và xử lý (tìm kiếm văn bản trong) 1   GB -những tệp văn bản (lớn hơn nhiều so với các tệp liên quan ở đây) và đạt được hiệu suất đáng kể bằng cách sử dụng mẫu nhà sản xuất/người tiêu dùng. Tác vụ của nhà sản xuất đọc trong các dòng văn bản bằng cách sử dụng BufferedStream và giao cho họ một nhiệm vụ tiêu dùng riêng biệt đã thực hiện tìm kiếm.

Tôi đã sử dụng cơ hội này để tìm hiểu TPL Dataflow, rất phù hợp để nhanh chóng viết mã mẫu này.

Tại sao BufferedStream nhanh

Một bộ đệm là một khối lượng byte trong bộ nhớ sử dụng để dữ liệu bộ nhớ cache, do đó làm giảm số lượng các cuộc gọi đến các hệ điều hành. Bộ đệm cải thiện hiệu năng đọc và ghi. Một bộ đệm có thể được sử dụng để đọc hoặc viết, nhưng không bao giờ đồng thời cả hai. Các phương thức Read và Write của BufferedStream tự động duy trì bộ đệm.

tháng 12 năm 2014 UPDATE: Mileage của bạn có thể Vary

Căn cứ ý kiến, FileStream nên sử dụng một BufferedStream nội bộ. Vào thời điểm câu trả lời này được cung cấp lần đầu tiên, tôi đã đo được hiệu suất đáng kể bằng cách thêm BufferedStream. Lúc đó tôi đang nhắm mục tiêu .NET 3.x trên nền tảng 32 bit. Hôm nay, nhắm mục tiêu .NET 4.5 trên nền tảng 64 bit, tôi không thấy bất kỳ cải tiến nào.

liên quan

tôi tình cờ gặp một trường hợp trình chiếu một lớn, tạo ra tập tin CSV vào dòng Phản hồi từ một hành động ASP.Net MVC là rất chậm. Thêm một BufferedStream cải thiện hiệu suất bằng 100x trong trường hợp này.Để biết thêm chi thấy Unbuffered Output Very Slow

+10

Dude, BufferedStream tạo ra tất cả sự khác biệt. +1 :) – Marcus

+0

Nhanh hơn nhiều so với streamReader.ReadLine chỉ ... cảm ơn rất nhiều Eric.Can bạn cũng giải thích lý do tại sao nó nhanh hơn rất nhiều/hoặc trỏ tôi đến tài nguyên nơi tôi có thể đọc về nó. Cảm ơn trước. – techExplorer

+1

Có một chi phí để yêu cầu dữ liệu từ một hệ thống phụ IO.Trong trường hợp quay đĩa, bạn có thể phải đợi đĩa quay vào vị trí để đọc đoạn dữ liệu tiếp theo hoặc tệ hơn, đợi đầu đĩa di chuyển. Trong khi SSD không có bộ phận cơ khí để làm chậm mọi thứ, vẫn có chi phí hoạt động cho mỗi IO để truy cập chúng. Luồng đệm được đọc nhiều hơn những gì mà StreamReader yêu cầu, giảm số lượng cuộc gọi đến hệ điều hành và cuối cùng là số lượng yêu cầu IO riêng biệt. –

12

Nếu bạn đọc performance and benchmark stats on this website, bạn sẽ thấy rằng cách nhanh nhất để đọc (vì đọc, viết, và chế biến đều khác nhau) một file văn bản là đoạn mã sau đây:

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

Tất cả khoảng 9 phương pháp khác nhau được đánh dấu là băng ghế dự bị, nhưng có vẻ như đi ra ngoài phần lớn thời gian, ngay cả khi thực hiện trình đọc được đệm như những người đọc khác đã đề cập.

+1

Điều này làm việc tốt cho việc tách rời một tệp postgres 19GB để dịch nó thành cú pháp sql trong nhiều tệp. Cảm ơn anh chàng postgres người không bao giờ thực hiện các thông số của tôi một cách chính xác./thở dài –

+0

Sự khác biệt về hiệu suất ở đây dường như trả cho các tệp lớn, lớn hơn 150MB (bạn cũng nên sử dụng 'StringBuilder' để tải chúng vào bộ nhớ, tải nhanh hơn vì nó không tạo chuỗi mới mỗi khi bạn thêm ký tự) – b729sefc

7

Đối với tệp nhị phân, cách nhanh nhất để đọc chúng tôi đã tìm thấy là điều này.

MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(file); 
MemoryMappedViewStream mms = mmf.CreateViewStream(); 
using (BinaryReader b = new BinaryReader(mms)) 
{ 
} 

Trong bài kiểm tra của tôi nhanh hơn hàng trăm lần.

+0

Bạn có bằng chứng nào về điều này không? Tại sao nên sử dụng OP này trên bất kỳ câu trả lời nào khác? Hãy đào sâu hơn một chút và cung cấp chi tiết hơn một chút –

0

Tôi biết câu hỏi này khá cũ nhưng tôi đã tìm thấy nó vào một ngày khác và đã thử nghiệm đề xuất cho MemoryMappedFile và đây là phương pháp nhanh nhất. Một so sánh là đọc một tập tin 34516.939 dòng 345MB thông qua một phương pháp readline mất 12+ giờ trên máy tính của tôi trong khi thực hiện tải cùng và đọc qua MemoryMappedFile mất 3 giây.

0

Tất cả câu trả lời tuyệt vời! tuy nhiên, đối với ai đó đang tìm kiếm câu trả lời, những câu trả lời này có vẻ hơi không hoàn chỉnh.

Là một chuỗi chuẩn chỉ có thể có kích thước X, 2Gb đến 4Gb tùy thuộc vào cấu hình của bạn, những câu trả lời này không thực sự đáp ứng được câu hỏi của OP. Một phương pháp là làm việc với Danh sách các chuỗi:

List<string> Words = new List<string>(); 

using (StreamReader sr = new StreamReader(@"C:\Temp\file.txt")) 
{ 

string line = string.Empty; 

while ((line = sr.ReadLine()) != null) 
{ 
    Words.Add(line); 
} 
} 

Một số có thể muốn Mã hóa và chia dòng khi xử lý. Danh sách chuỗi bây giờ có thể chứa khối lượng văn bản rất lớn.