开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 14 天,点击查看活动详情
试想一下,当我们在下载将近10个G的王者荣耀时,突然出现网络环境异常导致下载失败,需要从头下载,你说气不气?
断点续传就是来解决这种问题的。出现因网络异常等导致文件传输失败时,引入了断点续传后,就不需要从头进行了,可以从已经上传、下载的部分继续未完成的上传下载任务。
对于大文件来讲,断点续传会将大文件拆分成多个小文件进行传输,即拆分成固定大小的块。接着,将这些小文件进行合并。
那具体来讲,断点续传到底怎么做呢?
大文件拆分成块
- 导入源文件,并获取源文件长度。
- 定义分的块的大小,通过源文件大小与单个块的大小确定块的数量。
- 通过流对象来读取源文件,向分块文件写数据,达到分块的大小后就不再写了。
具体代码如下,注释已经写的非常全了,我就不一块一块拿出来去介绍了。
public void testChunk() throws IOException{
//源文件
File sourceFile = new File("/Users/laoli/Desktop/project2022/testvideo/nacos.mov");
sourceFile.createNewFile();
//定义分块文件存储路径
File chunkFolderPath = new File("/Users/laoli/Desktop/project2022/testvideo/chunk/");
if(!chunkFolderPath.exists()){
chunkFolderPath.mkdirs();//不存在分块目录就进行创建
}
//确定分块大小:1M
int chunkSize = 1024*1024*1;
//分块数量,需要向上转型,即数量在0-1之间就是1
long chunkNum = (long) Math.ceil(sourceFile.length() * 1.0 / chunkSize);
//思路:通过流对象读取源文件,向分块文件去写数据,达到分块的大小就不再写了.
//- 读需要建一个输入流:随机读文件的内容
RandomAccessFile raf_read = new RandomAccessFile(sourceFile, "r");//r是可读
//建立读写的缓冲取
byte[] b= new byte[1024];
for (int i = 0; i < chunkNum; i++) {
//分块文件,命名
File file = new File("/Users/laoli/Desktop/project2022/testvideo/chunk/" + i);
//如果这个分块文件已经存在,我们就删掉
if(file.exists()){
file.delete();
}
//先将文件创建出来,刚创建出来的话在磁盘上就存在了,只不过是个空的
boolean newFile = file.createNewFile();
//若文件创建成功,就可以向文件中去写数据了
if (newFile){
//向分块文件写数据的流对象
RandomAccessFile raf_write = new RandomAccessFile(file, "rw");//r是可读
//向文件写数据:读写用流需要建一个缓冲区byte[]
int len = -1; //-1就是读到末尾
//先读再写。读到缓冲区。
//没读到末尾就继续往里面写
while ((len = raf_read.read(b))!=-1){
raf_write.write(b,0,len); //从0写,写一个字节:就是把缓冲区的全部写进去
if(file.length()>=chunkSize){
//达到了分块的大小就不写了
break;
}
}
//写完关写流
raf_write.close();
}
}
//所有的都弄完,关读流
raf_read.close();
}
文件拆分的运行结果如下:
文件合并
小文件合并的思路与大文件拆块异曲同工。这里我们仍需要源文件(文件拆分前的那个文件),另外需要额外创建合并后的文件。
小文件合并成大文件后,还需要对文件进行校验,通过md5值来校验源文件和合并后的文件,以保证传来的是同一个文件。不校验的话肯定会存在很多问题的,比如突然遭到网络攻击,黑客用病毒替换掉了一个小文件,你根本察觉不到。相信点到这里,jym也明白了为什么源文件仍然需要了。
文件合并还需要注意是小文件的顺序问题:我在对大文件进行拆分的时候,是将拆出的小文件通过数字递增(1,2,3,4...)来命名的,所以在顺序问题上,先拿到分块文件列表并按文件名递增进行排序来处理顺序问题。详细代码如下:
public void testMerge() throws IOException{
//源文件
File sourceFile = new File("/Users/laoli/Desktop/project2022/testvideo/nacos.mov");
sourceFile.createNewFile();
//定义分块文件存储路径
File chunkFolderPath = new File("/Users/laoli/Desktop/project2022/testvideo/chunk/");
if(!chunkFolderPath.exists()){
chunkFolderPath.mkdirs();//不存在分块目录就进行创建
}
//合并后的文件
File mergeFile = new File("/Users/laoli/Desktop/project2022/testvideo/nacos_01.mov");
mergeFile.createNewFile(); //把合并文件先创建出来
//思路:通过流对象读取分块文件,向合并文件去写数据,将所有分块文件依次写入.
// -获取分块列表:该列表是有顺序的,我们可以按文件名升序
File[] chunkFiles = chunkFolderPath.listFiles();
List<File> chunkFileList = Arrays.asList(chunkFiles);
Collections.sort(chunkFileList, new Comparator<File>() {
//比较器
@Override
public int compare(File o1, File o2) {
//转数字是因为我们前面拆文件,命名成数字了
return Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName());
}
});
//依次读取分块文件
// -创建合并文件的流对象,写流
RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");//r是可读
//建立读写的缓冲取
byte[] b= new byte[1024];
for (File file : chunkFileList) {
//读取:我们需要建输入流来读
RandomAccessFile raf_read = new RandomAccessFile(file, "r");//r是可读
//向合并文件去写
int len = -1;
while ((len = raf_read.read(b))!=-1){
//向合并文件写数据
raf_write.write(b,0,len);
}
}
//写完之后还需要校验一下是否与原始文件一样
//比较两者的md5值就行
FileInputStream sourceFileStream = new FileInputStream(sourceFile);//源文件的流对象
FileInputStream mergeFileStream = new FileInputStream(mergeFile);//合并后的文件的流对象
String sourceFileMD5Hex = DigestUtils.md5Hex(sourceFileStream);//源文件的md5值
String mergeFileMD5Hex = DigestUtils.md5Hex(mergeFileStream); //合并后文件的md5值
if (sourceFileMD5Hex.equals(mergeFileMD5Hex)){
System.out.println("合并成功"); //两者md5相同就合并成功
}
}
最后是文件合并的运行结果:
相信讲到这里,jym已经掌握断点续传的原理与实现了。