Java NIO FileChannel so với hiệu suất / tính hữu dụng của FileOutputstream


168

Tôi đang cố gắng tìm hiểu xem có sự khác biệt nào về hiệu suất (hoặc lợi thế) khi chúng ta sử dụng nio FileChannelso với bình thường FileInputStream/FileOuputStreamđể đọc và ghi tệp vào hệ thống tệp. Tôi quan sát thấy rằng trên máy của tôi cả hai thực hiện ở cùng một cấp độ, cũng nhiều lần FileChannelcách chậm hơn. Tôi có thể vui lòng biết thêm chi tiết so sánh hai phương pháp này. Đây là mã tôi đã sử dụng, tập tin mà tôi đang kiểm tra có ở xung quanh 350MB. Đây có phải là một lựa chọn tốt để sử dụng các lớp dựa trên NIO cho Tệp I / O không, nếu tôi không tìm kiếm quyền truy cập ngẫu nhiên hoặc các tính năng nâng cao khác như vậy?

package trialjavaprograms;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class JavaNIOTest {
    public static void main(String[] args) throws Exception {
        useNormalIO();
        useFileChannel();
    }

    private static void useNormalIO() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        InputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        byte[] buf = new byte[64 * 1024];
        int len = 0;
        while((len = is.read(buf)) != -1) {
            fos.write(buf, 0, len);
        }
        fos.flush();
        fos.close();
        is.close();
        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }

    private static void useFileChannel() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        FileInputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        FileChannel f = is.getChannel();
        FileChannel f2 = fos.getChannel();

        ByteBuffer buf = ByteBuffer.allocateDirect(64 * 1024);
        long len = 0;
        while((len = f.read(buf)) != -1) {
            buf.flip();
            f2.write(buf);
            buf.clear();
        }

        f2.close();
        f.close();

        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }
}

5
transferTo/ transferFromsẽ là thông thường hơn để sao chép các tập tin. Bất cứ kỹ thuật nào cũng không nên làm cho ổ cứng của bạn nhanh hơn hay chậm hơn, mặc dù tôi đoán có thể có vấn đề nếu nó đọc các đoạn nhỏ tại một thời điểm và khiến đầu mất nhiều thời gian tìm kiếm.
Tom Hawtin - tackline

1
(Bạn không đề cập đến hệ điều hành nào bạn đang sử dụng, hoặc nhà cung cấp và phiên bản JRE nào.)
Tom Hawtin - tackline

Rất tiếc, tôi đang sử dụng FC10 với Sun JDK6.
Keshav

Câu trả lời:


202

Kinh nghiệm của tôi với kích thước tệp lớn java.niohơn nhanh hơn java.io. Vững chắc nhanh hơn. Giống như trong phạm vi> 250%. Điều đó nói rằng, tôi đang loại bỏ các tắc nghẽn rõ ràng, mà tôi đề nghị điểm chuẩn vi mô của bạn có thể bị. Các lĩnh vực tiềm năng để điều tra:

Kích thước bộ đệm. Thuật toán bạn cơ bản có là

  • sao chép từ đĩa vào bộ đệm
  • sao chép từ bộ đệm vào đĩa

Kinh nghiệm của riêng tôi là kích thước bộ đệm này đã chín muồi để điều chỉnh. Tôi đã giải quyết trên 4KB cho một phần của ứng dụng của mình, 256KB cho một phần khác. Tôi nghi ngờ mã của bạn đang chịu đựng với một bộ đệm lớn như vậy. Chạy một số điểm chuẩn với bộ đệm 1KB, 2KB, 4KB, 8KB, 16KB, 32KB và 64KB để chứng minh điều đó với chính mình.

Không thực hiện các điểm chuẩn java đọc và ghi vào cùng một đĩa.

Nếu bạn làm như vậy, thì bạn thực sự đang đo điểm chuẩn cho đĩa chứ không phải Java. Tôi cũng đề nghị rằng nếu CPU của bạn không bận, thì có lẽ bạn đang gặp một số nút cổ chai khác.

Đừng sử dụng bộ đệm nếu bạn không cần.

