前言
Java中文件的拷贝方式,大体上分为两个派系
- stream的方式实现拷贝
- NIO的方式实现拷贝
以下举例四个经典的拷贝方式的实现,尽管Java中有自己的Copy实现,还是想使用BIO或NIO实现下面的四个方式
四种拷贝方式
无Buffer的输入输出流拷贝方式
public class NoBufferStreamCopy implements FileCopyRunner {
@Override
public void copyFile(File source, File target) {
try (
InputStream input = new FileInputStream(source);
OutputStream output = new FileOutputStream(target);
) {
int read;
while ((read = input.read())!= -1){
output.write(read);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "NoBufferStreamCopy";
}
}
有Buffer的输入输出流拷贝方式
public class BufferStreamCopy implements FileCopyRunner {
@Override
public void copyFile(File source, File target) {
try (
InputStream input = new FileInputStream(source);
OutputStream output = new FileOutputStream(target);
) {
byte[] buffer = new byte[1024];
int leng;
while ((leng = input.read(buffer)) != -1){
output.write(buffer, 0, leng);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "BufferStreamCopy";
}
}
NIO基础的拷贝实现
public class NioBufferCopy implements FileCopyRunner {
@Override
public void copyFile(File source, File target) {
try (
FileChannel input = new FileInputStream(source).getChannel();
FileChannel output = new FileOutputStream(target).getChannel();
) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (input.read(buffer) != -1){
buffer.flip();
while (buffer.hasRemaining()) {
output.write(buffer);
}
buffer.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "NioBufferCopy";
}
}
NIO Transfer实现拷贝
public class NioTransferCopy implements FileCopyRunner {
@Override
public void copyFile(File source, File target) {
try (
FileChannel inputChannel = new FileInputStream(source).getChannel();
FileChannel ouputChannel = new FileOutputStream(target).getChannel();
) {
for (long count = inputChannel.size(); count > 0;) {
long transferTo = inputChannel.transferTo(inputChannel.position(), count, ouputChannel);
count -= transferTo;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "NioTransferCopy";
}
}
对比
测试类
public class FileCopyDemo {
private static final int ROUNDS = 5;
private static final File sourceBig = new File("/home/kongweikun/Downloads/test/ccc.txt");
private static final File sourceMid = new File("/home/kongweikun/Downloads/test/bbb.txt");
private static final File sourceSmal = new File("/home/kongweikun/Downloads/test/aaa.txt");
private static final File target = new File("/home/kongweikun/Downloads/test/ddd.txt");
public static void benchMark(FileCopyRunner test, File source, File target, String name) {
long elapsed = 0L;
for (int i = 0; i < ROUNDS; i++) {
long start = System.currentTimeMillis();
test.copyFile(source, target);
elapsed = System.currentTimeMillis() - start;
// 删除copy之后的文件
target.delete();
}
System.out.println(test + name + " : " + elapsed / ROUNDS + "毫秒");
}
public static void main(String[] args) {
FileCopyRunner noBufferStreamCopy = new NoBufferStreamCopy();
FileCopyRunner bufferedStreamCopy = new BufferStreamCopy();
FileCopyRunner nioBufferCopy = new NioBufferCopy();
FileCopyRunner nioTransferCopy = new NioTransferCopy();
// 测试 1
System.out.println("-----------------------------------------");
System.out.println("无Buffer的BIO");
benchMark(noBufferStreamCopy, sourceBig, target, "大文件");
benchMark(noBufferStreamCopy, sourceMid, target, "中等文件");
benchMark(noBufferStreamCopy, sourceSmal, target, "小文件");
System.out.println("-----------------------------------------");
System.out.println("有Buffer的BIO");
benchMark(bufferedStreamCopy, sourceBig, target, "大文件");
benchMark(bufferedStreamCopy, sourceMid, target, "中等文件");
benchMark(bufferedStreamCopy, sourceSmal, target, "小文件");
//
System.out.println("-----------------------------------------");
System.out.println("NIO基础");
benchMark(nioBufferCopy, sourceBig, target, "大文件");
benchMark(nioBufferCopy, sourceMid, target, "中等文件");
benchMark(noBufferStreamCopy, sourceSmal, target, "小文件");
System.out.println("-----------------------------------------");
System.out.println("NIO的TransferTo");
benchMark(nioTransferCopy, sourceBig, target, "大文件");
benchMark(nioTransferCopy, sourceMid, target, "中等文件");
benchMark(nioTransferCopy, sourceSmal, target, "小文件");
}
}
-----------------------------------------
无Buffer的BIO
NoBufferStreamCopy大文件 : 614毫秒
NoBufferStreamCopy中等文件 : 0毫秒
NoBufferStreamCopy小文件 : 0毫秒
-----------------------------------------
有Buffer的BIO
BufferStreamCopy大文件 : 1毫秒
BufferStreamCopy中等文件 : 0毫秒
BufferStreamCopy小文件 : 0毫秒
-----------------------------------------
NIO基础
NioBufferCopy大文件 : 1毫秒
NioBufferCopy中等文件 : 0毫秒
NoBufferStreamCopy小文件 : 0毫秒
-----------------------------------------
NIO的TransferTo
NioTransferCopy大文件 : 0毫秒
NioTransferCopy中等文件 : 0毫秒
NioTransferCopy小文件 : 0毫秒
总结
对于 Copy 的效率,这个其实与操作系统和配置等情况相关,总体上来说,NIOtransferTo/From 的方式可能更快,因为它更能利用现代操作系统底层机制,避免不必要拷贝和上下文切换。
不同的 copy 方式,底层机制有什么区别?
这里涉及到了用户态空间和内核态空间,这是操作系统层面的基本概念,操作系统内核、硬件驱动等运行在内核态空间,具有相对高的特权.而用户态空间,则是给普通应用和服务使用.
当我们使用输入输出流的进行读写的时候,实际上经历了多次的上下文切换:
应用数据的读取: 首先内核态将数据从磁盘读取到内核缓存,再切换到用户态将数据从内核缓存读取到用户缓存
所以,这种方式会带来一定的额外开销,可能会降低 IO 效率。
但是NIO的transferTo的实现, 会涉及到零拷贝, 也就是数据传输并不会涉及到用户态,省去了上下文的切换和不必要的拷贝.
transferTo的传输过程是: