简体   繁体   English

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

[英]Java NIO FileChannel versus FileOutputstream performance / usefulness

I am trying to figure out if there is any difference in performance (or advantages) when we use nio FileChannel versus normal FileInputStream/FileOuputStream to read and write files to filesystem. 当我们使用nio FileChannel与普通的FileInputStream/FileOuputStream来读取和写入文件系统文件时,我试图弄清楚性能(或优势)是否存在任何差异。 I observed that on my machine both perform at the same level, also many times the FileChannel way is slower. 我观察到,在我的机器上,两者都在同一级别执行,而且FileChannel方式也很慢。 Can I please know more details comparing these two methods. 我可以了解比较这两种方法的更多细节。 Here is the code I used, the file that I am testing with is around 350MB . 这是我使用的代码,我测试的文件大约是350MB Is it a good option to use NIO based classes for File I/O, if I am not looking at random access or other such advanced features? 如果我不是在查看随机访问或其他此类高级功能,那么对文件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");
    }
}

My experience with larger files sizes has been that java.nio is faster than java.io . 我对较大文件大小的体验是java.niojava.io更快。 Solidly faster. 坚固得快。 Like in the >250% range. 比如> 250%的范围。 That said, I am eliminating obvious bottlenecks, which I suggest your micro-benchmark might suffer from. 也就是说,我正在消除明显的瓶颈,我建议你的微基准可能会受到影响。 Potential areas for investigating: 潜在的调查领域:

The buffer size. 缓冲区大小。 The algorithm you basically have is 你基本上拥有的算法是

  • copy from disk to buffer 从磁盘复制到缓冲区
  • copy from buffer to disk 从缓冲区复制到磁盘

My own experience has been that this buffer size is ripe for tuning. 我自己的经验是,这个缓冲区的大小是成熟的调整。 I've settled on 4KB for one part of my application, 256KB for another. 我已经为我的应用程序的一部分确定了4KB,另一部分支持了256KB。 I suspect your code is suffering with such a large buffer. 我怀疑你的代码遭受如此大的缓冲。 Run some benchmarks with buffers of 1KB, 2KB, 4KB, 8KB, 16KB, 32KB and 64KB to prove it to yourself. 使用1KB,2KB,4KB,8KB,16KB,32KB和64KB的缓冲区运行一些基准来证明这一点。

Don't perform java benchmarks that read and write to the same disk. 不要执行读取和写入同一磁盘的java基准测试。

If you do, then you are really benchmarking the disk, and not Java. 如果你这样做,那么你真的是对磁盘进行基准测试,而不是Java。 I would also suggest that if your CPU is not busy, then you are probably experiencing some other bottleneck. 我还建议,如果您的CPU不忙,那么您可能遇到了其他一些瓶颈。

Don't use a buffer if you don't need to. 如果您不需要,请不要使用缓冲区。

Why copy to memory if your target is another disk or a NIC? 如果目标是另一个磁盘或NIC,为什么要复制到内存? With larger files, the latency incured is non-trivial. 对于较大的文件,所产生的延迟非常重要。

Like other have said, use FileChannel.transferTo() or FileChannel.transferFrom() . 像其他人所说的那样,使用FileChannel.transferTo()FileChannel.transferFrom() The key advantage here is that the JVM uses the OS's access to DMA ( Direct Memory Access ), if present. 这里的关键优势是JVM使用操作系统访问DMA( 直接内存访问 )(如果存在)。 (This is implementation dependent, but modern Sun and IBM versions on general purpose CPUs are good to go.) What happens is the data goes straight to/from disc, to the bus, and then to the destination... bypassing any circuit through RAM or the CPU. (这是依赖于实现的,但是通用CPU上的现代Sun和IBM版本很好用。)会发生什么事情是数据直接进出光盘,到达总线,再到目的地......绕过任何电路RAM或CPU。

The web app I spent my days and night working on is very IO heavy. 我花了几天时间工作的网络应用程序非常重要。 I've done micro benchmarks and real-world benchmarks too. 我也做过微基准测试和实际基准测试。 And the results are up on my blog, have a look-see: 结果出现在我的博客上,看看:

Use production data and environments 使用生产数据和环境

Micro-benchmarks are prone to distortion. 微基准测试很容易失真。 If you can, make the effort to gather data from exactly what you plan to do, with the load you expect, on the hardware you expect. 如果可以的话,尽可能地在您期望的硬件上使用您期望的负载来准确地收集您计划执行的操作的数据。

My benchmarks are solid and reliable because they took place on a production system, a beefy system, a system under load, gathered in logs. 我的基准测试结果坚实可靠,因为它们发生在生产系统,强大系统,负载系统,日志中。 Not my notebook's 7200 RPM 2.5" SATA drive while I watched intensely as the JVM work my hard disc. 不是我的笔记本电脑的7200转2.5英寸SATA驱动器,当我看到JVM工作我的硬盘时非常强烈。

What are you running on? 你在跑什么? It matters. 这很重要。

