使用Java的IO与NIO来Copy文件的四种方法实现以及性能对比
发布日期:2021-06-30 16:02:22 浏览次数:2 分类:技术文章

本文共 14179 字,大约阅读时间需要 47 分钟。

推荐:

使用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); }}

匿名内部类一:

使用FileInputStreamFileOutputStream来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 int in the range 0 to     * 255. If no byte is available because the end of the stream     * has been reached, the value -1 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"; } };

匿名内部类二:

第二种方法使用BufferedInputStreamBufferedOutputStream来进行文件的Copy,它们会产生一个缓冲区,默认大小都是8192字节,源码如下:

private static int DEFAULT_BUFFER_SIZE = 8192;    /**     * Creates a BufferedInputStream     * and saves its  argument, the input stream     * in, for later use. An internal     * buffer array is created and  stored in buf.     *     * @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中的ChannelBuffer来进行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()position0,是不是就说明开始准备要写入了,并且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"; } };

匿名内部类四:

第四种方法只使用NIOChannel来进行文件的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.4bufferedStreamCopy:0.4nioBufferCopy:1.0nioTransferCopy:0.4---Copying two---bufferedStreamCopy:5.0nioBufferCopy:5.8nioTransferCopy:2.6---Copying three---bufferedStreamCopy:161.0nioBufferCopy:154.0nioTransferCopy:90.8---Copying four---bufferedStreamCopy:1659.0nioBufferCopy:1294.4nioTransferCopy:1266.6

我测试了很多次,需要缓冲区的方法,性能跟文件大小、缓冲区大小都有关系。

不过,第四种方法性能还是挺不错的。

如果有说错的地方,请大家不吝赐教(记得留言哦~~~~)。

转载地址:https://kaven.blog.csdn.net/article/details/104168782 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Java网络编程-Socket编程初涉四(NIO模型的简易多人聊天室)
下一篇:Java网络编程-NIO原理

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年04月15日 00时58分52秒