技术介绍:
断点下载指的是在文件下载过程中,如果下载中断或失败,比如下载到一半的时候停电了、断网了、不小心退出下载界面了等等,下一次进入下载页面可以从中断或失败的位置继续下载,而无需重新开始下载整个文件。
(注意:本文通过本地文件的拷贝来模拟文件传输的断点过程)
核心想法:通过在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