If the thing you want to compare is performance of file copying, then for the channel test you should do this instead: 如果要比较的是文件复制的性能,那么对于通道测试,你应该这样做:

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

This won't be slower than buffering yourself from one channel to the other, and will potentially be massively faster. 这不会比从一个通道缓冲到另一个通道慢,并且可能会大大加快。 According to the Javadocs: 根据Javadocs:

Many operating systems can transfer bytes directly from the filesystem cache to the target channel without actually copying them. 许多操作系统可以直接从文件系统缓存向目标通道传输字节,而无需实际复制它们。

Based on my tests (Win7 64bit, 6GB RAM, Java6), NIO transferFrom is fast only with small files and becomes very slow on larger files. 基于我的测试(Win7 64位,6GB RAM,Java6),NIO transferFrom仅对小文件很快,在较大文件上变得非常慢。 NIO databuffer flip always outperforms standard IO. NIO数据缓冲器翻转总是优于标准IO。

  • Copying 1000x2MB 复制1000x2MB

    1. NIO (transferFrom) ~2300ms NIO(transferFrom)~2300ms
    2. NIO (direct datababuffer 5000b flip) ~3500ms NIO(直接数据缓冲5000b翻转)~3500ms
    3. Standard IO (buffer 5000b) ~6000ms 标准IO(缓冲5000b)~6000ms
  • Copying 100x20mb 复制100x20mb

    1. NIO (direct datababuffer 5000b flip) ~4000ms NIO(直接数据缓冲5000b翻转)~4000ms
    2. NIO (transferFrom) ~5000ms NIO(transferFrom)~5000ms
    3. Standard IO (buffer 5000b) ~6500ms 标准IO(缓冲区5000b)~6500ms
  • Copying 1x1000mb 复制1x1000mb

    1. NIO (direct datababuffer 5000b flip) ~4500s NIO(直接datababuffer 5000b翻转)~4500s
    2. Standard IO (buffer 5000b) ~7000ms 标准IO(缓冲5000b)~7000ms
    3. NIO (transferFrom) ~8000ms NIO(transferFrom)~8000ms

The transferTo() method works on chunks of a file; transferTo()方法适用于文件的块; wasn't intended as a high-level file copy method: How to copy a large file in Windows XP? 不是作为高级文件复制方法: 如何在Windows XP中复制大文件?

Answering the "usefulness" part of the question: 回答问题的“有用性”部分:

One rather subtle gotcha of using FileChannel over FileOutputStream is that performing any of its blocking operations (eg read() or write() ) from a thread that's in interrupted state will cause the channel to close abruptly with java.nio.channels.ClosedByInterruptException . FileOutputStream上使用FileChannel一个相当微妙的问题是,从处于中断状态的线程执行任何阻塞操作(例如read()write() )将导致通道突然关闭java.nio.channels.ClosedByInterruptException

Now, this could be a good thing if whatever the FileChannel was used for is part of the thread's main function, and design took this into account. 现在,如果FileChannel用于的任何东西都是线程的主要功能的一部分,这可能是一件好事,设计考虑到了这一点。

But it could also be pesky if used by some auxiliary feature such as a logging function. 但如果使用某些辅助功能(如日志记录功能),它也可能会令人讨厌。 For example, you can find your logging output suddenly closed if the logging function happens to be called by a thread that's also interrupted. 例如,如果日志功能恰好被一个也被中断的线程调用,您可以发现您的日志记录输出突然关闭。

It's unfortunate this is so subtle because not accounting for this can lead to bugs that affect write integrity. 不幸的是,这是如此微妙,因为不考虑这会导致影响写入完整性的错误。 [1][2] [1] [2]

I tested the performance of FileInputStream vs. FileChannel for decoding base64 encoded files. 我测试了FileInputStream与FileChannel的性能,用于解码base64编码文件。 In my experients I tested rather large file and traditional io was alway a bit faster than nio. 在我的经验中,我测试了相当大的文件,传统的io总是比nio快一点。

FileChannel might have had an advantage in prior versions of the jvm because of synchonization overhead in several io related classes, but modern jvm are pretty good at removing unneeded locks. 由于几个io相关类中的同步化开销,FileChannel可能在jvm的早期版本中具有优势,但现代jvm非常擅长删除不需要的锁。

If you are not using the transferTo feature or non-blocking features you will not notice a difference between traditional IO and NIO(2) because the traditional IO maps to NIO. 如果您没有使用transferTo功能或非阻塞功能,您将不会注意到传统IO和NIO之间的区别(2),因为传统的IO映射到NIO。

But if you can use the NIO features like transferFrom/To or want to use Buffers, then of course NIO is the way to go. 但是如果你可以使用像transferFrom / To这样的NIO功能或者想要使用Buffers,那么NIO当然是要走的路。

My experience is, that NIO is much faster with small files. 我的经验是,对于小文件,NIO要快得多。 But when it comes to large files FileInputStream/FileOutputStream is much faster. 但是当谈到大文件时,FileInputStream / FileOutputStream要快得多。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM