2011-01-18 44 views
13

Tôi hiện đang viết một cái gì đó mà cần phải xử lý các tập tin văn bản rất lớn (một vài GiB ít nhất). Điều cần thiết ở đây (và điều này là cố định) là:Cách xử lý tệp văn bản rất lớn?

  • CSV-based, sau RFC 4180 với ngoại lệ của dòng nhúng phá vỡ
  • ngẫu nhiên truy cập đọc đến dòng này, mặc dù hầu hết từng dòng và gần cuối
  • nối các dòng ở cuối
  • (thay đổi dòng). Rõ ràng là các cuộc gọi cho phần còn lại của tệp được viết lại, nó cũng hiếm, vì vậy không đặc biệt quan trọng tại thời điểm này

Kích thước của tệp cấm lưu giữ nó hoàn toàn trong bộ nhớ (cũng không được mong muốn) thêm các thay đổi sẽ được duy trì càng sớm càng tốt).

Tôi đã nghĩ đến việc sử dụng vùng được ánh xạ bộ nhớ làm cửa sổ vào tệp được di chuyển xung quanh nếu một dòng nằm ngoài phạm vi của nó được yêu cầu. Tất nhiên, ở giai đoạn đó tôi vẫn không có trừu tượng ở trên mức byte. Để thực sự làm việc với nội dung tôi có một số CharsetDecoder cho tôi một số CharBuffer. Bây giờ vấn đề là, tôi có thể xử lý các dòng văn bản có lẽ chỉ tốt trong CharBuffer, nhưng tôi cũng cần phải biết bù đắp byte của dòng đó trong tệp (để giữ bộ nhớ cache của các chỉ mục dòng và bù đắp để tôi không có để quét tệp từ đầu một lần nữa để tìm một dòng cụ thể).

Có cách nào để ánh xạ độ lệch trong một số CharBuffer thành bù trừ trong kết hợp ByteBuffer không? Nó rõ ràng tầm thường với ASCII hoặc ISO-8859- *, ít hơn với UTF-8 và với ISO 2022 hoặc BOCU-1 mọi thứ sẽ trở nên xấu xí (không phải là tôi thực sự mong đợi hai sau, nhưng UTF-8 nên là mặc định ở đây - và vẫn đặt ra vấn đề).

Tôi đoán tôi có thể chỉ chuyển đổi một phần của CharBuffer thành byte một lần nữa và sử dụng độ dài. Hoặc là nó hoạt động hoặc tôi gặp vấn đề với dấu phụ trong trường hợp này tôi có thể ủy quyền sử dụng NFC hoặc NFD để đảm bảo rằng văn bản luôn được mã hóa rõ ràng.

Tuy nhiên, tôi tự hỏi liệu đó có phải là cách để đến đây không. Có lựa chọn nào tốt hơn không?

ETA: Một số trả lời cho câu hỏi phổ biến và góp ý ở đây:

Đây là một lưu trữ dữ liệu cho mô phỏng chạy, dự định được một sự thay thế nhỏ ish địa phương đến một cơ sở dữ liệu toàn diện. Chúng tôi cũng có các backend cơ sở dữ liệu và chúng được sử dụng, nhưng đối với các trường hợp chúng không có sẵn hoặc không áp dụng, chúng tôi muốn điều này.

Tôi cũng chỉ hỗ trợ một tập hợp con của CSV (không có ngắt dòng được nhúng), nhưng điều đó vẫn ổn. Các điểm có vấn đề ở đây là khá nhiều mà tôi không thể dự đoán bao lâu các dòng được và do đó cần phải tạo ra một bản đồ thô của tập tin.

Đối với những gì tôi đã nêu ở trên: Vấn đề tôi đang cân nhắc là tôi có thể dễ dàng xác định kết thúc của một dòng ở cấp độ ký tự (U + 000D + U + 000A), nhưng tôi không muốn giả định rằng điều này trông giống như 0A 0D ở cấp độ byte (đã bị lỗi đối với UTF-16, ví dụ: ở đây là 0D 00 0A 00 hoặc 00 0D 00 0A). Suy nghĩ của tôi là tôi có thể làm cho ký tự mã hóa có thể thay đổi được bằng các chi tiết mã hóa cứng mà tôi hiện đang sử dụng. Nhưng tôi đoán tôi chỉ có thể dính vào UTF-8 và ăn tất cả mọi thứ khác. Cảm thấy sai, bằng cách nào đó, mặc dù.

