使用RandomAccessFile实现多线程文件拷贝(多种方式进行性能对比)

import sun.misc.Cleaner;
import sun.nio.ch.DirectBuffer;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 多线程文件复制
 * 经过多种方式和多次的测试:使用NIO的方式是能完整复制文件且相对耗时少的方式,实测:复制一个185M的文件,大概耗时5-6秒左右。
 * 但其实使用单线程复制也不慢多少,大概慢1-2秒。
 * 比较遗憾的是,信心满满的使用MappedByteBuffer来测试,但测试结果是会出错,具体原因没深究!
 *
 */
public class MultiThreadCopyFile {

    private ExecutorService executor;

    private String srcFile;
    private String desFile;
    private long fileSize;
    private long blockSize;
    private int threadNum;

    public MultiThreadCopyFile(String srcFile, String desFile, int threadNum) {
        File file = new File(srcFile);
        if (!file.exists()) {
            throw new RuntimeException("源文件不存在");
        }

        if (threadNum <= 0) {
            throw new RuntimeException("线程数必须大于0");
        }

        this.srcFile = srcFile;
        this.desFile = desFile;
        this.threadNum = threadNum;
        this.fileSize = file.length(); // 文件总长度
        this.blockSize = this.fileSize / threadNum; // 每块下载的文件大小
        // 由于使用的MappedByteBuffer最大只能映射2G的大小,所以这里判断一下每块的区间是否大于2G
        if (this.blockSize > Integer.MAX_VALUE - 1) {
            throw new RuntimeException("当前下载线程数量偏少,建议增加下载线程数量");
        } else if (blockSize == 0) { // 线程数量太多也需要检查一下
            throw new RuntimeException("当前下载线程数量偏多,请减少下载线程数量");
        }
        executor = Executors.newFixedThreadPool(threadNum);
    }

    public void start() {
        for (int i = 0; i < threadNum; i++) {
            long startPos = i * blockSize;
            long endPos;
            if (i == threadNum - 1) {
                endPos = fileSize;
            } else {
                endPos = startPos + blockSize;
            }
            executor.submit(new DownlodTask(startPos, endPos));
        }

        // 测试单线程复制文件
//        new Thread(new DownlodTask(0, 0)).start();
    }

    class DownlodTask implements Runnable {

        private long startPos;
        private long endPos;

        public DownlodTask(long startPos, long endPost) {
            this.startPos = startPos;
            this.endPos = endPost;
        }

        @Override
        public void run() {
            copyByNIO();
        }

