Java NIO FileChannel 与 FileOutputstream 性能/实用性

发布于 2024-08-08 18:10:38 字数 2122 浏览 3 评论 0原文

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

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. I observed that on my machine both perform at the same level, also many times the FileChannel way is slower. 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. 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?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

半窗疏影 2024-08-15 18:10:39

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

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

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

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

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

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

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

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

就像其他人所说的那样,使用 FileChannel.transferTo() 或 FileChannel.transferFrom() 。这里的主要优点是 JVM 使用操作系统对 DMA 的访问(直接内存访问),如果展示。 (这取决于实现,但通用 CPU 上的现代 Sun 和 IBM 版本已经很不错了。) 发生的情况是数据直接往返于磁盘、总线,然后到达目的地...通过 RAM 或 CPU 绕过任何电路。

我日以继夜开发的网络应用程序的 IO 量非常大。我也做过微观基准测试和现实世界基准测试。结果在我的博客上,看看:

使用生产数据和环境

微基准测试容易失真。如果可以的话,请努力从您计划执行的操作、您期望的负载、您期望的硬件上收集数据。

我的基准测试是可靠的,因为它们是在生产系统上进行的,一个强大的系统,一个负载下的系统,收集在日志中。 不是我的笔记本电脑的 7200 RPM 2.5" SATA 驱动器,而我密切关注 JVM 运行我的硬盘。

您在运行什么?这很重要。

My experience with larger files sizes has been that java.nio is faster than java.io. Solidly faster. Like in the >250% range. 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. 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.

Don't perform java benchmarks that read and write to the same disk.

If you do, then you are really benchmarking the disk, and not Java. I would also suggest that if your CPU is not busy, then you are probably experiencing some other bottleneck.

Don't use a buffer if you don't need to.

Why copy to memory if your target is another disk or a NIC? With larger files, the latency incured is non-trivial.

Like other have said, use FileChannel.transferTo() or FileChannel.transferFrom(). The key advantage here is that the JVM uses the OS's access to DMA (Direct Memory Access), if present. (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.

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.

What are you running on? It matters.

吃不饱 2024-08-15 18:10: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();

这不会比从一个通道缓冲到另一个通道慢,并且可能会快得多。根据 Javadoc:

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

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:

Many operating systems can transfer bytes directly from the filesystem cache to the target channel without actually copying them.

梦里的微风 2024-08-15 18:10:39

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

FileOutputStream 上使用 FileChannel 的一个相当微妙的问题是执行其任何阻塞操作(例如 read()< /code> 或 write()) 来自 中断状态将导致通道突然关闭 java.nio.channels.ClosedByInterruptException

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

但如果由某些辅助功能(例如日志记录功能)使用,它也可能会很麻烦。例如,如果日志记录函数恰好被也被中断的线程调用,您可能会发现日志输出突然关闭。

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

Answering the "usefulness" part of the question:

One rather subtle gotcha of using FileChannel over FileOutputStream is that performing any of its blocking operations (e.g. read() or write()) from a thread that's in interrupted state will cause the channel to close abruptly with 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.

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]

白日梦 2024-08-15 18:10:39

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

  • 正在复制 1000x2MB

    1. NIO(传输)~2300ms
    2. NIO(直接datababuffer 5000b翻转)~3500ms
    3. 标准 IO(缓冲区 5000b)~6000ms
  • 复制 100x20mb

    1. NIO(直接datababuffer 5000b翻转)~4000ms
    2. NIO(传输)~5000ms
    3. 标准 IO(缓冲区 5000b)~6500ms
  • 复制 1x1000mb

    1. NIO(直接datababuffer 5000b翻转)~4500s
    2. 标准 IO(缓冲区 5000b)~7000ms
    3. NIO(传输)~8000ms

TransferTo() 方法适用于文件块;并不是作为高级文件复制方法:
如何在 Windows XP 中复制大文件?

Based on my tests (Win7 64bit, 6GB RAM, Java6), NIO transferFrom is fast only with small files and becomes very slow on larger files. NIO databuffer flip always outperforms standard IO.

  • Copying 1000x2MB

    1. NIO (transferFrom) ~2300ms
    2. NIO (direct datababuffer 5000b flip) ~3500ms
    3. Standard IO (buffer 5000b) ~6000ms
  • Copying 100x20mb

    1. NIO (direct datababuffer 5000b flip) ~4000ms
    2. NIO (transferFrom) ~5000ms
    3. Standard IO (buffer 5000b) ~6500ms
  • Copying 1x1000mb

    1. NIO (direct datababuffer 5000b flip) ~4500s
    2. Standard IO (buffer 5000b) ~7000ms
    3. NIO (transferFrom) ~8000ms

The transferTo() method works on chunks of a file; wasn't intended as a high-level file copy method:
How to copy a large file in Windows XP?

尤怨 2024-08-15 18:10:39

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

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

I tested the performance of FileInputStream vs. FileChannel for decoding base64 encoded files. In my experients I tested rather large file and traditional io was alway a bit faster than 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.

愚人国度 2024-08-15 18:10:39

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

但是,如果您可以使用 NIO 功能(例如 TransferFrom/To)或想要使用 Buffer,那么 NIO 当然是最佳选择。

(需要注意的是,在某些情况下,传统的阻塞 IO 表现更好,其中一些是由错误引起的,另一些是因为开销较小或不同的缓冲假设,所以比较总是好的)。

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.

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.

(Caveat there are some cases where the traditional blocking IO behaves better, some of them are caused by bugs and some because of less overhead or different buffering asumptions, so its always good to compare).

小ぇ时光︴ 2024-08-15 18:10:39

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

My experience is, that NIO is much faster with small files. But when it comes to large files FileInputStream/FileOutputStream is much faster.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文