+1

trông giống như tái tạo trang bộ nhớ đệm của máy chủ sql. Đây là kịch bản làm việc hay bất cứ thứ gì khác ngăn cản bạn sử dụng cơ sở dữ liệu nhúng? – Osw

+0

Bất kể giải pháp thực tế nào, bạn sẽ cần phải xây dựng ít nhất một chỉ mục một phần hoặc nếu bạn kiểm soát hoàn toàn tệp và nội dung của tệp, bạn có thể cân nhắc sử dụng độ dài dòng cố định. – biziclop

Trả lời

2

Nó rất khó để duy trì một 1: lập bản đồ 1 giữa một chuỗi các Java chars (có hiệu quả UTF-16) và byte có thể là bất cứ điều gì tùy thuộc vào mã hóa tập tin của bạn.Ngay cả với UTF-8, ánh xạ "rõ ràng" của 1 byte đến 1 char chỉ hoạt động với ASCII. Cả UTF-16 lẫn UTF-8 đều đảm bảo rằng ký tự unicode có thể được lưu trữ trong một máy đơn lẻ char hoặc byte.

Tôi sẽ duy trì cửa sổ của mình vào tệp dưới dạng bộ đệm byte chứ không phải bộ đệm char. Sau đó, để tìm kết thúc dòng trong bộ đệm byte, tôi sẽ mã hóa chuỗi Java "\r\n" (hoặc có thể chỉ là "\n") dưới dạng chuỗi byte bằng cách sử dụng cùng một mã hóa với tệp. Tôi sẽ sử dụng chuỗi byte đó để tìm kiếm dòng kết thúc trong bộ đệm byte. Vị trí của một dòng kết thúc trong bộ đệm + bù đắp của bộ đệm từ đầu của tập tin bản đồ chính xác đến vị trí byte trong tập tin của dòng kết thúc.

Các dòng phụ thêm chỉ là trường hợp tìm cách kết thúc tệp và thêm các dòng mới của bạn. Thay đổi dòng là khó khăn hơn. Tôi nghĩ rằng tôi sẽ duy trì một danh sách hoặc bản đồ các vị trí byte của các dòng đã thay đổi và thay đổi là gì. Khi đã sẵn sàng để viết những thay đổi:

  1. sắp xếp danh sách các thay đổi bởi vị trí byte
  2. đọc các tập tin ban đầu lên đến sự thay đổi tiếp theo và ghi nó vào một tập tin tạm thời.
  3. ghi dòng đã thay đổi vào tệp tạm thời.
  4. bỏ qua dòng đã thay đổi trong tệp gốc.
  5. quay lại bước 2 trừ khi bạn đã đến cuối tệp gốc
  6. di chuyển tệp tạm thời lên tệp gốc.
+0

Hm, thực sự, tôi không nghĩ đến việc tìm kiếm ngắt dòng được mã hóa. Bằng cách nào đó các giải pháp rõ ràng đôi khi làm tôi khó chịu. Đối với việc bù đắp cho ánh xạ chỉ mục hàng: Đó là kế hoạch và cần thiết, thực sự. Một sự thay đổi trong một hàng duy nhất xâm nhập tất cả các hàng tiếp theo, tất nhiên, nhưng theo một cách có thể dự đoán được. Nhưng vì đây là một hoạt động rất hiếm, tôi có thể bỏ qua nó (bây giờ). – Joey

+0

Lưu ý bên: Ánh xạ bộ nhớ của tệp đã cung cấp cho tôi bộ đệm byte. Nhưng tôi không thể xử lý những byte như văn bản, trừ khi tôi giải mã chúng thành một bộ đệm char. Nhưng tôi thực sự có cả hai. Cảm ơn bạn đã đề xuất với việc tìm kiếm ngắt dòng được mã hóa; Tôi đang đi tuyến đường đó ngay bây giờ. – Joey

+0