        /**
         * 使用NIO的FileChannel
         *
         */
        private void copyByNIO() {
            try {
                long start = System.currentTimeMillis();

                RandomAccessFile rafi = new RandomAccessFile(srcFile, "r");
                RandomAccessFile rafo = new RandomAccessFile(desFile, "rw");
                rafi.seek(startPos);
                rafo.seek(startPos);

                long size = endPos - startPos;

                FileChannel channelIn = rafi.getChannel();
                FileChannel channelOut = rafo.getChannel();

                // 锁住需要操作的区域,第三个参数为fasle代表锁住
//                FileLock lock = channelOut.lock(startPos, size, false);

                // 复制数据
                channelIn.transferTo(startPos, size, channelOut);

                // 释放锁
//                lock.release();

                rafi.close();
                rafo.close();

                System.out.println(Thread.currentThread().getName() + ":文件开始位置:" + startPos + ",文件大小:" + size + ",耗时:" + (System.currentTimeMillis() - start));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 使用byte数组来做中间缓存
         */
        private void copyByByteArray() {
            try {
                long start = System.currentTimeMillis();

                RandomAccessFile rafi = new RandomAccessFile(srcFile, "r");
                RandomAccessFile rafo = new RandomAccessFile(desFile, "rw");

                long size = endPos - startPos;
                long markSize = 0; // 标记当前已读取了多少字节
                rafi.seek(startPos);
                rafo.seek(startPos);

                int bufSize = 8192;
                byte[] buff = new byte[bufSize];
                int len; // 实际读取到的字节长度
                int readLen; // 还能往缓存数组读取多少个字节
                while (true) {
                    // 判断当前已读取的数量加上缓存数组长度是否大于需要读取的总大小
                    if (markSize + bufSize > size) {
                        readLen = (int) (size - markSize);
                    } else {
                        readLen = bufSize;
                    }

                    if (readLen <= 0) {
                        break;
                    }

                    len = rafi.read(buff, 0, readLen);
                    if (len == -1) {
                        break;
                    }

                    rafo.write(buff, 0, len);
                    markSize += len;
                }

                rafi.close();
                rafo.close();

                System.out.println(Thread.currentThread().getName() + ":文件大小:" + size + ",markSize=" + markSize + ",耗时:" + (System.currentTimeMillis() - start));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 单线程复制
         */
        private void copy() {
            try {
                long start = System.currentTimeMillis();
                RandomAccessFile rafi = new RandomAccessFile(srcFile, "r");
                RandomAccessFile rafo = new RandomAccessFile(desFile, "rw");

                byte[] buff = new byte[8192];
                int len;
                while ((len = rafi.read(buff)) != -1) {
                    rafo.write(buff, 0, len);
                }
                rafi.close();
                rafo.close();
                System.out.println("耗时:" + (System.currentTimeMillis() - start));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 这个在多线程下会直接出现以下bug,具体是什么问题和怎么解决没细究:
         *
         * # A fatal error has been detected by the Java Runtime Environment:
         * #
         * #  SIGBUS (0xa) at pc=0x00007fff731d7811, pid=87173, tid=0x0000000000003703
         * #
         * # JRE version: Java(TM) SE Runtime Environment (8.0_191-b12) (build 1.8.0_191-b12)
         * # Java VM: Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode bsd-amd64 compressed oops)
         * # Problematic frame:
         * # C  [libsystem_platform.dylib+0x3811]  _platform_memmove$VARIANT$Ivybridge+0x31
         * #
         * # Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
         * #
         * # An error report file with more information is saved as:
         * # /Users/log/Documents/workspace/idea_workspace/AndroidServer/hs_err_pid87173.log
         * [thread 21763 also had an error]
         * #
         * # If you would like to submit a bug report, please visit:
         * #   http://bugreport.java.com/bugreport/crash.jsp
         * #
         *
         * Process finished with exit code 134 (interrupted by signal 6: SIGABRT)
         *
         */
        private void copyByMappedBuffer() {
            try {
                RandomAccessFile rafi = new RandomAccessFile(srcFile, "r");
                RandomAccessFile rafo = new RandomAccessFile(desFile, "rw");

                FileChannel channelIn = rafi.getChannel();
                FileChannel channelOut = rafo.getChannel();

                long size = endPos - startPos;
                System.out.println(Thread.currentThread().getName() + ": startPos=" + startPos + "; endPos=" + endPos);
                // 这里需注意:通过查看MappedByteBuffer的构造方法可知,它能接受的最大映射内存为Integer.MAX_VALUE=0x7fffffff,
                // 也就是大概2G,所以在切割文件时需要特别处理一下!
                MappedByteBuffer bufferIn = channelIn.map(FileChannel.MapMode.READ_ONLY, startPos, size);
                MappedByteBuffer bufferOut = channelOut.map(FileChannel.MapMode.READ_WRITE, startPos, size);

                for (int i = 0; i < size; i++) {
                    bufferOut.put(bufferIn);
                }
                bufferOut.force();
                rafi.close();
                rafo.close();
                unmap(bufferIn);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 释放MappedByteBuffer资源,需手动自己释放MappedByteBuffer的内存
          */
        private void unmap(MappedByteBuffer mappedByteBuffer) {
            Cleaner cleaner = ((DirectBuffer) mappedByteBuffer).cleaner();
            if (cleaner != null) {
                cleaner.clean();
            }
        }

    }


}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值