Java NIO FileChannel 与 FileOutputstream 性能/实用性

2022-08-31 06:54:44

我试图弄清楚当我们使用nio与正常文件读取和写入文件系统时,性能(或优势)是否有任何差异。我观察到,在我的机器上,两者都表现在同一水平上,而且很多时候,这种方式更慢。我能否知道比较这两种方法的更多详细信息。这是我使用的代码,我正在测试的文件大约是 。如果我没有查看随机访问或其他此类高级功能,那么将基于NIO的类用于文件I / O是否是一个不错的选择?FileChannelFileInputStream/FileOuputStreamFileChannel350MB

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

答案 1

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

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

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

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

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

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

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

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

像其他人说过的,使用或.这里的主要优点是JVM使用操作系统对DMA(直接内存访问)的访问(如果存在)。(这取决于实现,但通用CPU上的现代Sun和IBM版本很好。发生的事情是数据直接进入/从光盘到总线,然后到达目的地...通过 RAM 或 CPU 绕过任何电路。FileChannel.transferTo()FileChannel.transferFrom()

我花了一天和一夜时间工作的Web应用程序非常繁重。我也做过微基准测试和现实世界的基准测试。结果在我的博客上,看看:

使用生产数据和环境

微观基准容易失真。如果可以的话,努力从您计划执行的操作中收集数据,并具有您期望的负载,并在您期望的硬件上收集数据。

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

你在做什么?这很重要。


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

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


推荐