@Joey: Vâng, tôi cần phải nhấn mạnh, phần quan trọng của mẹo là mã hóa ngắt dòng bằng cách sử dụng mã hóa tệp thay vì giải mã tệp (hoặc một phần của tệp). Làm thế nào bạn nhận được chuỗi byte của cửa sổ tập tin không phải là quan trọng, ngoại trừ, tất nhiên, bộ nhớ ánh xạ nên được nhanh hơn. – JeremyP

1

Bạn có thể chia tệp đó thành "tệp phụ" (tất nhiên bạn không được chia tệp đó trong một thẻ Utf-8)? Sau đó, bạn cần một số dữ liệu meta cho từng tệp phụ (tổng số ký tự và tổng số dòng).

Nếu bạn có điều này và "các tệp con" tương đối nhỏ để bạn luôn có thể tải một cách thỏa đáng thì việc xử lý trở nên dễ dàng.

Thậm chí việc chỉnh sửa trở nên dễ dàng, bởi vì bạn chỉ cần cập nhật "tệp con" và dữ liệu meta của nó.

Nếu bạn đặt nó vào cạnh: thì bạn có thể sử dụng cơ sở dữ liệu và lưu trữ một dòng cho mỗi hàng cơ sở dữ liệu. - Nếu đây là ý tưởng hay, phụ thuộc rất lớn vào trường hợp sử dụng của bạn.

+0

Đối với dữ liệu dựa trên dòng, bạn sẽ không muốn tách một dòng giữa các tệp. Bạn cũng có thể chia nhỏ một tệp hầu như, ghi lại cùng một thông tin với một khoảng trống trong tệp. Điều cực đoan là phải nhớ vị trí của mỗi dòng trong tệp. –

+0

@Peter Lawrey: tất nhiên nếu nó có thể chia tách chỉ ở ngắt dòng – Ralph

+0

Điều tôi đang làm ở đây là một tệp dựa trên CSV đơn giản được dùng như một thay thế cục bộ nhỏ cho cơ sở dữ liệu đầy đủ. Đó là một bồn rửa dữ liệu cho chạy mô phỏng và chúng tôi đã có cơ sở dữ liệu backends là tốt. Nhưng chúng không phải luôn luôn áp dụng hoặc thích hợp. – Joey

0

CharBuffer giả định tất cả các nhân vật là UTF-16 hoặc UCS-2 (có lẽ ai đó biết sự khác biệt)

Vấn đề sử dụng một định dạng văn bản thích hợp là bạn cần phải đọc từng byte để biết nơi mà các nhân vật n-th hoặc là dòng thứ n. Tôi sử dụng các tệp văn bản nhiều GB nhưng giả sử dữ liệu ASCII-7 và tôi chỉ đọc/ghi tuần tự.

Nếu bạn muốn truy cập ngẫu nhiên trên tệp văn bản chưa được lập chỉ mục, bạn không thể mong đợi nó có hiệu suất.

Nếu bạn sẵn sàng mua một máy chủ mới, bạn có thể nhận một gói với 24 GB với giá khoảng £ 1,800 và 64GB cho khoảng £ 4.200. Điều này sẽ cho phép bạn tải ngay cả các tệp nhiều GB vào bộ nhớ.

+0

UTF-16. UCS-2 không được sử dụng từ khá lâu (ngoại trừ trong các ứng dụng [sai] giả sử một ký tự Unicode dài hai byte). Và đó là lý do tại sao tôi có một bộ mã CharsetDecoder để lấy các ký tự Unicode từ bất kỳ tập tin nào trong tập tin. Nhưng rõ ràng là che khuất ánh xạ ký tự sang byte một chút. Và không, tôi không làm điều đó không được lập chỉ dẫn. Tôi sẽ nhớ sự bù đắp của các dòng được chọn để có một ý tưởng thô khi mỗi dòng nằm trong tệp (như được ghi chú trong câu hỏi). Đối với bộ nhớ, nó cũng chạy trên các hệ thống 32 bit. – Joey

+0

Nếu bạn biết nơi bắt đầu và kết thúc của mỗi dòng là. (bạn có thể tìm ra kết thúc từ nơi dòng tiếp theo bắt đầu), bạn có thể đọc được dòng (hoặc dòng nếu bạn cần nhiều hơn). Tuy nhiên, nếu bạn bị giới hạn bộ nhớ/phần cứng, bạn nên mong đợi mỗi truy cập ngẫu nhiên 9 ms (độ trễ của ổ đĩa điển hình). Trong trường hợp này, bạn làm thế nào trong phần mềm không quan trọng quá nhiều và bạn chỉ nên làm cho nó đơn giản và đáng tin cậy. –

