JAVA多线程实现断点续传

断点续传:将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度。


RandomAccessFile的四种传输模式
  • r 以只读的方式打开文本,也就意味着不能用write来操作文件
  • rw 读操作和写操作都是允许的
  • rws 每当进行写操作,同步的刷新到磁盘,刷新内容和元数据
  • rwd 每当进行写操作,同步的刷新到磁盘,刷新内容

断点续传实现思路:将大文件均分成几块后,每个线程分别处理一块数据的读取和写入。每次写入都要更新记录的日志文件,断网或暂定后重新开始传输时,根据日志文件的信息,可以接着读取写入数据,不用重头开始传输

代码如下(多线程实现断点续传):

    /**
     * 断点续传
     *
     * @param dataStr   目标文件地址
     * @param targetStr 存放地址
     */
    public static void breakpointResume(String dataStr, String targetStr) {
        File dataFile = new File(dataStr);
        long length = dataFile.length();
        int threadNum = 4;//指定线程数
        //每个线程均分文件大小,且向上取整
        long part = (long) Math.ceil(length / threadNum);
        //线程减法计数器
        CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        Instant beginTime = Instant.now();
        //记录传输的日志文件
        File logFile = new File(targetStr + ".log");
        String[] splitData = null;//不是null就需要断点续传
        BufferedReader reader = null;
        try {
            if (logFile.exists()) {
                //存在日志文件,需要进行断点续传
                reader = new BufferedReader(new FileReader(logFile));
                String data = reader.readLine();
                splitData = data.split(",");
            } else {
                //不存在日志文件,创建日志文件
                logFile.createNewFile();
            }
            Map<Integer, Long> maps = new ConcurrentHashMap<>();
            for (int i = 0; i < threadNum; i++) {
                final int k = i;
                System.out.println("线程正在执行任务:" + k);
                String[] finalData = splitData;
                new Thread(() -> {
                    RandomAccessFile inFile = null;
                    RandomAccessFile outFile = null;
                    RandomAccessFile rafLog = null;
                    try {
                        inFile = new RandomAccessFile(dataFile, "r");//读
                        outFile = new RandomAccessFile(targetStr, "rw");//写
                        rafLog = new RandomAccessFile(logFile, "rw");//操作日志文件的流
                        //确定每个线程读取文件的开始和结束的位置,有断点续传就从日志文件取出的位置开始读取
                        inFile.seek(finalData == null ? k * part : Long.parseLong(finalData[k]));//设置每个线程读取的启始位置
                        outFile.seek(finalData == null ? k * part : Long.parseLong(finalData[k]));//设置每个线程写入的启始位置
                        byte[] bytes = new byte[1024 * 10];//每次读取字节大小
                        int len = -1, allLen = 0;
                        while (true) {
                            len = inFile.read(bytes);//从磁盘读取到缓存
                            if (len == -1) { //数据读完,结束
                                break;
                            }
                            //如果不等于 -1,把每次读取的字节累加
                            allLen = allLen + len;
                            //将读取的字节数放入到map中
                            maps.put(k, allLen + (finalData == null ? k * part : Long.parseLong(finalData[k])));//每个线程的绝对偏移量
                            outFile.write(bytes, 0, len);//从缓存写入到磁盘
                            //将map中的字节日志信息数据写入磁盘
                            StringJoiner stringJoiner = new StringJoiner(",");
                            maps.forEach((key, value) -> stringJoiner.add(String.valueOf(value)));
                            //将日志信息写入磁盘
                            rafLog.seek(0);//覆盖之前的日志信息
                            rafLog.write(stringJoiner.toString().getBytes("UTF-8"));
                            /**
                             * 当前线程读取的内容
                             *  allLen + (k * part)
                             *  或
                             *  allLen + finalData[k] 日志文件里面的偏移量
                             *  >=
                             *  下个线程的起始部分((k + 1) * part)
                             *  当前线程就不再读取写入数据,结束任务
                             */
                            if (allLen + (finalData == null ? k * part : Long.parseLong(finalData[k])) >= (k + 1) * part) {
                                break;
                            }
                        }

                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        //关流
                        if (null != outFile && null != inFile && null != rafLog) {
                            try {
                                outFile.close();
                                inFile.close();
                                rafLog.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        countDownLatch.countDown();//减一
                    }
                }).start();
            }

            //主线程要等到线程计数器归零,再继续往下执行
            countDownLatch.await();
            Instant endTime = Instant.now();
            System.out.println("总耗时:" + (Duration.between(beginTime, endTime).toMillis()) + "毫秒");
            //删除日志文件
            logFile.delete();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != reader) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

运行如下:

6个多G的资料  >_<
线程正在执行任务:0
线程正在执行任务:1
线程正在执行任务:2
线程正在执行任务:3
总耗时:60039毫秒

CPU 、内存、磁盘都被利用起来了:
在这里插入图片描述

内容转自B站up视频

  • 3
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值