使用Java的IO与NIO来Copy文件的四种方法实现以及性能对比

使用Java的IO与NIO来Copy文件的四种方法实现以及性能对比

FileCopyRunner接口,定义了Copy文件的接口,等下在测试类中使用匿名内部类来实现。java

package nio.channel;

import java.io.File;

public interface FileCopyRunner { 
 
  
    void copyFile(File source , File target);
}

测试类:web

  • 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 255ide

* 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";
            }
        };

匿名内部类二:svg

第二种方法使用BufferedInputStreamBufferedOutputStream来进行文件的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";
            }
        };

匿名内部类三:this

第三种方法使用NIO中的ChannelBuffer来进行Copy文件。
Java网络编程-NIO原理spa

和上一种方法同样,这里建立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.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

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

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

若是有说错的地方,请你们不吝赐教(记得留言哦~~~~)。