什么是断点续传?
引用百度百科:对断点续传的定义
个人理解:如果我们要在项目的媒资管理部分,上传视频文件(通常这类视频文件都比较大),http协议本身对上传文件大小没有限制,但是客户的网络环境质量,电脑硬件环境参差不齐,可能会导致一个大文件快上传完了出现断网的情况,从而导致文件没有上传成功,需要客户重新上传,用户体验非常差,所以对于大文件上传的要求是能做到 断点续传
断点续传流程图如下图:
简要概述实现步骤:
- 前端上传完先把文件分成块
- 一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传
- 各分块上传完成最后在服务端合并文件
(为了方便理解,我用Java代码的方法 测试文件的分块与合并)
先进行大文件资源 分块操作:
1.导入一系列的包
package com.xuecheng.media;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.*;
导入多个外部和Java自带的库,其中org.apache.commons.codec.digest.DigestUtils:用于生成文章的摘要,能够在断点续传中验证文件的完整性
2.定义相关的类和方法
public class BigFileTest {
//测试文件分块方法
@Test
public void testChunk() throws IOException {
定义一个公共类BigFileTest,用于包含文件处理相关的测试方法,并且定义了一个testChunk方法,该方法用于执行文件分块操作,并声明了可能抛出IOException异常,因为文件操作可能会失败
3.创建分块文件
File sourceFile = new File("d:/develop/bigfile_test/nacos.mp4");
String chunkPath = "d:/develop/bigfile_test/chunk/";
File chunkFolder = new File(chunkPath);
if (!chunkFolder.exists()) {
chunkFolder.mkdirs();
}
创建一个File对象,表示要对d:/develop/bigfile_test/路径下的nacos.mp4源文件进行分块,并创建一个File对象,表示分块存储的文件夹,如果分块文件夹不存在,则创建它
4.对大文件分成文件块
//分块大小
long chunkSize = 1024 * 1024 * 1;
//分块数量
long chunkNum = (long) Math.ceil(sourceFile.length() * 1.0 / chunkSize);
System.out.println("分块总数:" + chunkNum);
然后对 文件进行分割,定义每个分块的大小,通过源文件的长度除以分块大小(要采用向上取整噢)使用Math.ceil方法向上取整,计算出分块的总数
5.进行对文件的读取写入
//缓冲区大小
byte[] b = new byte[1024];
//使用RandomAccessFile访问文件
RandomAccessFile raf_read = new RandomAccessFile(sourceFile, "r");
//分块
for (int i = 0; i < chunkNum; i++) {
//创建分块文件
File file = new File(chunkPath + i);
if (file.exists()) {
file.delete();
}
boolean newFile = file.createNewFile();
if (newFile) {
//向分块文件中写数据
RandomAccessFile raf_write = new RandomAccessFile(file, "rw");
int len = -1;
while ((len = raf_read.read(b))!= -1) {
raf_write.write(b, 0, len);
if (file.length() >= chunkSize) {
break;
}
}
raf_write.close();
System.out.println("完成分块" + i);
}
}
raf_read.close();
然后进行缓冲区与文件读取写入,以上代码块创建了一个大小为1024字节的缓冲区数组b,用于读取源文件数据,并且以只读模式打开源文件,然后进行分块操作
循环分块数量chunkNum(对源文件进行分块后得到的分块总数),if循环判断分块文件是否创建成功,如果创建成功,则从源文件中读取数据到缓冲区,直到读取到文件末尾
再进行分块文件 合并操作:
各个文件块读取完毕后,就可以进行 文件合并 操作,分块合并步骤
File chunkFolder = new File("D:\\...\\chunk\\"); // 分块文件目录
File sourceFile = new File("D:\\...\\星际牛仔1.mp4"); // 原始文件
File mergeFile = new File("D:\\...\\星际牛仔1-1.mp4"); // 合并后的文件
mergeFile.createNewFile(); // 创建空的目标文件
定义分块文件的存储目录,原始文件路径和合并后的文件路径,并创建一个空的目标文件
RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw"); // 以读写模式打开合并文件
byte[] buffer = new byte[1024]; // 读写缓冲区(1KB)
初始化写入流,RandomAccessFile是一个用于随机访问文件的类,适合大文件操作,“rw”表示读写模式,byte[ ] buffer = new byte[1024]这段代码创建了一个字节数组,作为读写缓冲区(临时存储从文件或网络中读取的数据),每次读取1KB数据,减少磁盘I/O次数
//listFiles()表示返回目录中所有文件和子目录的File数组,files是包含所有分块文件的数组
File[] files = chunkFolder.listFiles();
//将数组转换为list集合,fileList是包含所以分块文件的list<File>
List<File> fileList = Arrays.asList(files);
//然后对List进行排序,Comparator.comparingInt创建一个比较器,按照指定规则排序(将文件名转换为整数进行排序)
Collections.sort(fileList, Comparator.comparingInt(o -> Integer.parseInt(o.getName())));
对分块文件进行排序,o -> Integer.parseInt(o.getName()):是一个Lambda表达式,用于将文件名转换为整数进行排序
//循环遍历分块文件列表
for (File chunkFile : fileList) {
//以只读模式打开分块文件 "r"表示以只读模式打开
RandomAccessFile raf_read = new RandomAccessFile(chunkFile, "r");
//读取分块文件内容并写入合并文件
int len;
while ((len = raf_read.read(buffer)) != -1) {
raf_write.write(buffer, 0, len);
}
raf_read.close(); // 关闭分块文件流
}
raf_write.close(); // 关闭合并文件流
这段代码遍历分块文件列表,并以只读模式打开分块文件,然后读取分块文件内容并写入到合并文件,最后关闭分块文件流和合并文件流
这里我再总结一下掌握断点续传的技术,我们能够将多个分块文件合并成一个完整文件,避免用户在下载过程因为网络,系统等一系列原因导致下载中断,而需重新开始下载的情况。而是可以直接从中断处继续下载,节省了我们时间和网络资源