Làm thế nào để sao chép các tệp dữ liệu lớn theo từng dòng?


9

Tôi có một CSVtệp 35GB . Tôi muốn đọc từng dòng và viết dòng đó ra một CSV mới nếu nó phù hợp với một điều kiện.

try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("source.csv"))) {
    try (BufferedReader br = Files.newBufferedReader(Paths.get("target.csv"))) {
        br.lines().parallel()
            .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
            .forEach(line -> {
                writer.write(line + "\n");
        });
    }
}

Điều này mất khoảng. 7 phút. Có thể tăng tốc quá trình đó hơn nữa?


1
Có, bạn có thể thử không làm điều này từ Java mà là thực hiện trực tiếp từ Linux / Windows / của bạn. hệ điều hành. Java được diễn giải và sẽ luôn có một chi phí sử dụng. Bên cạnh đó, không, tôi không có cách rõ ràng nào để tăng tốc và 7 phút cho 35 GB có vẻ hợp lý với tôi.
Tim Biegeleisen

1
Có lẽ loại bỏ parallellàm cho nó nhanh hơn? Và không phải điều đó xáo trộn các dòng xung quanh?
Thilo

1
Tạo BufferedWriterchính bạn, sử dụng hàm tạo cho phép bạn đặt kích thước bộ đệm. Có thể kích thước bộ đệm lớn hơn (hoặc nhỏ hơn) sẽ tạo ra sự khác biệt. Tôi sẽ cố gắng khớp BufferedWriterkích thước bộ đệm với kích thước bộ đệm của hệ điều hành máy chủ.
Abra

5
@TimBiegeleisen: "Java được diễn giải" là sai lệch ở mức tốt nhất và hầu như luôn luôn sai. Đúng, đối với một số tối ưu hóa, bạn có thể cần phải rời khỏi thế giới JVM, nhưng thực hiện việc này nhanh hơn trong Java chắc chắn là có thể thực hiện được.
Joachim Sauer

1
Bạn nên lập hồ sơ cho ứng dụng để xem nếu có bất kỳ điểm nóng nào mà bạn có thể làm gì đó. Bạn sẽ không thể làm được gì nhiều về IO thô (bộ đệm byte 8192 mặc định không tệ lắm, vì có các kích thước cung, v.v.), nhưng có thể có những điều xảy ra (bên trong) mà bạn có thể có thể làm việc với.
Kayaman

Câu trả lời:


4

Nếu đó là một tùy chọn, bạn có thể sử dụng GZipInputStream / GZipOutputStream để giảm thiểu I / O đĩa.

Files.newBufferedReader / Writer sử dụng kích thước bộ đệm mặc định, 8 KB tôi tin. Bạn có thể thử một bộ đệm lớn hơn.

Chuyển đổi thành Chuỗi, Unicode, làm chậm lại (và sử dụng gấp đôi bộ nhớ). UTF-8 được sử dụng không đơn giản như StandardCharsets.ISO_8859_1.

Tốt nhất là nếu bạn có thể làm việc với các byte cho hầu hết các phần và chỉ cho các trường CSV cụ thể chuyển đổi chúng thành Chuỗi.

Một tập tin ánh xạ bộ nhớ có thể là thích hợp nhất. Tính song song có thể được sử dụng bởi các phạm vi tệp, nhổ tệp.

try (FileChannel sourceChannel = new RandomAccessFile("source.csv","r").getChannel(); ...
MappedByteBuffer buf = sourceChannel.map(...);

Điều này sẽ trở thành một chút nhiều mã, nhận được các dòng ngay (byte)'\n', nhưng không quá phức tạp.


Vấn đề với việc đọc byte là trong thế giới thực, tôi phải đánh giá phần đầu của dòng, chuỗi con trên một ký tự cụ thể và chỉ ghi phần còn lại của dòng vào tệp outfile. Vì vậy, tôi có lẽ không thể đọc các dòng như byte chỉ?
viên

Tôi chỉ thử nghiệm GZipInputStream + GZipOutputStreamđầy đủ inmemory trên một ramdisk. Hiệu suất kém hơn nhiều ...
viên

1
Trên Gzip: nó không phải là đĩa chậm. Có, byte là một tùy chọn: dòng mới, dấu phẩy, tab, dấu chấm phẩy tất cả có thể được xử lý dưới dạng byte và sẽ nhanh hơn đáng kể so với Chuỗi. Byte dưới dạng UTF-8 đến UTF-16 char thành Chuỗi thành UTF-8 thành byte.
Eggen

1
Chỉ cần ánh xạ các phần khác nhau của tập tin theo thời gian. Khi bạn đạt đến giới hạn, chỉ cần tạo một cái mới MappedByteBuffertừ vị trí đã biết cuối cùng ( FileChannel.mapmất nhiều thời gian).
Joachim Sauer

1
Năm 2019, không có nhu cầu sử dụng new RandomAccessFile(…).getChannel(). Chỉ cần sử dụng FileChannel.open(…).
Holger

0

bạn có thể thử điều này:

try (BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile), 1024 * 1024 * 64)) {
  try (BufferedReader br = new BufferedReader(new FileReader(sourceFile), 1024 * 1024 * 64)) {

Tôi nghĩ rằng nó sẽ giúp bạn tiết kiệm một hoặc hai phút. kiểm tra có thể được thực hiện trên máy của tôi trong khoảng 4 phút bằng cách chỉ định kích thước bộ đệm.

nó có thể nhanh hơn không? thử cái này:

final char[] cbuf = new char[1024 * 1024 * 128];

try (Writer writer = new FileWriter(targetFile)) {
  try (Reader br = new FileReader(sourceFile)) {
    int cnt = 0;
    while ((cnt = br.read(cbuf)) > 0) {
      // add your code to process/split the buffer into lines.
      writer.write(cbuf, 0, cnt);
    }
  }
}

Điều này sẽ giúp bạn tiết kiệm ba hoặc bốn phút.

Nếu điều đó vẫn chưa đủ. (Lý do tôi đoán bạn đặt câu hỏi có lẽ là bạn cần thực hiện nhiệm vụ nhiều lần). nếu bạn muốn hoàn thành nó trong một phút hoặc thậm chí vài giây. sau đó bạn nên xử lý dữ liệu và lưu nó vào db, sau đó xử lý tác vụ bằng nhiều máy chủ.


Ví dụ cuối cùng của bạn: làm thế nào tôi có thể đánh giá cbufnội dung và chỉ viết các phần ra? Và tôi có phải thiết lập lại bộ đệm khi đã đầy không? (làm thế nào tôi có thể biết bộ đệm đã đầy?)
viên

0

Nhờ tất cả các đề xuất của bạn, nhanh nhất tôi đã đưa ra là trao đổi với người viết BufferedOutputStream, điều này đã cải thiện khoảng 25%:

   try (BufferedReader reader = Files.newBufferedReader(Paths.get("sample.csv"))) {
        try (BufferedOutputStream writer = new BufferedOutputStream(Files.newOutputStream(Paths.get("target.csv")), 1024 * 16)) {
            reader.lines().parallel()
                    .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
                    .forEach(line -> {
                        writer.write((line + "\n").getBytes());
                    });
        }
    }

Vẫn là hiệu suất BufferedReadertốt hơn BufferedInputStreamtrong trường hợp của tôi.

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.