java多线程 分片下载文件
先发送请求探测是否支持分片下载,同时从响应头中获取文件总大小然后分片,获取到每页的开始结束位置提交分片任务到线程池中,await等待所有分片任务下载完成,进行合并任务基础bean:测试......
·
核心
先发送请求探测是否支持分片下载,同时从响应头中获取Content-Range
文件总大小
然后分片,获取到每页的开始结束位置
提交分片任务到线程池中,await等待所有分片任务下载完成,进行合并任务
实现
基础bean:
@Data
public class SlicePageInfo {
private CopyOnWriteArrayList<SliceInfo> sliceInfoList;
private Long page;
}
@Data
@AllArgsConstructor
public class SliceInfo {
private long start;
private long end;
private long page;
}
@Slf4j
public class DownLoadSliceUtil {
/**
* 文件分片大小,大文件可以调整
*/
public final static long PER_PAGE = (long) 1024 * 1024;
private static final RestTemplate REST_TEMPLATE = new RestTemplate();
public static ResponseEntity<byte[]> getFileContentByUrlAndPosition(String downloadUrl, long start, long end) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Range", "bytes=" + start + "-" + end);
org.springframework.http.HttpEntity<Object> httpEntity = new org.springframework.http.HttpEntity<>(httpHeaders);
return REST_TEMPLATE.exchange(downloadUrl, HttpMethod.GET, httpEntity, byte[].class);
}
public static void download(String tempPath, String downloadUrl, SliceInfo sliceInfo, String fName) {
log.info("下载分片文件:{},分片序号 {}", fName, sliceInfo.getPage());
// 创建一个分片文件对象
File file = new File(tempPath, sliceInfo.getPage() + "-" + fName);
if (file.exists() && file.length() == PER_PAGE) {
log.info("此分片文件 {} 已存在", sliceInfo.getPage());
return;
}
try (FileOutputStream fos = new FileOutputStream(file);) {
ResponseEntity<byte[]> responseEntity = DownLoadSliceUtil.getFileContentByUrlAndPosition(downloadUrl, sliceInfo.getStart(), sliceInfo.getEnd());
byte[] body = responseEntity.getBody();
if (body != null && body.length == 0) {
log.warn("分片文件:{},没有内容", file.getName());
return;
}
// 将分片内容写入临时存储分片文件
fos.write(body);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void mergeFileTranTo(String tempPath, String fName, long page) {
try (FileChannel channel = new FileOutputStream(new File(tempPath, fName)).getChannel()) {
for (long i = 1; i <= page; i++) {
File file = new File(tempPath, i + "-" + fName);
FileChannel fileChannel = new FileInputStream(file).getChannel();
long size = fileChannel.size();
for (long left = size; left > 0; ) {
left -= fileChannel.transferTo((size - left), left, channel);
}
fileChannel.close();
file.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Slf4j
public class DownLoadEngine {
private static final ExecutorService executorService = ExecutorFactory.newFixedExecutorService(5);
/**
* 分片下载
*
* @param downloadUrl 下载链接
* @param tempPath 临时文件路径
* @param fileName 文件名称
*/
public static void downloadSlice(String downloadUrl, String tempPath, String fileName) {
//大小探测
ResponseEntity<byte[]> responseEntity = DownLoadSliceUtil.getFileContentByUrlAndPosition(downloadUrl, 0, 1);
HttpHeaders headers = responseEntity.getHeaders();
String rangeBytes = headers.getFirst("Content-Range");
if (Objects.isNull(rangeBytes)) {
log.error("url:{},不支持分片下载", downloadUrl);
return;
}
long allBytes = Long.parseLong(rangeBytes.split("/")[1]);
log.info("文件总大小:{}M", allBytes / 1024.0 / 1024.0);
//分页
SlicePageInfo slicePageInfo = splitPage(allBytes);
CountDownLatch countDownLatch = new CountDownLatch(Math.toIntExact(slicePageInfo.getPage()));
CountDownLatch mainLatch = new CountDownLatch(1);
executorService.execute(() -> {
try {
countDownLatch.await();
DownLoadSliceUtil.mergeFileTranTo(tempPath, fileName, slicePageInfo.getPage());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mainLatch.countDown();
}
});
for (SliceInfo sliceInfo : slicePageInfo.getSliceInfoList()) {
executorService.submit(() -> {
try {
DownLoadSliceUtil.download(tempPath, downloadUrl, sliceInfo, fileName);
} catch (Exception e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
});
}
try {
mainLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 文件分片下载
* @param allBytes 文件总大小
* @return /
*/
public static SlicePageInfo splitPage(long allBytes) {
CopyOnWriteArrayList<SliceInfo> list = new CopyOnWriteArrayList<>();
long size = allBytes;
long left = 0;
long page = 0;
while (size > 0) {
long start = 0;
long end;
start = left;
//分页
if (size < PER_PAGE) {
end = left + size;
} else {
end = left += PER_PAGE;
}
size -= PER_PAGE;
page++;
if (start != 0) {
start++;
}
log.info("页码:{},开始位置:{},结束位置:{}", page, start, end);
final SliceInfo sliceInfo = new SliceInfo(start, end, page);
list.add(sliceInfo);
}
SlicePageInfo slicePageInfo = new SlicePageInfo();
slicePageInfo.setSliceInfoList(list);
slicePageInfo.setPage(page);
return slicePageInfo;
}
}
测试
@Test
public void downloadFile() {
DownLoadEngine.downloadSlice("https://dldir1.qq.com/qqfile/qq/PCQQ9.6.1/QQ9.6.1.28732.exe", "D:\temp", "qq.exe");
}
更多推荐
已为社区贡献3条内容
所有评论(0)