Java NIO FileChannel与FileOutputstream的性能/实用性


169

我试图弄清楚,当我们使用nio FileChannel与常规的FileInputStream/FileOuputStream方式将文件读写到文件系统时,性能(或优势)是否存在任何差异。我观察到,在我的机器上,两者的性能都相同,而且FileChannel速度也慢了很多倍。请问您可以比较这两种方法的更多详细信息。这是我使用的代码,正在测试的文件在左右350MB。如果我不考虑随机访问或其他此类高级功能,是否可以将基于NIO的类用于文件I / O是一个好选择吗?

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/ transferFrom在复制文件时更为常规。不管使用哪种技术都不会使您的硬盘驱动器更快或更慢,尽管我猜测如果一次读取小块数据并导致磁头花费大量时间寻找,可能会出现问题。
汤姆·哈特芬

1
(您没有提到您使用的是哪个操作系统,或者哪个JRE供应商和版本。)
Tom Hawtin-更新时间:

糟糕,我将FC10与Sun JDK6一起使用。
克谢夫2009年

Answers:


202

我对大型文件的体验java.nio比更快java.io确实更快。 例如在> 250%的范围内。也就是说,我正在消除明显的瓶颈,我认为这可能会影响您的微基准测试。调查的潜在领域:

缓冲区大小。 您基本上拥有的算法是

  • 从磁盘复制到缓冲区
  • 从缓冲区复制到磁盘

我自己的经验是,此缓冲区大小已经可以调整。我为应用程序的一部分分配了4KB,为另一部分分配了256KB。我怀疑您的代码正在遭受如此大的缓冲区。使用1KB,2KB,4KB,8KB,16KB,32KB和64KB的缓冲区运行一些基准测试以向自己证明。

不要执行读写同一磁盘的Java基准测试。

如果这样做,那么您实际上是在对磁盘进行基准测试,而不是对Java进行基准测试。我还建议,如果您的CPU不忙,那么您可能会遇到其他瓶颈。

如果不需要,请不要使用缓冲区。

如果目标是其他磁盘或NIC,为什么还要复制到内存?对于较大的文件,确保的延迟是不平凡的。

就像其他人所说的那样,请使用FileChannel.transferTo()FileChannel.transferFrom()。这里的主要优点是,JVM使用操作系统对DMA(直接内存访问)的访问(如果存在)。(这取决于实现方式,但是在通用CPU上使用现代的Sun和IBM版本是不错的选择。) 发生的情况是数据直接往返于磁盘,总线,再到目的地……绕过任何电路RAM或CPU。

我日夜不停地工作的Web应用程序非常繁忙。我也做了微观基准和现实基准。结果在我的博客上,看看:

使用生产数据和环境

微观基准易于失真。如果可以的话,请根据预期的负载,在预期的硬件上,按照计划做的工作来收集数据。

我的基准是可靠且可靠的,因为它们是在生产系统,强大的系统,有负载的系统以及日志中收集的。 不是我的笔记本电脑的7200 RPM 2.5英寸SATA驱动器,而当我密切关注JVM在硬盘上工作时。

你在做什么?这很重要。


@Stu Thompson-感谢您的帖子。因为我正在研究同一主题,所以我碰到了您的答案。我试图了解nio向Java程序员公开的操作系统改进。其中有两个-DMA和内存映射文件。您是否遇到过更多此类改进?PS-您的博客链接已损坏。
Andy Dufresne 2013年

我的博客目前处于@AndyDufresne状态,本周晚些时候将处于上升状态-在迁移过程中。
斯图汤普森


1
仅将文件从一个目录复制到另一个目录怎么样?(每个不同的磁盘驱动器)
Deckard


38

如果您要比较的是文件复制的性能,那么对于通道测试,您应该这样做:

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();

这不会比将自己从一个频道缓冲到另一个频道要慢,并且可能会大大加快速度。根据Javadocs:

许多操作系统可以直接将字节从文件系统高速缓存传输到目标通道,而无需实际复制它们。


7

根据我的测试(Win7 64位,6GB RAM,Java6),NIO transferFrom仅对于小文件而言很快,而对大文件来说则非常慢。NIO数据缓冲区翻转始终优于标准IO。

  • 复制1000x2MB

    1. NIO(transferFrom)〜2300毫秒
    2. NIO(直接数据缓冲区5000b翻转)〜3500ms
    3. 标准IO(缓冲区5000b)〜6000ms
  • 复制100x20mb

    1. NIO(直接数据缓冲区5000b翻转)〜4000ms
    2. NIO(transferFrom)〜5000毫秒
    3. 标准IO(缓冲区5000b)〜6500ms
  • 复制1x1000mb

    1. NIO(直接datababuffer 5000b翻转)〜4500s
    2. 标准IO(缓冲区5000b)〜7000ms
    3. NIO(transferFrom)〜8000毫秒

transferTo()方法适用于文件块;并不是要用作高级文件复制方法: 如何在Windows XP中复制大文件?


6

回答问题的“有用性”部分:

使用FileChannelover的一个相当微妙的陷阱FileOutputStream是,从处于中断状态的线程执行其任何阻塞操作(例如read()write())都会导致该通道突然关闭。java.nio.channels.ClosedByInterruptException

现在,如果将FileChannel用于线程的任何内容作为线程主要功能的一部分,则这可能是一件好事,并且在设计时已将其考虑在内。

但是,如果由某些辅助功能(例如记录功能)使用,它也可能令人讨厌。例如,如果日志记录函数碰巧被也被中断的线程调用,则您会发现日志记录输出突然关闭。

不幸的是,这是如此微妙,因为不考虑这一点会导致影响写入完整性的错误。[1] [2]


3

我测试了FileInputStream与FileChannel解码base64编码文件的性能。以我的经验,我测试了相当大的文件,传统的io总是比nio快一点。

由于与io相关的类中的同步开销,FileChannel在早期版本的jvm中可能具有优势,但是现代的jvm非常擅长删除不需要的锁。


2

如果您未使用transferTo功能或非阻塞功能,您将不会注意到传统IO和NIO(2)之间的差异,因为传统IO会映射到NIO。

但是,如果您可以使用诸如transferFrom / To之类的NIO功能,或者想使用Buffers,那么当然要走NIO。


0

我的经验是,NIO处理小文件要快得多。但是,对于大文件,FileInputStream / FileOutputStream更快。


4
你搞混了吗?我自己的经验是,java.nio使用更大的文件比更快java.io,而不是更小。
斯图·汤普森

不,我的经验是相反的。java.nio只要文件足够小以映射到内存,它就会很快。如果变得更大(200 MB及更多),java.io则速度更快。
tangens

哇。与我完全相反。请注意,您不一定需要映射文件即可读取文件-可以从中读取文件FileChannel.read()。不仅有一种使用读取文件的方法java.nio
斯图·汤普森

2
@tangens您检查了吗?
sinedsem
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.