Tại sao sao chép vào bộ nhớ nếu mục tiêu của bạn là một đĩa khác hoặc một NIC? Với các tệp lớn hơn, độ trễ phát sinh là không tầm thường.

Giống như đã nói, sử dụng FileChannel.transferTo()hay FileChannel.transferFrom(). Ưu điểm chính ở đây là JVM sử dụng quyền truy cập của HĐH vào DMA ( Truy cập bộ nhớ trực tiếp ), nếu có. (Điều này phụ thuộc vào việc triển khai, nhưng các phiên bản Sun và IBM hiện đại trên CPU có mục đích chung rất tốt.) Điều gì xảy ra là dữ liệu đi thẳng vào / từ đĩa, đến xe buýt, rồi đến đích ... bỏ qua bất kỳ mạch nào đi qua RAM hoặc CPU.

Ứng dụng web tôi đã dành cả ngày và đêm để làm việc rất nặng. Tôi cũng đã thực hiện các điểm chuẩn vi mô và điểm chuẩn trong thế giới thực. Và kết quả là trên blog của tôi, hãy xem:

Sử dụng dữ liệu sản xuất và môi trường

Điểm chuẩn vi mô dễ bị biến dạng. Nếu bạn có thể, hãy nỗ lực thu thập dữ liệu từ chính xác những gì bạn dự định làm, với tải bạn mong đợi, trên phần cứng bạn mong đợi.

Điểm chuẩn của tôi là vững chắc và đáng tin cậy bởi vì chúng diễn ra trên một hệ thống sản xuất, một hệ thống mạnh mẽ, một hệ thống đang tải, được tập hợp trong các bản ghi. Không phải ổ đĩa 7200 RPM 2.5 "của máy tính xách tay của tôi trong khi tôi xem mạnh mẽ khi JVM hoạt động với đĩa cứng của tôi.

Bạn đang chạy trên cái gì? Nó quan trọng.


@Stu Thompson - Cảm ơn bài viết của bạn. Tôi đã xem qua câu trả lời của bạn vì tôi đang nghiên cứu về cùng một chủ đề. Tôi đang cố gắng để hiểu các cải tiến hệ điều hành mà nio tiếp xúc với các lập trình viên java. Một vài trong số chúng là - DMA và các tập tin ánh xạ bộ nhớ. Bạn đã đi qua nhiều cải tiến như vậy? PS - Liên kết blog của bạn bị hỏng.
Andy Dufresne 17/03/13

@AndyDufresne blog của tôi hiện đang ngừng hoạt động, sẽ lên vào cuối tuần này - trong quá trình di chuyển nó.
Stu Thompson


1
Làm thế nào về việc chỉ sao chép các tập tin từ một thư mục khác? (Mỗi ổ đĩa khác nhau)
Deckard


38

Nếu điều bạn muốn so sánh là hiệu suất sao chép tệp, thì đối với kiểm tra kênh, bạn nên thực hiện việc này thay thế:

final FileInputStream inputStream = new FileInputStream(src);
final FileOutputStream outputStream = new FileOutputStream(dest);
final FileChannel inChannel = inputStream.getChannel();
final FileChannel outChannel = outputStream.getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
inputStream.close();
outputStream.close();

Điều này sẽ không chậm hơn so với việc tự đệm từ kênh này sang kênh khác và có khả năng sẽ nhanh hơn rất nhiều. Theo Javadocs:

Nhiều hệ điều hành có thể chuyển byte trực tiếp từ bộ đệm của hệ thống tệp sang kênh đích mà không thực sự sao chép chúng.


7

