2009-04-30 23 views
6

Tôi đang sử dụng C# để đọc tệp CSV văn bản thuần túy ~ 120 MB. Ban đầu tôi đã thực hiện phân tích cú pháp bằng cách đọc nó theo từng dòng, nhưng gần đây đã xác định rằng đọc toàn bộ nội dung tệp vào bộ nhớ đầu tiên nhanh hơn nhiều lần. Việc phân tích cú pháp đã khá chậm vì CSV có dấu phẩy được nhúng bên trong dấu ngoặc kép, có nghĩa là tôi phải sử dụng phân tách regex. Đây là người duy nhất tôi đã tìm thấy rằng làm việc đáng tin cậy:.NET System.OutOfMemoryException trên String.Split() của tệp CSV 120 MB

string[] fields = Regex.Split(line, 
@",(?!(?<=(?:^|,)\s*\x22(?:[^\x22]|\x22\x22|\\\x22)*,) 
(?:[^\x22]|\x22\x22|\\\x22)*\x22\s*(?:,|$))"); 
// from http://regexlib.com/REDetails.aspx?regexp_id=621 

Để làm phân tích cú pháp sau khi đọc toàn bộ nội dung vào bộ nhớ, tôi làm một tách chuỗi trên kí tự xuống dòng để có được một mảng chứa mỗi dòng. Tuy nhiên, khi tôi làm điều này trên tập tin 120 MB, tôi nhận được một System.OutOfMemoryException. Tại sao nó hết bộ nhớ một cách nhanh chóng khi máy tính của tôi có RAM 4 GB? Có cách nào tốt hơn để nhanh chóng phân tích cú pháp CSV phức tạp không?

Trả lời

7

Bạn có thể nhận ngoại lệ OutOfMemoryException cho bất kỳ kích thước phân bổ cơ bản nào. Khi bạn cấp phát một phần bộ nhớ, bạn thực sự yêu cầu một bộ nhớ liên tục của kích thước được yêu cầu. Nếu điều đó không thể được vinh danh, bạn sẽ thấy một OutOfMemoryException. Bạn cũng nên lưu ý rằng trừ khi bạn đang chạy Windows 64 bit, RAM 4 GB của bạn được chia thành không gian hạt nhân 2 GB và dung lượng người dùng 2 GB, vì vậy ứng dụng .NET của bạn không thể truy cập nhiều hơn 2 GB cho mỗi mặc định.

Khi thực hiện thao tác chuỗi trong .NET, bạn có nguy cơ tạo ra nhiều chuỗi tạm thời do thực tế là các chuỗi .NET không thay đổi được. Do đó bạn có thể thấy mức sử dụng bộ nhớ tăng lên đáng kể.

+0

chuỗi là con khốn của khoa học máy tính. một điều ác cần thiết, nhưng tôi vẫn muốn ai đó sẽ tìm ra một cách tốt hơn! –

4

Bạn không thể phân bổ một đối tượng duy nhất với nhiều bộ nhớ liền kề đó, cũng như bạn không mong đợi để có thể. Phát trực tuyến là cách thông thường để thực hiện việc này, nhưng bạn nói đúng là nó có thể chậm hơn (mặc dù tôi không nghĩ rằng nó thường khá chậm hơn nhiều.)

Như một sự thỏa hiệp, bạn có thể thử đọc lớn hơn một phần của tệp (nhưng vẫn không phải là toàn bộ) cùng một lúc, với một hàm như StreamReader.ReadBlock() và xử lý từng phần một.

0

Bạn có thể thử dùng số CLR profiler để xác định mức sử dụng bộ nhớ thực tế của mình. Có thể có giới hạn bộ nhớ khác với RAM hệ thống của bạn. Ví dụ nếu đây là một ứng dụng IIS, bộ nhớ của bạn bị giới hạn bởi các nhóm ứng dụng.

Với thông tin tiểu sử này, bạn có thể thấy rằng bạn cần phải sử dụng kỹ thuật có thể mở rộng hơn như phát trực tiếp tệp CSV mà bạn đã thử ban đầu.

5

Nếu bạn có toàn bộ tệp được đọc thành chuỗi, có lẽ bạn nên sử dụng StringReader.

StringReader reader = new StringReader(fileContents); 
string line; 
while ((line = reader.ReadLine()) != null) { 
    // Process line 
} 

Điều này nên được thực hiện giống như phát trực tiếp từ tệp có sự khác biệt về nội dung trong bộ nhớ.

Chỉnh sửa sau khi thử nghiệm

thử trên với một tập tin 140MB nơi chế biến bao gồm incrementing biến chiều dài với line.Length. Việc này mất khoảng 1,6 giây trên máy tính của tôi. Sau này tôi đã thử những điều sau đây:

System.IO.StreamReader reader = new StreamReader("D:\\test.txt"); 
long length = 0; 
string line; 
while ((line = reader.ReadLine()) != null) 
    length += line.Length; 

Kết quả là khoảng 1 giây.

Tất nhiên số dặm của bạn có thể thay đổi, đặc biệt nếu bạn đang đọc từ ổ đĩa mạng hoặc quá trình xử lý của bạn mất nhiều thời gian để ổ đĩa cứng tìm nơi khác. Nhưng cũng nếu bạn đang sử dụng FileStream để đọc tệp và bạn không phải đệm. StreamReader cung cấp khả năng đệm giúp tăng cường khả năng đọc.

+0

