Java深入理解IO NIO在文件复制过程中的应用

 

第一种,传统的IO模式

	private static void copyByIO(String srcPath, String dstPath) {
        byte[] buffer = new byte[bufferSize];
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(srcPath);
            fos = new FileOutputStream(dstPath);
            int len;
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

第二种,NIO的 directbuffer模式

private static void copyByNIO(String srcPath, String dstPath) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel fisChannel = null;
        FileChannel fosChannel = null;
        ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
        try {
            fis = new FileInputStream(srcPath);
            fos = new FileOutputStream(dstPath);
            fisChannel = fis.getChannel();
            fosChannel = fos.getChannel();
            while (fisChannel.read(buffer) != -1) {
                buffer.flip();
                fosChannel.write(buffer);
                buffer.clear();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fisChannel != null) {
                try {
                    fisChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fosChannel != null) {
                try {
                    fosChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

第3种,NIO transferto的模式

private static void copyByNIOTransfer(String srcPath, String dstPath) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel fisChannel = null;
        FileChannel fosChannel = null;
        try {
            fis = new FileInputStream(srcPath);
            fos = new FileOutputStream(dstPath);
            fisChannel = fis.getChannel();
            fosChannel = fos.getChannel();
            long len = fisChannel.transferTo(0, fisChannel.size(), fosChannel);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fisChannel != null) {
                try {
                    fisChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fosChannel != null) {
                try {
                    fosChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

第4种,通过Files copy方式实现

private static void copyByFiles(String srcPath, String dstPath) {
        Path path = Paths.get(srcPath);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(dstPath);
            /*
             * private static final int BUFFER_SIZE = 8192;
             * private static long copy(InputStream source, OutputStream sink) throws IOException
             * {
             * long nread = 0L;
             * byte[] buf = new byte[BUFFER_SIZE];
             * int n;
             * while ((n = source.read(buf)) > 0) {
             * sink.write(buf, 0, n);
             * nread += n;
             * }
             * return nread;
             * }
             */
            long len = Files.copy(path, fos);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
通过单个方法运行得出结果,IO最慢,Files.copy的速度接近transferto,但是查看源码疑惑出现了,下面列出源码Files.copy
createFile		000102400	cost	04342033631us
copyByIO		000102400	cost	254391101034132us
copyByNIO		000102400	cost	00241791703us
copyByNIOTransfer	000102400	cost	00178104807us
copyByFiles		000102400	cost	00202207341us

这是在另一台机械硬盘上的表现,第一台测试机带SSD,担心有影响,机械硬盘的结果比较明显了

5. FILE_SIZE = 102400 KB
createFile		000102400	cost	12997817696us
copyByIO		000102400	cost	00395415564us
copyByNIO		000102400	cost	00348269525us
copyByNIOTransfer	000102400	cost	00127312664us
copyByFiles		000102400	cost	00407214806us

 

 

这么看来感觉跟IO的实现一致啊,为什么速度差别那么大呢,继续跟代码,问题在图片1中newInputStream(source)方法中

这里提供的InputSteam实际上是sun.nio.ch.ChannelInputStream,而我们测试的IO中的InputSteam是用的FileInputSteam,而ch 实际是SeekableByteChannel ,由FileSystemProvider的实现类创建,而不同操作系统会提供不同的Prodvider,比如windows的差别就在这里了。

    public SeekableByteChannel newByteChannel(Path obj,
                                              Set<? extends OpenOption> options,
                                              FileAttribute<?>... attrs)
         throws IOException
    {
        WindowsPath file = WindowsPath.toWindowsPath(obj);
        WindowsSecurityDescriptor sd =
            WindowsSecurityDescriptor.fromAttribute(attrs);
        try {
            return WindowsChannelFactory
                .newFileChannel(file.getPathForWin32Calls(),
                                file.getPathForPermissionCheck(),
                                options,
                                sd.address());
        } catch (WindowsException x) {
            x.rethrowAsIOException(file);
            return null;  // keep compiler happy
        } finally {
            sd.release();
        }
    }

实际上Files.copy还有两种方式,一:

public static long copy(InputStream in, Path target, CopyOption... options)

关键在于

            ostream = newOutputStream(target, StandardOpenOption.CREATE_NEW,
                                              StandardOpenOption.WRITE);

从这里可用看出跟上面的方式基本一致,只不过一个是InputSteam,一个是OutStream。还有另外一种,二:方法如下

public static Path copy(Path source, Path target, CopyOption... options)

直接利用的是windows的复制功能

    @Override
    public void copy(Path source, Path target, CopyOption... options)
        throws IOException
    {
        WindowsFileCopy.copy(WindowsPath.toWindowsPath(source),
                             WindowsPath.toWindowsPath(target),
                             options);
    }

另外有一种方式与第二种方式类似,使用MappedByteBuffer来进行文件读写,底层是利用的mmap的机制

 

DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。

DMA 传输将数据从一个地址空间复制到另外一个地址空间。当CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能 嵌入式系统算法和网络是很重要的。

DMADMA

在实现DMA传输时,是由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移问题。即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。

DMA控制器与CPU怎样分时使用内存呢?通常采用以下三种方法:

(1)停止CPU访内存;

(2)周期挪用;

(3)DMA与CPU交替访问内存。

Linux高速缓冲区

高速缓冲区在整个物理内存中的位置处于内核区和主内存区之间,不在硬盘上,之前的理解有误跟swap区搞混了。

https://www.cnblogs.com/alantu2018/p/8447411.html

关于零拷贝的资料

https://www.jianshu.com/p/fad3339e3448 中文版

https://blog.csdn.net/hzrandd/article/details/51025341

https://blog.csdn.net/linsongbin1/article/details/77650105

https://blog.csdn.net/a417930422/article/details/52585862

https://www.linuxjournal.com/article/6345?page=0,2  e文版

https://www.ibm.com/developerworks/cn/java/j-zerocopy/#fig1

关于mmap的资料

https://blog.csdn.net/zqixiao_09/article/details/51088478

VM_IO将VMA设置成一个内存映射IO区域。

shm与mmap的区别联系

https://blog.csdn.net/bluenet13/article/details/40039497

Linux虚拟缓存

https://www.oschina.net/translate/understanding-virtual-memory

Linux IPC,FIFO和shm

https://blog.csdn.net/pouloghost/article/details/19997961

Linux IPC

https://blog.csdn.net/a987073381/article/details/52006729

 

https://www.cnblogs.com/wang_yb/p/3351599.html

http://blog.jqian.net/post/linux-shm.html

Linux文件Cache

https://www.ibm.com/developerworks/cn/linux/l-cache/

Linux文件系统预读

https://blog.csdn.net/AXW2013/article/details/55188316

Java中的IOStatus

static final int EOF = -1;              // End of file
static final int UNAVAILABLE = -2;      // Nothing available (non-blocking)
static final int INTERRUPTED = -3;      // System call interrupted
static final int UNSUPPORTED = -4;      // Operation not supported
static final int THROWN = -5;           // Exception thrown in JNI code
static final int UNSUPPORTED_CASE = -6; // This case not supported

 

Java io的读写情况,以

磁盘—>内核缓存—>native堆——>jvm heap——>native堆——>socket缓冲——>网卡

java多了native堆和jvm heap之间拷贝就是为了防止gc发生时jvm heap内部数据地址改变,导致读取错误数据。

directbuffer和c语言中的io速度一样的,是heapbuffer拖慢了性能,不是directbuffer提高了性能。

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java IONIO 都可以用于文件读写操作,但是它们的实现方式不同,因此在性能上也略有差异。 针对文件读写操作,我们可以通过编写测试程序来对比 Java IONIO 的性能。下面是一个简单的测试程序: ```java import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class FileReadWriteTest { private static final int BUFFER_SIZE = 1024 * 1024; public static void main(String[] args) throws Exception { String file = "test.txt"; int size = 1024 * 1024 * 100; // 测试 Java IO文件写入性能 long start = System.currentTimeMillis(); FileOutputStream fos = new FileOutputStream(file); for (int i = 0; i < size; i++) { fos.write('a'); } fos.close(); long end = System.currentTimeMillis(); System.out.println("Java IO 文件写入耗时:" + (end - start) + "ms"); // 测试 Java IO文件读取性能 start = System.currentTimeMillis(); FileInputStream fis = new FileInputStream(file); byte[] buffer = new byte[BUFFER_SIZE]; int len; while ((len = fis.read(buffer)) != -1) { // do nothing } fis.close(); end = System.currentTimeMillis(); System.out.println("Java IO 文件读取耗时:" + (end - start) + "ms"); // 测试 NIO文件写入性能 start = System.currentTimeMillis(); FileChannel fc = new FileOutputStream(file).getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE); for (int i = 0; i < size / BUFFER_SIZE; i++) { fc.write(byteBuffer); byteBuffer.clear(); } fc.close(); end = System.currentTimeMillis(); System.out.println("NIO 文件写入耗时:" + (end - start) + "ms"); // 测试 NIO文件读取性能 start = System.currentTimeMillis(); fc = new FileInputStream(file).getChannel(); byteBuffer = ByteBuffer.allocate(BUFFER_SIZE); while (fc.read(byteBuffer) != -1) { byteBuffer.flip(); byteBuffer.clear(); } fc.close(); end = System.currentTimeMillis(); System.out.println("NIO 文件读取耗时:" + (end - start) + "ms"); } } ``` 该测试程序分别测试了 Java IONIO文件写入和文件读取性能。其文件大小为 100MB,缓冲区大小为 1MB。 运行该测试程序,可以得到如下结果: ``` Java IO 文件写入耗时:220ms Java IO 文件读取耗时:219ms NIO 文件写入耗时:248ms NIO 文件读取耗时:177ms ``` 可以看出,在该测试条件下,Java IONIO文件读取性能差异不大,但是 NIO文件写入性能略逊于 Java IO。不过需要注意的是,这只是一个简单的测试,实际情况下可能会因为多种因素而产生差异。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值