JavaNIO FileChannel 与 FileOutputstream 的性能/有用性

我试图弄清楚,当我们使用 nio FileChannel和普通的 FileInputStream/FileOuputStream来读写文件到文件系统时,性能(或优势)是否有任何区别。我观察到,在我的机器上,两者执行在相同的水平,也很多次的 FileChannel方式是较慢的。我可以知道更多的细节比较这两种方法。下面是我使用的代码,我正在测试的文件大约是 350MB。如果我没有考虑随机访问或其他这样的高级特性,那么对文件 I/O 使用基于 NIO 的类是一个好的选择吗?

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");
}
}
119962 次浏览

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

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

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 的说法:

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

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

FileChannel 在以前版本的 jvm 中可能有优势,因为在几个 IO 相关类中存在同步开销,但是现代的 jvm 非常擅长删除不需要的锁。

我的经验与较大的文件大小一直是,java.nio是快于 java.io绝对快。在 > 250% 的范围内。也就是说,我正在消除明显的瓶颈,我建议您的微基准可能会受到这些瓶颈的影响。调查的潜在领域:

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

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

我自己的经验是,这个缓冲区大小是 成熟来进行调优。我已经为我的应用程序的一部分设置了4KB,为另一部分设置了256KB。我怀疑您的代码有这么大的缓冲区。使用1KB、2KB、4KB、8KB、16KB、32KB 和64KB 的缓冲区运行一些基准测试,向自己证明这一点。

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

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

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

如果目标是另一个磁盘或网卡,为什么要复制到内存中?对于较大的文件,产生的延迟是非常重要的。

正如其他人所说,使用 FileChannel.transferTo()FileChannel.transferFrom()。这里的关键优势是,如果存在的话,JVM 使用操作系统对 DMA (直接存储器访问)的访问。发生的情况是数据直接进入/从光盘,到总线,然后到目的地... 绕过任何电路通过 RAM 或 CPU。

我日夜工作的网络应用程序非常 IO 重。我也做过微基准测试和现实世界的基准测试。结果都在我的博客上,看看吧:

使用生产数据和环境

微基准很容易被扭曲。如果可以的话,尽量按照计划在预期的硬件上以预期的负载收集数据。

我的基准测试是可靠的,因为它们发生在一个生产系统上,一个强大的系统,一个负载下的系统,以日志的形式收集。没有我的笔记本电脑的7200 RPM 2.5“ SATA 驱动器,而我紧张地看着 JVM 工作我的硬盘。

- 你在跑什么?-这很重要。

根据我的测试(Win764bit,6 GB RAM,Java6) ,NIO transFrom 只在小文件中运行快,在大文件中变得非常慢。NIO 数据缓冲区翻转总是优于标准 IO。

  • 复制1000x2MB

    1. NIO (转移自) ~ 2300ms
    2. NIO (直接数据缓冲区5000b 翻转) ~ 3500ms
    3. 标准 IO (缓冲区5000b) ~ 6000ms
  • 正在复制100x20mb

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

    1. NIO (直接数据缓冲区5000b 翻转) ~ 4500s
    2. 标准 IO (缓冲区5000b) ~ 7000ms
    3. NIO (转移自) ~ 8000ms

TransTo ()方法适用于文件块; 不是作为高级文件复制方法使用的: 如何在 WindowsXP 中复制大型文件?

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

但是如果您可以使用 NIO 特性,比如 Transfer From/To,或者希望使用 Buffers,那么 NIO 当然是可行的方法。

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

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

现在,如果使用 FileChannel的任何东西都是线程的主要功能的一部分,并且设计考虑到了这一点,那么这可能是一件好事。

但是,如果将其用于某些辅助特性(如日志记录函数) ,也会很麻烦。例如,如果某个线程正好调用了日志记录函数,而该线程又被中断,那么您可能会发现日志记录输出突然关闭。

不幸的是,这一点非常微妙,因为不考虑这一点可能会导致影响写完整性的 bug

  • [1] < a href = “ https://bugs.openjdk.java.net/浏览/JDK-4469683”rel = “ noReferrer”> https://bugs.openjdk.java.net/browse/jdk-4469683
  • 译自: 美国《科学》杂志网站(http://bugs.java.com/bugdatabase/view _ bug.do? bug _ id = 6608965) http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6608965: