Java NIO FileChannel与FileOutputstream性能/实用性

我试图找出当我们使用nio FileChannel与正常的FileInputStream/FileOuputStream来读写文件到文件系统时,性能(或优势)是否有任何区别。 我观察到,在我的机器上都执行在同一级别,也多次FileChannel方式是慢的。 请问我能比较这两种方法了解更多细节。 这里是我使用的代码,我正在testing的文件大约是350MB 。 如果我不在随机访问或其他这样的高级function,是使用基于NIO的文件I / O类的一个很好的select?

 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"); } } 

我对更大的文件大小的经验是java.niojava.io快。 速度更快 像在> 250%的范围内。 这就是说,我正在消除明显的瓶颈,我build议你的微基准可能会受到影响。 潜在的调查领域:

缓冲区大小。 你基本上拥有的algorithm是

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

我自己的经验是,这个缓冲区大小已经成熟调整。 我已经为我的应用程序的一部分解决了4KB,另一部分解决了256KB。 我怀疑你的代码正在遭受这么大的缓冲区。 使用1KB,2KB,4KB,8KB,16KB,32KB和64KB的缓冲区运行一些基准testing。

不要执行读取和写入同一磁盘的Java基准testing。

如果你这样做,那么你真的是基准磁盘,而不是Java。 我还build议,如果你的CPU不忙,那么你可能遇到了一些其他的瓶颈。

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

如果您的目标是另一个磁盘或NIC,为什么要复制到内存? 对于较大的文件,所引起的延迟是不重要的。

像其他人所说,使用FileChannel.transferTo()FileChannel.transferFrom() 。 这里的关键优势是JVM使用操作系统对DMA( 直接存储器访问 )的访问(如果存在)。 (这是依赖于实现的,但是通用CPU上现代的Sun和IBM版本是很好的select。)数据直接进入/从光盘传输到总线,然后到达目的地……绕过任何电路RAM或CPU。

我日夜工作的networking应用程序非常繁重。 我也做过微型基准testing和真实世界基准testing。 结果在我的博客上,有一个看看:

  • 真实世界的性能指标:java.io与java.nio
  • 真实世界的性能指标:java.io与java.nio(The Sequel)

使用生产数据和环境

微基准很容易失真。 如果可以的话,努力从你期望的硬件上准确地收集你想要的数据。

我的基准是坚实可靠的,因为它们发生在一个生产系统,一个健壮系统,一个负载系统,聚集在日志中。 不是我的笔记本的7200转2.5“SATA驱动器,而我强烈观看JVM工作我的硬盘。

你在跑什么? 这很重要。

如果你想比较的东西是文件复制的性能,那么对于频道testing,你应该这样做:

 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:

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

基于我的testing(Win7 64bit,6GB内存,Java6),NIO transferFrom只能处理较小的文件,在较大的文件上变得非常慢。 NIO databuffer翻转总是胜过标准的IO。

  • 复制1000x2MB

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

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

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

transferTo()方法在文件的块上工作; 不是作为高级文件复制方法: 如何在Windows XP中复制大文件?

我testing了FileInputStream与FileChannel解码base64编码文件的性能。 在我的实验中,我testing了相当大的文件,而传统的io总比nio快一点。

FileChannel可能在jvm的早期版本中具有优势,因为在几个io相关的类中同步开销,但是现代的jvm在去除不需要的锁上非常好。

如果您没有使用transferTofunction或非阻塞function,则不会注意到传统IO和NIO(2)之间的区别,因为传统IO映射到NIO。

但是,如果您可以使用TransferFrom / To之类的NIOfunction,或者想要使用缓冲区,那么NIO当然是最好的select。

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

通过FileOutputStream使用FileChannel一个相当微妙的问题是,从处于中断状态的线程执行任何阻塞操作(例如, read()write() )将导致通道突然closures,出现java.nio.channels.ClosedByInterruptException

现在,如果使用FileChannel作为线程主要function的一部分,这可能是一件好事,devise将此考虑在内。

但是,如果使用某些辅助function(如日志loggingfunction),也可能会讨厌。 例如,如果日志loggingfunction恰好被同样被中断的线程调用,则可以发现日志输出突然closures。

不幸的是,这非常微妙,因为不考虑这可能会导致影响写入完整性的错误。 [1] [2]

我的经验是,NIO用小文件要快得多。 但是当涉及到大文件FileInputStream / FileOutputStream要快得多。