Java面试:文件拷贝的几种方式

文件拷贝方式

1、Java Copy File – Stream


private static void copyFileUsingStream(File source, File dest) throws IOException {
    InputStream is = null;
    OutputStream os = null;
    try {
        is = new FileInputStream(source);
        os = new FileOutputStream(dest);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = is.read(buffer)) > 0) {
            os.write(buffer, 0, length);
        }
    } finally {
        is.close();
        os.close();
    }
}

 

2、Java Copy File – java.nio.channels.FileChannel:
private static void copyFileUsingChannel(File source, File dest) throws IOException {
    FileChannel sourceChannel = null;
    FileChannel destChannel = null;
    try {
        sourceChannel = new FileInputStream(source).getChannel();
        destChannel = new FileOutputStream(dest).getChannel();
        destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
       }finally{
           sourceChannel.close();
           destChannel.close();
   }
}

3、Java Copy File – Apache Commons IO FileUtils

private static void copyFileUsingApacheCommonsIO(File source, File dest) throws IOException {
    FileUtils.copyFile(source, dest);
}

 

4、Java Copy File – Files class

private static void copyFileUsingJava7Files(File source, File dest) throws IOException {
    Files.copy(source.toPath(), dest.toPath());
}

 

如果只是简单的测试拷贝一个文件哪个更快,就是第一种最贱的的Stream方式的拷贝

追踪第四种标准类库的copy源码,不同的参数有不同的重载方法分析:能够在方法实现里直接看到使用的是 InputStream.transferTo(),但其内部实现其实是 stream 在用户态的读写

 

 

1. 拷贝实现机制分析:

用户态空间(User Space)和内核态空间(Kernel Space),这是操作系统层面的基本概念,操作系统内核、硬件驱动等运行在内核态空间,具有相对高的特权;而用户态空间,则是给普通应用和服务使用。

输入输出流进行读写时,实际上是进行了多次上下文切换,比如应用读取数据时,先在内核态将数据从磁盘读取到内核缓存,再切换到用户态将数据从内核缓存读取到用户缓存。

Java面试:文件拷贝的几种方式

Java面试:文件拷贝的几种方式

 

3、NIO Buffer

Buffer 是 NIO 操作数据的基本工具,Java 为每种原始数据类型都提供了相应的 Buffer 实现(布尔除外)

Buffer 有几个基本属性:

capacity,表示 Buffer 到底有多大,也就是数组的长度。

position,要操作的数据起始位置。

limit,相当于操作的限额。在读取或者写入时,limit 的意义很明显是不一样的。比如,读取操作时,很可能将 limit 设置到所容纳数据的上限;而在写入时,则会设置容量或容量以下的可写限度。

mark,记录上一次 postion 的位置,默认是 0,算是一个便利性的考虑,往往不是必须的。

Buffer 的基本操作:我们创建了一个 ByteBuffer,准备放入数据,capacity 当然就是缓冲区大小,而 position 就是 0,limit 默认就是 capacity 的大小。当我们写入几个字节的数据时,position 就会跟着水涨船高,但是它不可能超过 limit 的大小。如果我们想把前面写入的数据读出来,需要调用 flip 方法,将 position 设置为 0,limit 设置为以前的 position 那里。如果还想从头再读一遍,可以调用 rewind,让 limit 不变,position 再次设置为 0。

 

发现服务器内存使用得特别高,但是堆内存也比较稳定,这种场景是你你会怎么排查?

这里就涉及到堆外内存相关的问题:

Direct Buffer:如果我们看 Buffer 的方法定义,你会发现它定义了 isDirect() 方法,返回当前 Buffer 是否是 Direct 类型。这是因为 Java 提供了堆内和堆外(Direct)Buffer

 

回答问题:Direct Buffer它不在堆上,Xmx 之类参数,其实并不能影响 Direct Buffer 等堆外成员所使用的内存额度,大多数垃圾收集过程中,都不会主动收集 Direct Buffer,它的垃圾收集过程,就是基于前面所介绍的 Cleaner(一个内部实现)和幻象引用,对它的销毁往往要拖到 full GC 的时候

排查方法:通常的垃圾收集日志等记录,并不包含 Direct Buffer 等信息,所以 Direct Buffer 内存诊断也是个比较头疼的事情。幸好,在 JDK 8 之后的版本,我们可以方便地使用 Native Memory Tracking(NMT)特性来进行诊断

-XX:NativeMemoryTracking={summary|detail}

** NMT 通常都会导致 JVM 出现 5%~10% 的性能下降,请谨慎考虑。

解决方法:

1)显式地调用 System.gc() 来强制触发。

2)使用 Direct Buffer 的框架,框架会自己在程序中调用释放方法,Netty 就是这么做的。(netty框架的优势)

3)重复使用 Direct Buffer。

 

MappedByteBuffer,它将文件按照指定大小直接映射为内存区域,当程序访问这个内存区域时将直接操作这块儿文件数据,省去了将数据从内核空间向用户空间传输的损耗。我们可以使用FileChannel.map创建 MappedByteBuffer,本质上也是种 Direct Buffer

Java面试:文件拷贝的几种方式