Java NIO FileChannel与FileOutputstream的性能/有用性

nimxete2  于 2023-10-14  发布在  Java
关注(0)|答案(7)|浏览(134)

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

hrysbysz1#

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

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

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

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

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

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

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

如果目标是另一个磁盘或NIC,为什么要复制到内存?对于较大的文件,延迟是不平凡的。
就像其他人说的,使用FileChannel.transferTo()FileChannel.transferFrom()。这里的主要优点是JVM使用操作系统对DMA(Direct Memory Access)的访问(如果存在的话)。* (这取决于实现,但现代Sun和IBM版本的通用CPU是好的。)* 发生的是数据直接进入/从磁盘,到总线,然后到目的地.绕过任何通过RAM或CPU的电路。
我日夜工作的Web应用程序非常IO繁重。我也做过微观基准测试和真实世界的基准测试。结果在我的博客上,看看吧:

微观基准容易失真。如果可以的话,请努力从您计划执行的操作中收集数据,并在您预期的硬件上使用您预期的负载。
我的基准测试是可靠的,因为它们发生在一个生产系统上,一个健壮的系统,一个负载下的系统,收集在日志中。* 不是 * 我的笔记本电脑的7200 RPM 2.5”SATA驱动器,而我密切关注JVM工作我的硬盘。
你在跑什么?这很重要

qkf9rpyu

qkf9rpyu2#

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

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();

这不会比你自己从一个频道缓冲到另一个频道慢,而且可能会快得多。根据Javadoc的说法:
许多操作系统可以直接将字节从文件系统缓存传输到目标通道,而无需实际复制它们。

hfyxw5xn

hfyxw5xn3#

回答问题的“有用性”部分:
FileOutputStream上使用FileChannel的一个相当微妙的问题是,执行任何阻塞操作(例如,read()write())将导致通道突然关闭,并显示java.nio.channels.ClosedByInterruptException
现在,如果FileChannel的用途是线程主函数的一部分,并且设计考虑到了这一点,这可能是一件好事。
但如果它被一些辅助功能(如日志功能)使用,也会很麻烦。例如,如果日志记录函数碰巧被一个也被中断的线程调用,您可能会发现日志记录输出突然关闭。
不幸的是,这是如此微妙,因为不考虑这可能会导致影响写入完整性的错误。

tvmytwxo

tvmytwxo4#

根据我的测试(Win7 64位,6 GB RAM,Java 6),NIO transferFrom只对小文件快速,对大文件则非常慢。NIO数据缓冲器翻转始终优于标准IO。

  1. 1000x2MB
  2. NIO(transferFrom)~ 2300 ms
  3. NIO(直接数据缓冲器5000 b翻转)~ 3500 ms
    1.标准IO(缓冲5000 b)~ 6000 ms
  4. 100x20mb
  5. NIO(直接数据缓冲器5000 b翻转)~ 4000 ms
  6. NIO(transferFrom)~ 5000 ms
    1.标准IO(缓冲5000 b)~ 6500 ms
  7. 1x1000mb
  8. NIO(直接数据缓冲器5000 b翻转)~ 4500 s
    1.标准IO(缓冲5000 b)~ 7000 ms
  9. NIO(transferFrom)~ 8000 ms
    transferTo()方法作用于文件的块;并不打算作为高级文件复制方法:如何在Windows XP中复制大文件?
kxkpmulp

kxkpmulp5#

我测试了FileInputStream与FileChannel用于解码base64编码的文件。在我的经验中,我测试了相当大的文件,传统的io总是比nio快一点。
FileChannel在jvm的早期版本中可能有优势,因为几个io相关类的同步开销,但现代jvm在删除不需要的锁方面非常出色。

vcirk6k6

vcirk6k66#

如果你没有使用transferTo特性或非阻塞特性,你就不会注意到传统IO和NIO之间的区别(2),因为传统IOMap到NIO。
但是如果你可以使用NIO的特性,比如transferFrom/To或者想使用Buffers,那么NIO当然是最好的选择。
(注意,在某些情况下,传统的阻塞IO表现得更好,其中一些是由bug引起的,另一些是因为开销较少或不同的缓冲区设置,所以比较总是好的)。

klsxnrf1

klsxnrf17#

我的经验是,NIO在小文件上要快得多。但是对于大文件来说,FileInputStream/FileOutputStream要快得多。

相关问题