Java NIO FileChannel 与 FileOutputstream 性能/实用性

Posted

技术标签:

【中文标题】Java NIO FileChannel 与 FileOutputstream 性能/实用性【英文标题】:Java NIO FileChannel versus FileOutputstream performance / usefulness 【发布时间】:2010-12-08 23:40:01 【问题描述】:

我试图弄清楚当我们使用 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");
    

【问题讨论】:

transferTo/transferFrom 会更传统地用于复制文件。无论哪种技术都不应该使您的硬盘驱动器更快或更慢,尽管我想如果它一次读取小块并导致头部花费过多的时间寻找可能会出现问题。 (你没有提到你正在使用哪个操作系统,或者哪个 JRE 供应商和版本。) 很抱歉,我正在使用 FC10 和 Sun JDK6。 【参考方案1】:

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

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

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

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

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

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

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

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

就像其他人所说,使用FileChannel.transferTo()FileChannel.transferFrom()。这里的关键优势是 JVM 使用操作系统对 DMA (Direct Memory Access) 的访问(如果存在)。 (这取决于实现,但通用 CPU 上的现代 Sun 和 IBM 版本是不错的选择。) 发生的情况是数据直接进出磁盘,到达总线,然后到达目的地...通过 RAM 或 CPU 绕过任何电路。

我日夜工作的 Web 应用程序的 IO 非常繁重。我也做过微观基准和真实世界的基准。结果在我的博客上,看看吧:

Real world performance metrics: java.io vs. java.nio Real world performance metrics: java.io vs. java.nio (The Sequel)

使用生产数据和环境

微基准容易失真。如果可以的话,请努力在您期望的硬件上,以您期望的负载,从您计划做的事情中收集数据。

我的基准测试是可靠的,因为它们是在生产系统、功能强大的系统、负载下的系统上进行的,并以日志形式收集。 不是我的笔记本电脑的 7200 RPM 2.5" SATA 驱动器,而我正在密切注视着 JVM 运行我的硬盘。

你在做什么?这很重要。

【讨论】:

@Stu Thompson - 感谢您的帖子。由于我正在研究同一主题,因此我遇到了您的答案。我试图了解 nio 向 Java 程序员公开的操作系统改进。其中一些是 - DMA 和内存映射文件。你遇到过更多这样的改进吗? P.S - 您的博客链接已损坏。 @AndyDufresne 我的博客目前已关闭,将在本周晚些时候上线——正在迁移中。 这里是archive.org上的博客链接:web.archive.org/web/20120815094827/http://geekomatic.ch/2008/…web.archive.org/web/20120821114802/http://geekomatic.ch/2009/… 如何将文件从一个目录复制到另一个目录? (每个不同的磁盘驱动器) 有趣:mailinator.blogspot.in/2008/02/…【参考方案2】:

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

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:

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

【讨论】:

【参考方案3】:

根据我的测试(Win7 64 位、6GB RAM、Java6),NIO transferFrom 仅在处理小文件时速度很快,而在处理大文件时变得非常慢。 NIO 数据缓冲区翻转始终优于标准 IO。

复制 1000x2MB

    NIO (transferFrom) ~2300ms NIO(直接datababuffer 5000b翻转)~3500ms 标准 IO(缓冲区 5000b)~6000ms

复制 100x20mb

    NIO(直接datababuffer 5000b翻转)~4000ms NIO (transferFrom) ~5000ms 标准 IO(缓冲区 5000b)~6500ms

复制 1x1000mb

    NIO(直接datababuffer 5000b翻转)~4500s 标准 IO(缓冲区 5000b)~7000ms NIO (transferFrom) ~8000ms

transferTo() 方法适用于文件的块;不打算作为高级文件复制方法: How to copy a large file in Windows XP?

【讨论】:

【参考方案4】:

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

使用FileChannel 而不是FileOutputStream 的一个相当微妙的问题是,从interrupted state 中的线程执行其任何阻塞操作(例如read()write())将导致通道突然关闭java.nio.channels.ClosedByInterruptException.

现在,如果 FileChannel 的用途是线程主函数的一部分,并且设计考虑到了这一点,这可能是一件好事。

但如果被一些辅助功能(例如日志功能)使用,它也可能会令人讨厌。例如,如果日志函数恰好被同样中断的线程调用,您会发现日志输出突然关闭。

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

[1]https://bugs.openjdk.java.net/browse/JDK-4469683 [2]http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6608965

【讨论】:

【参考方案5】:

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

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

【讨论】:

【参考方案6】:

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

但是如果你可以使用 NIO 的功能,比如 transferFrom/To 或者想要使用 Buffers,那么 NIO 当然是最好的选择。

【讨论】:

【参考方案7】:

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

【讨论】:

你搞错了吗?我自己的经验是java.nio 处理更大 文件比java.io 更快,而不是更小。 不,我的经验正好相反。只要文件小到可以映射到内存,java.nio 就会很快。如果它变得更大(200 MB 或更多)java.io 会更快。 哇。跟我完全相反。请注意,您不一定需要映射文件来读取它——可以从FileChannel.read() 读取。使用java.nio 读取文件的方法不止一种。 @tangens 你检查了吗?

以上是关于Java NIO FileChannel 与 FileOutputstream 性能/实用性的主要内容,如果未能解决你的问题,请参考以下文章

java的nio之:java的nio系列教程之FileChannel

Java NIO 学习--FileChannel

Java NIO系列教程 FileChannel

Java NIO系列教程 FileChannel

java NIO FileChannel

Java NIO FileChannel