Dựa trên các thử nghiệm của tôi (Win7 64 bit, RAM 6GB, Java6), NIO transferFrom chỉ nhanh với các tệp nhỏ và trở nên rất chậm trên các tệp lớn hơn. NIO databuffer flip luôn vượt trội so với IO tiêu chuẩn.

  • Sao chép 1000x2MB

    1. NIO (chuyển từ) ~ 2300ms
    2. NIO (datababuffer trực tiếp 5000b lật) ~ 3500ms
    3. IO chuẩn (bộ đệm 5000b) ~ 6000ms
  • Sao chép 100x20mb

    1. NIO (lật dữ liệu trực tiếp 5000b lật) ~ 4000ms
    2. NIO (chuyển từ) ~ 5000ms
    3. IO chuẩn (bộ đệm 5000b) ~ 6500ms
  • Sao chép 1x1000mb

    1. NIO (lật dữ liệu trực tiếp 5000b lật) ~ 4500 giây
    2. IO chuẩn (bộ đệm 5000b) ~ 7000ms
    3. NIO (chuyển từ) ~ 8000ms

Phương thức transferTo () hoạt động trên các đoạn của tệp; không có ý định như một phương pháp sao chép tệp cấp cao: Làm cách nào để sao chép một tệp lớn trong Windows XP?


6

Trả lời phần "hữu ích" của câu hỏi:

Một Gotcha khá tinh tế của việc sử dụng FileChannelhơn FileOutputStreamlà thực hiện bất kỳ hoạt động ngăn chặn của nó (ví dụ read()hay write()) từ một chủ đề đó trong tình trạng bị gián đoạn sẽ khiến kênh để đóng đột ngột vớijava.nio.channels.ClosedByInterruptException .

Bây giờ, đây có thể là một điều tốt nếu bất cứ thứ gì FileChannelđược sử dụng là một phần của chức năng chính của luồng và thiết kế đã tính đến điều này.

Nhưng nó cũng có thể gây phiền toái nếu được sử dụng bởi một số tính năng phụ trợ như chức năng ghi nhật ký. Ví dụ: bạn có thể thấy đầu ra ghi nhật ký của mình đột ngột đóng nếu chức năng ghi nhật ký được gọi bởi một luồng cũng bị gián đoạn.

Thật không may, điều này rất tinh vi bởi vì không tính đến điều này có thể dẫn đến các lỗi ảnh hưởng đến tính toàn vẹn của ghi. [1] [2]


3

Tôi đã kiểm tra hiệu năng của FileInputStream so với FileChannel để giải mã các tệp được mã hóa base64. Trong các kinh nghiệm của tôi, tôi đã thử nghiệm tệp khá lớn và io truyền thống luôn nhanh hơn một chút so với nio.

FileChannel có thể đã có lợi thế trong các phiên bản trước của jvm do chi phí đồng bộ hóa trong một số lớp liên quan đến io, nhưng jvm hiện đại khá tốt trong việc loại bỏ các khóa không cần thiết.


2

Nếu bạn không sử dụng tính năng transferTo hoặc các tính năng không chặn, bạn sẽ không nhận thấy sự khác biệt giữa IO truyền thống và NIO (2) vì IO truyền thống ánh xạ sang NIO.

Nhưng nếu bạn có thể sử dụng các tính năng của NIO như transferFrom / To hoặc muốn sử dụng Bộ đệm, thì tất nhiên NIO là con đường để đi.


0

Kinh nghiệm của tôi là, NIO nhanh hơn nhiều với các tệp nhỏ. Nhưng khi nói đến các tệp lớn FileInputStream / FileOutputStream thì nhanh hơn nhiều.


4
Bạn đã có được hỗn hợp đó? Kinh nghiệm của riêng tôi java.niolà nhanh hơn với các tệp lớn hơn java.io, không nhỏ hơn.
Stu Thompson

Không, kinh nghiệm của tôi là cách khác. java.niolà nhanh miễn là tập tin đủ nhỏ để được ánh xạ vào bộ nhớ. Nếu nó lớn hơn (200 MB trở lên) java.iothì nhanh hơn.
tangens

Ồ Sự đối nghịch hoàn toàn với tôi. Lưu ý rằng bạn không nhất thiết phải ánh xạ một tệp để đọc nó - người ta có thể đọc từ FileChannel.read(). Không chỉ có một cách tiếp cận duy nhất để đọc tệp bằng cách sử dụng java.nio.
Stu Thompson

2
@tangens bạn đã kiểm tra cái này chưa?
sinedsem
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.