Java文件断点续传
断点续传实现思路:将大文件均分成几块后,每个线程分别处理一块数据的读取和写入。每次写入都要更新记录的日志文件,断网或暂定后重新开始传输时,根据日志文件的信息,可以接着读取写入数据,不用重头开始传输。文章来源地址https://www.toymoban.com/news/detail-519652.html
package com.demo;
import java.io.*;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
/**
* @descpription: 文件断点续传
* @date 2023/3/4
*/
public class RandomAccessFileDemo {
/**
* 指定线程数
*/
private static final Integer THREAD_NUM = 2;
public static void main(String[] args) {
String sourceStr = "~/Downloads/视频/视频.zip";
String targetStr = "~/Desktop/视频123.zip";
breakpointResume(sourceStr,targetStr);
}
/**
* 断点续传
*
* @param sourceStr 目标文件地址
* @param targetStr 存放地址
*/
public static void breakpointResume(String sourceStr, String targetStr) {
File dataFile = new File(sourceStr);
long length = dataFile.length();
//每个线程均分文件大小,且向上取整
long part = (long) Math.ceil(length / THREAD_NUM);
//线程减法计数器
CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);
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 < THREAD_NUM; 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();
}
}
}
}
}
文章来源:https://www.toymoban.com/news/detail-519652.html
到了这里,关于Java文件断点续传的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!