172

我试图弄清楚当我们使用 nioFileChannel与正常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");
    }
}
4

7 回答 7

209

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

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

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

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

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

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

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

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

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

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

使用生产数据和环境

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

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

你在跑什么?这很重要。

于 2009-11-06T21:48:33.427 回答
39

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

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:

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

于 2009-10-22T10:11:33.503 回答
7

根据我的测试(Win7 64 位、6GB RAM、Java6),NIO transferFrom 仅在处理小文件时速度很快,而在处理大文件时变得非常慢。NIO 数据缓冲区翻转始终优于标准 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 中复制大文件?

于 2011-11-03T14:33:19.523 回答
7

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

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

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

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

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

于 2017-02-23T07:36:35.090 回答
3

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

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

于 2009-10-22T19:58:51.680 回答
2

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

但是如果你可以使用像 transferFrom/To 这样的 NIO 功能或者想要使用 Buffers,那么 NIO 当然是要走的路。

于 2013-04-22T16:57:54.460 回答
-1

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

于 2009-10-22T06:26:56.213 回答