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),这是操作系统层面的基本概念,操作系统内核、硬件驱动等运行在内核态空间,具有相对高的特权;而用户态空间,则是给普通应用和服务使用。
输入输出流进行读写时,实际上是进行了多次上下文切换,比如应用读取数据时,先在内核态将数据从磁盘读取到内核缓存,再切换到用户态将数据从内核缓存读取到用户缓存。
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