Đây là một câu trả lời khá hay nếu anh ta thực sự có thể đọc các tập tin vào một chuỗi ở nơi đầu tiên, mà nó có vẻ như anh ấy có thể, ít nhất là vào lúc này. Tôi sẽ không ngạc nhiên nếu nhiều máy thất bại ngay lập tức cố gắng tải lên một tập tin 120MB (hoặc thất bại đôi khi và làm việc lần khác.) – mquander

8

Đừng cuộn trình phân tích cú pháp của riêng bạn trừ khi bạn phải. Tôi đã có may mắn với cái này:

A Fast CSV Reader

Nếu không có gì khác bạn có thể nhìn dưới mui xe và xem cách người khác làm điều đó.

+1

+1 như tôi đã sử dụng để phân tích cú pháp các tập tin CSV lớn. – Wayne

+1

+1 từ tôi nữa. Theo kinh nghiệm của tôi, trình đọc CSV của Sébastien Lorion hiệu quả, linh hoạt và mạnh mẽ. Nó sẽ nhai thông qua một tập tin 120MB trong thời gian không. – LukeH

0

Bạn sắp hết bộ nhớ trên ngăn xếp chứ không phải đống.

Bạn có thể thử tính lại ứng dụng của mình sao cho bạn đang xử lý dữ liệu đầu vào trong "khối dữ liệu" có thể quản lý hơn là xử lý 120MB cùng một lúc.

+0

Chuỗi được phân bổ trên heap, không phải là ngăn xếp. Chỉ có các nguyên thủy của int/byte/double/etc được cấp phát trên stack imr. –

+0

@không chắc chắn: bạn đã đúng. tuy nhiên, có nhiều trường hợp không rõ ràng trong đó ngăn xếp chương trình có thể lấp đầy. Cho rằng hệ thống trong câu hỏi có bộ nhớ vật lý dồi dào, tôi cho rằng đây có lẽ là một trong những trường hợp đó. =) – Garrett

+0

Ngăn xếp lấp đầy kết quả trong một StackOverflowException, không phải là một OutOfMemoryException; sau này luôn được sử dụng để chỉ ra không đủ bộ nhớ trên GC Heap. –

1

Như các áp phích khác cho biết, OutOfMemory là bởi vì nó không thể tìm thấy một bộ nhớ tiếp giáp với kích thước được yêu cầu.

Tuy nhiên, bạn nói rằng thực hiện dòng phân tích cú pháp theo dòng nhanh hơn vài lần so với tất cả cùng một lúc và sau đó thực hiện quá trình xử lý của bạn. Điều này chỉ có ý nghĩa nếu bạn đang theo đuổi cách tiếp cận ngây thơ làm chặn đọc, ví dụ (trong mã giả):

while(! file.eof()) 
{ 
    string line = file.ReadLine(); 
    ProcessLine(line); 
} 

Bạn thay vào đó nên sử dụng trực tuyến, nơi luồng của bạn được lấp đầy bằng Write() các cuộc gọi từ một thay thế thread đang đọc tệp, do đó tệp đọc không bị chặn bởi bất kỳ ProcessLine() nào của bạn và ngược lại. Điều đó phải ngang bằng với hiệu suất đọc toàn bộ tệp cùng một lúc và sau đó thực hiện quá trình xử lý của bạn.

+0

Bạn có thể đưa ra một ví dụ về cách tiếp cận đa luồng không? Tôi đã làm điều đó một cách ngây thơ, và bây giờ tôi hiểu tại sao điều đó có thể là một vấn đề lớn. –

+0

. Net có tích hợp đọc và ghi tập tin không đồng bộ, một điểm khởi đầu tốt là lời gọi BeginRead(). Các kết quả của Google sau đây có nhiều ví dụ: http://www.google.com/search?q=.net+asynchronous+file –

0

Tôi đồng ý với hầu hết mọi người ở đây, bạn cần sử dụng phát trực tuyến.

Tôi không biết liệu có ai đã nói cho đến giờ không, nhưng bạn nên xem xét phương pháp exstention.

Và tôi biết, chắc chắn, tay xuống, tách kỹ thuật CSV tốt nhất trên .NET/CLR là this one

kỹ thuật đó tạo cho tôi + 10GB XML sản lượng từ đầu vào CSV, bao gồm các bộ lọc đầu vào exstensive và tất cả, nhanh hơn bất cứ điều gì khác tôi đã nhìn thấy.

+0

Ồ, cũng phải, Phát trực tuyến> Đệm trong RAM của bạn bất kể điều gì. Hãy suy nghĩ về nó, nếu bạn có 4GIG, và bạn tải lên 2GIG đầu vào, chỉ cần thời gian tải và sự rung chuyển của các trang con định vị lại VM của bạn và kích thước lớn của bảng trang sẽ chỉ chiếm bộ nhớ cache CPU của bạn, v.v. .. trong/ngoài của một nhỏ, dễ quản lý không gian làm việc giữ bộ nhớ cache của bạn "nóng" và tất cả thời gian CPU của bạn là dành cho nhiệm vụ trong tầm tay, không phải là sự fluxuation lớn trong tải hệ thống ... – RandomNickName42

0

Bạn nên đọc đoạn văn bản vào bộ đệm và làm việc trên đó. Sau đó đọc một đoạn khác và vân vân.

Có nhiều thư viện ở đó sẽ thực hiện việc này hiệu quả cho bạn. Tôi duy trì một cái gọi là CsvHelper. Có rất nhiều trường hợp cạnh mà bạn cần xử lý, chẳng hạn như khi dấu phẩy hoặc dòng kết thúc ở giữa một trường.

Các vấn đề liên quan