前言
我们每个人应该都使用过专门的下载工具,像迅雷、QQ旋风、百度网盘等。我们在下载或上传文件的时候,有时候可能由于网络的原因,还没下载完毕或者上传完毕,比如我现在使用迅雷下载一个文件,下载到了80%的时候断网了,我把电脑关了,等到有网络的时候,又打开电脑,打开迅雷,继续下载,这时并不是从头开始下载,而是从80%的时候继续下载,一直到下载完毕。实际上这就是一个断点续传的场景。
断点续传
通常视频文件都比较大,所以对于上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制,但是客户的网络环境质量、电脑硬件环境等参差不齐,如果一个大文件还没上传完毕而网断了,电断了,没有上传完成,需要客户重新上传,这是致命的,所以对于大文件上传的要求最基本的是断点续传。
1、概念
FTP(文件传输协议的简称)(File Transfer Protocol、 FTP)客户端软件断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度。
小结:断点续传的关键是对资源进行分块,然后对块文件进行上传或下载。
2、上传或下载流程
以下对上传流程进行说明,下载类似:
说明:
- 上传前先把文件分成块。
- 一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传。
- 各分块上传完成最后合并文件。
3、文件的分块和合并
为了更好的理解文件分块上传的原理,下边用java代码测试文件的分块与合并。
(1)文件分块
分块流程如下:
- 获取源文件长度。
- 根据设定的分块文件的大小计算出块数。
- 从源文件读数据依次向每一个块文件写数据。
下面用代码实现视频文件的分块:
public class Test {
// 测试文件的分块
public static void main(String[] args) throws IOException {
// 源文件对象
File file = new File("E:/ffmpeg/无上神帝01.mp4");
// 块文件目录
String chunkFolder = "E:/ffmpeg/chunk/";
File file2 = new File(chunkFolder);
if (!file2.exists()) {
file2.mkdirs();
}
// 定义每个块文件的大小,这里规定为1M
long chunkSize = 1 * 1024 * 1024;
// 源文件的大小
double fileSize = file.length() * 1.0;
// 计算块数,向上取整
long chunkNum = (long) Math.ceil(fileSize / chunkSize);
// 创建文件读取对象
RandomAccessFile raf_read = new RandomAccessFile(file, "r");
// 缓冲区
byte[] bys = new byte[1024];
System.out.println("开始分块......");
for (int i = 0; i < chunkNum; i++) {
// 块文件对象
File chunkFile = new File(chunkFolder + i);
// 块文件的写对象
RandomAccessFile raf_write = new RandomAccessFile(chunkFile, "rw");
int len = -1;
while ((len = raf_read.read(bys)) != -1) {// 读取
// 写入
raf_write.write(bys, 0, len);
// 达到1M时开始写下一块
if (chunkFile.length() >= chunkSize) {
break;
}
}
// 每个写入对象都需要关闭
raf_write.close();
}
// 全部读写完毕后,关闭读对写
raf_read.close();
System.out.println("源文件已分块完毕,请查看!");
}
}
执行,控制台:
查看:
一共是分了56块,除了最后一块,每一块的大小都是1M。分完块之后,可以对每一块进行上传并且记录位置,这样即使没有上传完毕,下载可以接着断开的地方继续上传,而不必从头开始传。
(2)文件合并
合并流程如下:
- 找到要合并的文件并按文件合并的先后进行排序。
- 创建合并文件。
- 依次从合并的文件中读取数据向合并文件写入数据。
- 删除块文件。
以下为代码:
public class Test {
// 测试块文件的合并
public static void main(String[] args) throws IOException {
// 块文件所在目录
String chunkFolder = "E:/ffmpeg/chunk/";
// 文件对象
File file = new File(chunkFolder);
// 获取目录列表
File[] files = file.listFiles();
// 数组转集合
List<Object> list = Arrays.asList(files);
// 块文件按名称排序
Collections.sort(list, new Comparator<Object>() {
// 排序规则为按名称升序
@Override
public int compare(Object o1, Object o2) {
// 类型转换
File f1 = (File) o1;
File f2 = (File) o2;
if (Integer.parseInt(f1.getName()) > Integer.parseInt(f2.getName())) {
return 1;
}
return -1;
}
});
// 合并后的文件对象
File mergeFile = new File("E:/ffmpeg/合并文件.mp4");
// 创建对象
mergeFile.createNewFile();
// 写对象
RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");
byte[] bys = new byte[1024];
System.out.println("开始合并块文件......");
for (Object obj : list) {
File chunkFile = (File) obj;
// 读对象
RandomAccessFile raf_read = new RandomAccessFile(chunkFile, "r");
int len = -1;
while ((len = raf_read.read(bys)) != -1) {
raf_write.write(bys, 0, len);
}
// 每个读对写都需要关闭
raf_read.close();
}
// 全部读写完毕后关闭写对象
raf_write.close();
// 将块文件全部删除
for (File f : files) {
f.delete();
}
// 目录删除
file.delete();
System.out.println("块文件合并完成,请查看!");
}
}
执行,控制台:
查看:
原来的chunk目录已经删除了,而且生成了合并文件,打开文件看是否能播放:
可以播放,没问题。全部分块上传完毕后,可以合并分块小文件为一个大文件,然后删除掉块文件。
4、总结
断点续传在大文件的上传和下载中尤为常见,理解断点续传的工作原理,并掌握分块和合并,提升用户的上传、下载体验。