+0

Vâng, vấn đề tôi nghĩ là tôi có thể dễ dàng xác định kết thúc của một dòng ở cấp độ ký tự (U + 000D + U + 000A), nhưng tôi không muốn giả định rằng nó trông giống như '0A 0D' ở cấp độ byte (không thành công cho UTF-16, ví dụ, nơi nó là '0D 00 0A 00' hoặc' 00 0D 00 0A'). Suy nghĩ của tôi là tôi có thể làm cho ký tự mã hóa có thể thay đổi được bằng các chi tiết mã hóa cứng mà tôi hiện đang sử dụng. Nhưng tôi đoán tôi chỉ có thể dính vào UTF-8 và ăn tất cả mọi thứ khác. Cảm thấy sai, bằng cách nào đó, mặc dù. – Joey

0

Nếu bạn đã cố định các đường rộng thì sử dụng RandomAccessFile có thể giải quyết được rất nhiều vấn đề của bạn. Tôi nhận thấy rằng các đường của bạn có thể là không phải là chiều rộng cố định, nhưng bạn có thể áp đặt giả tạo điều này bằng cách thêm một chỉ báo cuối dòng và sau đó là các đường đệm (ví dụ: với khoảng trắng).

Điều này rõ ràng hoạt động tốt nhất nếu tệp của bạn hiện có phân bố khá dài dòng và không có một số dòng rất dài. Nhược điểm là điều này sẽ làm tăng kích thước tệp của bạn một cách giả tạo.

+0

Định dạng là CSV. Không gian là một phần của các lĩnh vực vì vậy tôi không thể chỉ cần pad chúng. Cũng lưu trữ các chuỗi có độ dài không xác định là có vấn đề - nếu một chuỗi dài hơn xuất hiện, tôi có nên dán mọi dòng tôi đã viết trước đó không? – Joey

+0

@Joey Phương pháp dựa trên độ dài đường cố định nên sẽ phải có độ dài tối đa được áp đặt. – Qwerky

0
  • Tìm đầu dòng:

Stick với UTF-8 và \ n biểu thị sự kết thúc của dòng không phải là một vấn đề. Ngoài ra, bạn có thể cho phép UTF-16, và nhận ra dữ liệu: nó phải được trích dẫn (ví dụ), có N commans (dấu chấm phẩy) và một đầu của dòng. Có thể đọc tiêu đề để biết có bao nhiêu cột cấu trúc.

  • Chèn vào giữa file

có thể đạt được bằng cách đặt một số không gian vào cuối/đầu mỗi dòng.

  • dòng phụ thêm vào cuối

Đó là tầm thường chừng nào các tập tin bị khóa (như thay đổi nào nữa)

0

Trong trường hợp đếm cột cố định, tôi sẽ chia tệp một cách hợp lý và/hoặc thực tế thành các cột và triển khai một số trình bao bọc/bộ điều hợp cho các tác vụ IO và quản lý tệp.

0

Bạn có thể bắt đầu phân tích cú pháp một nơi nào đó gần vị trí bạn đang tìm kiếm?

Ý tưởng sẽ là các khoảng cách byte trong đó mã hóa ở trạng thái ban đầu (tức là nếu dữ liệu được mã hóa ISO-2022 thì vị trí này sẽ ở chế độ tương thích ASCII). Bất kỳ chỉ mục nào trong dữ liệu sẽ bao gồm một con trỏ vào bảng này cộng với bất kỳ thứ gì được yêu cầu để tìm hàng thực tế. Nếu bạn đặt các điểm khởi động lại sao cho mỗi điểm nằm giữa hai điểm phù hợp với cửa sổ mmap, thì bạn có thể bỏ qua mã kiểm tra/remap/khởi động lại từ lớp phân tích cú pháp và sử dụng trình phân tích cú pháp giả định rằng dữ liệu được ánh xạ tuần tự.

+0

Như đã nói trong câu hỏi, đây là kế hoạch và không chính xác vấn đề của tôi ngay bây giờ. – Joey

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