使用Java的IO与NIO来Copy文件的四种方法实现以及性能对比
FileCopyRunner接口,定义了Copy文件的接口,等下在测试类中使用匿名内部类来实现。
package nio.channel;
import java.io.File;
public interface FileCopyRunner {
void copyFile(File source , File target);
}
测试类:
benchmark()
:Copy文件ROUNDS(5)
次,并且返回耗费的平均时间(1.0F)*elapsed / ROUNDS
。close()
:关闭资源。
完整代码(下面说四种方法):
package nio.channel;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileCopyDemo {
private static final int ROUNDS = 5;
private static void benchmark(FileCopyRunner test ,
File sourse , File target){
long elapsed = 0L;
for (int i = 0; i < ROUNDS; i++) {
long startTime = System.currentTimeMillis();
test.copyFile(sourse , target);
elapsed += System.currentTimeMillis() - startTime;
target.delete();
}
System.out.println(test+":"+(1.0F)*elapsed / ROUNDS);
}
public static void close(Closeable closeable){
if(closeable != null){
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
FileCopyRunner noBufferStreamCopy = new FileCopyRunner() {
@Override
public void copyFile(File sourse, File target) {
InputStream fin = null;
OutputStream fout = null;
try {
fin = new FileInputStream(sourse);
fout = new FileOutputStream(target);
int result;
while((result = fin.read()) != -1){
fout.write(result);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
close(fin);
close(fout);
}
}
@Override
public String toString() {
return "noBufferStreamCopy";
}
};
FileCopyRunner bufferedStreamCopy = new FileCopyRunner() {
@Override
public void copyFile(File sourse, File target) {
InputStream fin = null;
OutputStream fout = null;
try {
fin = new BufferedInputStream(new FileInputStream(sourse));
fout = new BufferedOutputStream(new FileOutputStream(target));
byte[] buffer = new byte[8192];
int result;
while((result = fin.read(buffer)) != -1){
fout.write(buffer , 0 ,result);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
close(fin);
close(fout);
}
}
@Override
public String toString() {
return "bufferedStreamCopy";
}
};
FileCopyRunner nioBufferCopy = new FileCopyRunner() {
@Override
public void copyFile(File sourse, File target) {
FileChannel fin = null;
FileChannel fout = null;
try {
fin = new FileInputStream(sourse).getChannel();
fout = new FileOutputStream(target).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(8192);
while(fin.read(buffer) != -1){
buffer.flip(); //开始读模式
while(buffer.hasRemaining()){
fout.write(buffer);
}
buffer.clear(); // 开始写模式
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
close(fin);
close(fout);
}
}
@Override
public String toString() {
return "nioBufferCopy";
}
};
FileCopyRunner nioTransferCopy = new FileCopyRunner() {
@Override
public void copyFile(File sourse, File target) {
FileChannel fin = null;
FileChannel fout = null;
try {
fin = new FileInputStream(sourse).getChannel();
fout = new FileOutputStream(target).getChannel();
long transferred = 0;
long size = fin.size();
while(transferred != size){
transferred += fin.transferTo(0,size,fout);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
close(fin);
close(fout);
}
}
@Override
public String toString() {
return "nioTransferCopy";
}
};
File one = new File("E:\\test\\1.png");
File oneCopy = new File("E:\\test\\1-copy.png");
System.out.println("---Copying one---");
benchmark(noBufferStreamCopy , one , oneCopy);
benchmark(bufferedStreamCopy , one , oneCopy);
benchmark(nioBufferCopy , one , oneCopy);
benchmark(nioTransferCopy , one , oneCopy);
File two = new File("E:\\test\\2.mp4");
File twoCopy = new File("E:\\test\\2-copy.mp4");
System.out.println("---Copying two---");
// benchmark(noBufferStreamCopy , two , twoCopy);
benchmark(bufferedStreamCopy , two , twoCopy);
benchmark(nioBufferCopy , two , twoCopy);
benchmark(nioTransferCopy , two , twoCopy);
File three = new File("E:\\test\\3.mp4");
File threeCopy = new File("E:\\test\\3-copy.mp4");
System.out.println("---Copying three---");
// benchmark(noBufferStreamCopy , three , threeCopy);
benchmark(bufferedStreamCopy , three , threeCopy);
benchmark(nioBufferCopy , three , threeCopy);
benchmark(nioTransferCopy , three , threeCopy);
File four = new File("E:\\test\\4.avi");
File fourCopy = new File("E:\\test\\4-copy.avi");
System.out.println("---Copying four---");
// benchmark(noBufferStreamCopy , four , fourCopy);
benchmark(bufferedStreamCopy , four , fourCopy);
benchmark(nioBufferCopy , four , fourCopy);
benchmark(nioTransferCopy , four , fourCopy);
}
}
匿名内部类一:
使用FileInputStream、FileOutputStream来Copy文件,它是一个字节一个字节进行read的,所以也是一个字节一个字节进行write的,read()的源码注释很清楚的写出来了,所以这种方法的性能特别差,等下用较大文件测试时,我们选择跳过这种方法(因为太久了),内部逻辑应该很简单吧,从read()的源码注释可以知道read()的返回值是介于0-255的值,其实就是读取的一个字节(8位)The value byte is returned as an int in the range 0 to 255。
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
FileCopyRunner noBufferStreamCopy = new FileCopyRunner() {
@Override
public void copyFile(File source, File target) {
InputStream fin = null;
OutputStream fout = null;
try {
fin = new FileInputStream(source);
fout = new FileOutputStream(target);
int result;
while((result = fin.read()) != -1){
fout.write(result);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
close(fin);
close(fout);
}
}
@Override
public String toString() {
return "noBufferStreamCopy";
}
};
匿名内部类二:
第二种方法使用BufferedInputStream
、BufferedOutputStream
来进行文件的Copy,它们会产生一个缓冲区,默认大小都是8192字节
,源码如下:
private static int DEFAULT_BUFFER_SIZE = 8192;
/**
* Creates a <code>BufferedInputStream</code>
* and saves its argument, the input stream
* <code>in</code>, for later use. An internal
* buffer array is created and stored in <code>buf</code>.
*
* @param in the underlying input stream.
*/
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
/**
* Creates a new buffered output stream to write data to the
* specified underlying output stream.
*
* @param out the underlying output stream.
*/
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
利用缓冲区,会大大提升性能,因为避免了频繁的打开、关闭文件,有了缓冲区,我们每次对文件进行读、写操作,都可以读、写更多字节数据,减少了打开、关闭文件等操作的次数。
FileCopyRunner bufferedStreamCopy = new FileCopyRunner() {
@Override
public void copyFile(File source, File target) {
InputStream fin = null;
OutputStream fout = null;
try {
fin = new BufferedInputStream(new FileInputStream(source));
fout = new BufferedOutputStream(new FileOutputStream(target));
byte[] buffer = new byte[8192];
int result;
while((result = fin.read(buffer)) != -1){
fout.write(buffer , 0 ,result);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
close(fin);
close(fout);
}
}
@Override
public String toString() {
return "bufferedStreamCopy";
}
};
匿名内部类三:
第三种方法使用NIO
中的Channel
、Buffer
来进行Copy文件。
和上一种方法一样,这里创建8192字节
的Buffer
,方便进行性能的对比。
ByteBuffer buffer = ByteBuffer.allocate(8192);
下面两种操作,大家应该知道吧,不知道就往下看。
buffer.flip(); //开始读模式
buffer.clear(); // 开始写模式
源码如下(先不管mark
有什么用):
private int position = 0;
private int limit;
private int capacity;
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
下面这张图应该描述的很清楚。
写模式:position位置之前的数据都是写入的(包括position位置),每写入一字节数据,position++,所以clear()将position置0,是不是就说明开始准备要写入了,并且limit = capacity,即进入写模式。
读模式:写入数据后,0位置到position位置的数据都是写入的,调用flip(),将limit置成position,而position置成0,所以position(0)到limit(原position位置)是不是就是要被读取的数据范围,即进入读模式
FileCopyRunner nioBufferCopy = new FileCopyRunner() {
@Override
public void copyFile(File source, File target) {
FileChannel fin = null;
FileChannel fout = null;
try {
fin = new FileInputStream(source).getChannel();
fout = new FileOutputStream(target).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(8192);
while(fin.read(buffer) != -1){
buffer.flip(); //开始读模式
while(buffer.hasRemaining()){
fout.write(buffer);
}
buffer.clear(); // 开始写模式
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
close(fin);
close(fout);
}
}
@Override
public String toString() {
return "nioBufferCopy";
}
};
匿名内部类四:
第四种方法只使用NIO
的Channel
来进行文件的Copy,Channel
通过transferTo()
可以把数据写入另一个Channel
。
从transferTo()
的源码注释也可以看出。
Transfers bytes from this channel’s file to the given writable byte channel.
FileCopyRunner nioTransferCopy = new FileCopyRunner() {
@Override
public void copyFile(File source, File target) {
FileChannel fin = null;
FileChannel fout = null;
try {
fin = new FileInputStream(source).getChannel();
fout = new FileOutputStream(target).getChannel();
long transferred = 0;
long size = fin.size();
while(transferred != size){
transferred += fin.transferTo(0,size,fout);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
close(fin);
close(fout);
}
}
@Override
public String toString() {
return "nioTransferCopy";
}
};
性能对比
文件详情如下:
- 1.png(37KB)。
- 2.mp4(3.92MB)。
- 3.mp4(132MB)。
- 4.avi(638MB)。
每种方法Copy文件5
次,计算平均耗时(第一种方法除外,因为它太慢了)。
输出:
---Copying one---
noBufferStreamCopy:143.4
bufferedStreamCopy:0.4
nioBufferCopy:1.0
nioTransferCopy:0.4
---Copying two---
bufferedStreamCopy:5.0
nioBufferCopy:5.8
nioTransferCopy:2.6
---Copying three---
bufferedStreamCopy:161.0
nioBufferCopy:154.0
nioTransferCopy:90.8
---Copying four---
bufferedStreamCopy:1659.0
nioBufferCopy:1294.4
nioTransferCopy:1266.6
我测试了很多次,需要缓冲区的方法,性能跟文件大小、缓冲区大小都有关系。
不过,第四种方法性能还是挺不错的。