首页 > 编程语言 >java断点下载文件(整合多线程)

java断点下载文件(整合多线程)

时间:2023-10-03 10:45:58浏览次数:32  
标签:startByte java stopIndex 断点 long currentIndex 多线程 redisUtil 下载

技术介绍:

断点下载指的是在文件下载过程中,如果下载中断或失败,比如下载到一半的时候停电了、断网了、不小心退出下载界面了等等,下一次进入下载页面可以从中断或失败的位置继续下载,而无需重新开始下载整个文件。

 

(注意:本文通过本地文件的拷贝来模拟文件传输的断点过程)

 

核心想法:通过在redis中保存一个实时变量,来记录此时此刻文件下载到了哪个位置,手动制造错误后,重新启动下载程序,程序获取位置变量,再通过随机访问文件的特性从这个位置开始重新下载。

 

核心代码:

  主测试类:

@Resource
public RedisUtil redisUtil;

private static final String FILE_PATH = "被拷贝文件路径";
private static final String SAVE_PATH = "拷贝文件路径";
private static final int NUM_THREADS = 3;
private static int stopIndex;

@RequestMapping(value = "/")
public void EndPointTest() {
    try {
        //已经在redis中手动执行redisUtil.set("stopIndex",-1);当stopIndex为-1的时候代表没有发生过断点
        File file = new File(FILE_PATH);
        long fileSize = file.length();
        long chunkSize = fileSize / NUM_THREADS;

        stopIndex = Integer.parseInt(redisUtil.get("stopIndex"));
        if (stopIndex > 0) {
            System.out.println("执行断点下载"); //最好使用log.info
            isEndPoint = true;
            for (int i = 0; i < NUM_THREADS; i++) { 
                long startByte = (i * chunkSize);
                long endByte = (i == NUM_THREADS - 1) ? fileSize - 1 : (i + 1) * chunkSize - 1;
                Thread mergerThread = new MergerThread2(FILE_PATH, SAVE_PATH, startByte, endByte, this.redisUtil);
                mergerThread.start();
            }
        } else {
            System.out.println("执行正常下载");
            isEndPoint = false;
            for (int i = 0; i < NUM_THREADS; i++) {
                long startByte = i * chunkSize;
                long endByte = (i == NUM_THREADS - 1) ? fileSize - 1 : (i + 1) * chunkSize - 1;
                Thread mergerThread = new MergerThread2(FILE_PATH, SAVE_PATH, startByte, endByte, this.redisUtil);
                mergerThread.start();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 

线程业务:

public class MergerThread2 extends Thread {
private String filePath; private String savePath; private long startByte; private long endByte; private RedisUtil redisUtil; RandomAccessFile inputFile; RandomAccessFile outputFile; public MergerThread2() { } public MergerThread2(String filePath, String savePath, long startByte, long endByte, RedisUtil redisUtil) { this.filePath = filePath; this.savePath = savePath; this.startByte = startByte; this.endByte = endByte; this.redisUtil = redisUtil; } public void run() { try { byte[] buffer = new byte[1]; // 不要设为1024,这样read方法如果只读了2个数据,那就会有1022个0填充数组,调用write(buffer)后, // 文件中会出现大量空白,设置为1是想读一个字节后立马写一个字节 long currentIndex = 0; //与buffer一样,千万不要写在MergerThread2实现类里面,而要写在run方法里面,不然三个线程在执行后会把 //buffer和currentIndex变一样,另外currentIndex用于记录已经在文件的什么地方了。 inputFile = new RandomAccessFile(filePath, "r"); outputFile = new RandomAccessFile(savePath, "rw"); int bytesRead = 0; //表示调用read方法后读到了多少字节,这个例子中这个值都是1,但是读到末尾时会变成-1。 long bytesToRead = endByte - startByte + 1;//表示一共有多少字节需要读 if (DemoController.isEndPoint) { currentIndex = Integer.parseInt(redisUtil.get("stopIndex")); inputFile.seek(startByte + currentIndex); //在重启程序后,startByte依然是三个线程三等分文件后分别的起始位置, // 要手动加上上一次发生断点时的位置,从那里开始读,并从被写入文件的同样位置开始写 outputFile.seek(startByte + currentIndex); while (bytesToRead > 0 && bytesRead!=-1) { //必须要加-1判断,不然读最后一部分数据的线程会死循环。 bytesRead = inputFile.read(buffer, 0, 1); outputFile.write(buffer, 0, 1); currentIndex++; redisUtil.set("stopIndex", String.valueOf(currentIndex)); bytesToRead -= bytesRead; //Thread.sleep(1000); } inputFile.close(); outputFile.close(); redisUtil.set("stopIndex", "-1"); //重置 } else { inputFile.seek(startByte); //正常下载时,三个线程的startByte是不一样的 outputFile.seek(startByte); while (bytesToRead > 0) { bytesRead = inputFile.read(buffer, 0, 1); outputFile.write(buffer, 0, 1); currentIndex++; redisUtil.set("stopIndex", String.valueOf(currentIndex)); bytesToRead -= bytesRead; Thread.sleep(1000); //十秒读一个字节,因为文件很小,不写这句的话文件会瞬间完成拷贝,来不及手动关闭程序设置断点,一定要这样读写文件,注意不要 for(int i=0;i<?;i++){ outputFile.write(buffer[i]); } } } inputFile.close(); outputFile.close(); } catch (Exception e) { e.printStackTrace(); } finally { System.exit(0); } } }

 

当然了,这只是一种最简单的案例,难免错误重重,仅供批评!

标签:startByte,java,stopIndex,断点,long,currentIndex,多线程,redisUtil,下载
From: https://www.cnblogs.com/xialang/p/17740868.html

相关文章

  • 基于Java的高校学生综合测评管理系统的设计与实现(亮点:选课、课程评分、各类活动申请
    (高校学生综合测评管理系统)二、我的优势2.1自己的网站网站上传的项目均为博主自己收集和开发的,质量都可以得到保障,适合自己懂一点程序开发的同学使用!2.2自己的小程序(小蔡coding)<imgsrc="https://img-blog.csdnimg.cn/img_convert/3df3eff92652bb0959df5e3d738d05c9.png"......
  • java本地文件多线程拷贝
    简单介绍:本地文件多线程拷贝是指通过多个线程同时进行文件复制操作。传统的文件复制操作往往是串行进行的,当需要复制单个大文件时,复制速度往往会比较慢。而采用多线程进行文件拷贝可以提高效率。通过同时创建多个线程,每个线程负责复制不同的文件或者不同的文件片段,可以充分利用计......
  • 三个Java入门项目
    Java实现简单计算器参考链接......
  • redis7源码分析:redis 多线程模型解析
    多线程模式中,在main函数中会执行InitServerLastvoidInitServerLast(){bioInit();//关键一步,这里启动了多条线程,用于执行命令,redis起名为IO线程initThreadedIO();set_jemalloc_bg_thread(server.jemalloc_bg_thread);server.initial_memory_usage=......
  • JAVA--异常
    什么是反射?可以从类里面将该类的成员方法成员变量,构造方法的信息给拿出来使用可以获取成员变量,构造方法,成员方法的所有信息.学习反射应该学习如何获取和解剖获取class对象的三种方式在源代码阶段使用Class.forName("全类名");(最常用)在加载阶段使用A.class(一......
  • Java---异常
    14.1概念在日常编码中或多或少都会现一些问题,这些问题有的是错误和有的是异常(不正常)。日常编码这种问题分为两类:1、错误Error2、异常Exception错误在jvm,硬件层面报出的问题异常是代码运行层面bug:1、编码语法错误(很好解决)2、运行时异常这种异常也好解决,根据提示去做修......
  • java对浮点数保留合适的位数
    importjava.text.DecimalFormat;publicclassMain{publicstaticvoidmain(String[]args){doublenumber=3.1415926;DecimalFormatdf=newDecimalFormat("#.0#");Stringformatted;if(nu......
  • Java面试突击题库
                 ......
  • java——mysql随笔——SQL优化&锁
               插入数据SQL优化:              主键优化:                                    order  by优化:       ......
  • Java基础:meta-info.md文件的作用
    作用概述meta-info.md文件通常是一个用于提供关于软件包或项目的元信息的文本文件。它可以包含各种类型的信息,具体取决于项目的需求和约定。一些常见的元信息包括:项目说明:描述项目的目的、功能、特点等。这可以是一个简短的描述或详细的文档。版本信息:指定软件包或项目的版本号、......