Java多线程读写文件

一、背景知识

  1. 多线程可以提高任务的执行效率,尤其是CPU计算密集型任务
  2. 读写文件是IO密集型任务
  3. 过多的线程执行同一个任务,并不一定能提高效率,因为线程切换,需要耗时。再比如IO密集型任务,IO是瓶颈,并不是线程越多,IO会越快。
  4. 开多线程去执行任务,需要综合考虑实际情况。

二、问题
最近项目中遇到,安装APK(其中有拷贝apk文件到具体路劲的逻辑),考虑到优化,缩短这里的apk文件拷贝时间。优化过程,使用了断点拷贝和多线程拷贝。

  1. 断点拷贝:使用Java的随机读取RandomAccessFile来实现。通过可以seek()到上次断点的位置,接着继续读写。

        /**
     * 随机读取,支持断点读写
     *
     * @param src 源文件
     * @param dst 目标文件
     * @throws IOException
     */
    @WorkerThread
    public static void copyFileRandomSingleThread(String src, String dst) throws IOException {
    
        RandomAccessFile srcFile = new RandomAccessFile(src, "rw");
        RandomAccessFile dstFile = new RandomAccessFile(dst, "rw");
    
        try {
            long currentDstLength = dstFile.length();
            srcFile.seek(currentDstLength);
            dstFile.seek(currentDstLength);
            Log.d(TAG, "Read " + src + " and Write " + dst + " from " + currentDstLength);
    
            byte[] buffer = new byte[8192];
            int read = -1;
            while ((read = srcFile.read(buffer)) != -1) {
                dstFile.write(buffer, 0, read);
            }
        } catch (IOException e) {
            Log.d(TAG, "copyFileRandom exception, ", e);
        } finally {
            try {
                srcFile.close();
            } catch (IOException e) {
                Log.d(TAG, "copyFileRandom close exception, ", e);
            }
            try {
                dstFile.close();
            } catch (IOException e) {
                Log.d(TAG, "copyFileRandom close exception, ", e);
            }
        }
    
    
    }
    
    
  2. 多线程拷贝(如果必须在多线程拷贝完文件后,再去做其他任务,此处需要用到CountDownlatch进行同步)
    将File分片,然后每个线程去读不同的片。

    /**
     * 多线程拷贝文件
     *
     * @param sourcePath
     * @param targetPath
     * @param threadNums
     */
    private static void copyFileRandomMultiThread(String sourcePath, String targetPath, int threadNums) {
        long totalFileLength = new File(sourcePath).length();
        long currentTargetLength = new File(targetPath).length();
    
    
        long segmentLength = (totalFileLength - currentTargetLength) / (threadNums);
        //同步锁,全部写完,才能继续下一步处理
        CountDownLatch mCountDownLatch;
        //如果不可以整除,则需要多一个线程
        if (totalFileLength % threadNums != 0) {
            mCountDownLatch = new CountDownLatch(threadNums + 1);
        } else {
            mCountDownLatch = new CountDownLatch(threadNums);
        }
    
        int i;
        Log.d(TAG, "threadNums-->" + mCountDownLatch.getCount());
        long start = System.currentTimeMillis();
        for (i = 0; i < threadNums; i++) {
    
            sThreadPool.execute(new CopyFileRunnable(mCountDownLatch, sourcePath, targetPath, i * segmentLength + currentTargetLength, (i + 1) * segmentLength));
    
        }
    
        if (totalFileLength % threadNums != 0) {
            sThreadPool.execute(new CopyFileRunnable(mCountDownLatch, sourcePath, targetPath, i * segmentLength + currentTargetLength, totalFileLength));
        }
        try {
            mCountDownLatch.await();
            Log.d(TAG, "multi thread copyFile costs-->" + (System.currentTimeMillis() - start));
        } catch (InterruptedException e) {
            Log.d(TAG, "countDownLatch exception: ", e);
        }
    }
    
    
    private static class CopyFileRunnable implements Runnable {
        private RandomAccessFile in;
        private RandomAccessFile out;
        private long start;
        private long end;
        private CountDownLatch latch;
    
        /**
         * @param countDownLatch 同步锁
         * @param in             源文件地址
         * @param out            目标文件地址
         * @param start          分段复制的开始位置
         * @param end            分段复制的结束位置
         */
        CopyFileRunnable(CountDownLatch countDownLatch, String in, String out,
                         long start, long end) {
            this.start = start;
            this.end = end;
            try {
                this.in = new RandomAccessFile(in, "rw");
                this.out = new RandomAccessFile(out, "rw");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
    
            this.latch = countDownLatch;
        }
    
        @Override
        public void run() {
            try {
                in.seek(start);
                out.seek(start);
                android.util.Log.d(TAG, "multi thread from: " + start + "; end:" + end);
                int hasRead = 0;
                byte[] buff = new byte[8 * 1024];
                while (start < end && (hasRead = in.read(buff)) != -1) {
                    start += hasRead;
                    out.write(buff, 0, hasRead);
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    in.close();
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
                latch.countDown();
            }
    
        }
    }
    
  3. 完整的程序代码

    public class Utils {
    
    private static final String TAG = Utils.class.getSimpleName();
    
    private static final int CPU_SIZE = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = 2 * CPU_SIZE + 1;
    private static final int MAX_POLL_SIZE = CORE_POOL_SIZE;
    private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() {
        private AtomicInteger index = new AtomicInteger(1);
    
        @Override
        public Thread newThread(@NonNull Runnable r) {
    
            Thread thread = new Thread(r);
            thread.setName("Sandbox_Utils-thread-" + index.getAndIncrement());
            return thread;
        }
    };
    private static final int DEFAULT_THREAD_NUM = 1;
    private static ExecutorService sThreadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POLL_SIZE, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(128), THREAD_FACTORY);
    
    
    /**
     * 随机读取,支持断点读写
     *
     * @param src 源文件
     * @param dst 目标文件
     * @throws IOException
     */
    @WorkerThread
    public static void copyFileRandomSingleThread(String src, String dst) throws IOException {
    
        RandomAccessFile srcFile = new RandomAccessFile(src, "rw");
        RandomAccessFile dstFile = new RandomAccessFile(dst, "rw");
    
        try {
            long currentDstLength = dstFile.length();
            srcFile.seek(currentDstLength);
            dstFile.seek(currentDstLength);
            Log.d(TAG, "Read " + src + " and Write " + dst + " from " + currentDstLength);
    
            byte[] buffer = new byte[8192];
            int read = -1;
            while ((read = srcFile.read(buffer)) != -1) {
                dstFile.write(buffer, 0, read);
            }
        } catch (IOException e) {
            Log.d(TAG, "copyFileRandom exception, ", e);
        } finally {
            try {
                srcFile.close();
            } catch (IOException e) {
                Log.d(TAG, "copyFileRandom close exception, ", e);
            }
            try {
                dstFile.close();
            } catch (IOException e) {
                Log.d(TAG, "copyFileRandom close exception, ", e);
            }
        }
    
    
    }
    
    
    /**
     * copy 文件单线程
     *
     * @param sourcePath
     * @param targetPath
     */
    public static void copyFileRandom(String sourcePath, String targetPath) {
        copyFileRandom(sourcePath, targetPath, 1);
    }
    
    /**
     * @param sourcePath 源文件路径
     * @param targetPath 目标文件路径
     * @param threadNums 设定的线程数
     */
    @WorkerThread
    public static void copyFileRandom(String sourcePath, String targetPath, int threadNums) {
    
        if (threadNums <= 0) {
            threadNums = DEFAULT_THREAD_NUM;
        }
    
        //单线程拷贝
        if (threadNums == 1) {
            try {
                copyFileRandomSingleThread(sourcePath, targetPath);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //多线程拷贝
        } else {
            copyFileRandomMultiThread(sourcePath, targetPath, threadNums);
        }
    
    
    }
    
    /**
     * 多线程拷贝文件
     *
     * @param sourcePath
     * @param targetPath
     * @param threadNums
     */
    private static void copyFileRandomMultiThread(String sourcePath, String targetPath, int threadNums) {
        long totalFileLength = new File(sourcePath).length();
        long currentTargetLength = new File(targetPath).length();
    
    
        long segmentLength = (totalFileLength - currentTargetLength) / (threadNums);
        //同步锁,全部写完,才能继续下一步处理
        CountDownLatch mCountDownLatch;
        //如果不可以整除,则需要多一个线程
        if (totalFileLength % threadNums != 0) {
            mCountDownLatch = new CountDownLatch(threadNums + 1);
        } else {
            mCountDownLatch = new CountDownLatch(threadNums);
        }
    
        int i;
        Log.d(TAG, "threadNums-->" + mCountDownLatch.getCount());
        long start = System.currentTimeMillis();
        for (i = 0; i < threadNums; i++) {
    
            sThreadPool.execute(new CopyFileRunnable(mCountDownLatch, sourcePath, targetPath, i * segmentLength + currentTargetLength, (i + 1) * segmentLength));
    
        }
    
        if (totalFileLength % threadNums != 0) {
            sThreadPool.execute(new CopyFileRunnable(mCountDownLatch, sourcePath, targetPath, i * segmentLength + currentTargetLength, totalFileLength));
        }
        try {
            mCountDownLatch.await();
            Log.d(TAG, "multi thread copyFile costs-->" + (System.currentTimeMillis() - start));
        } catch (InterruptedException e) {
            Log.d(TAG, "countDownLatch exception: ", e);
        }
    }
    
    
    private static class CopyFileRunnable implements Runnable {
        private RandomAccessFile in;
        private RandomAccessFile out;
        private long start;
        private long end;
        private CountDownLatch latch;
    
        /**
         * @param countDownLatch 同步锁
         * @param in             源文件地址
         * @param out            目标文件地址
         * @param start          分段复制的开始位置
         * @param end            分段复制的结束位置
         */
        CopyFileRunnable(CountDownLatch countDownLatch, String in, String out,
                         long start, long end) {
            this.start = start;
            this.end = end;
            try {
                this.in = new RandomAccessFile(in, "rw");
                this.out = new RandomAccessFile(out, "rw");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
    
            this.latch = countDownLatch;
        }
    
        @Override
        public void run() {
            try {
                in.seek(start);
                out.seek(start);
                android.util.Log.d(TAG, "multi thread from: " + start + "; end:" + end);
                int hasRead = 0;
                byte[] buff = new byte[8 * 1024];
                while (start < end && (hasRead = in.read(buff)) != -1) {
                    start += hasRead;
                    out.write(buff, 0, hasRead);
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    in.close();
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
                latch.countDown();
            }
    
        }
    }
    
    
    
    
    
  • 2
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值