什么是断点续传
通常视频文件都比较大,所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制,但是客户的网络环境质量、电脑硬件环境等参差不齐,如果一个大文件快上传完了网断了没有上传完成,需要客户重新上传,用户体验非常差,所以对于大文件上传的要求最基本的是断点续传。
什么是断点续传:
引用百度百科:断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载,断点续传可以提高节省操作时间,提高用户体验性。
断点续传流程如下图:
流程如下:
1、前端上传前先把文件分成块
2、一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传
3、各分块上传完成最后在服务端合并文件
文件分块
文件分块的流程如下:
1、获取源文件长度
2、根据设定的分块文件的大小计算出块数
3、从源文件读数据依次向每一个块文件写数据。
/** * 文件分块上传测试 */ @Test public void testChunk(){ //获取源文件 File sourceFile = new File("B:\\workspace\\test\\you.ncm"); //源文件字节大小 long length = sourceFile.length(); //分块文件目录 String chunkPath="B:\\workspace\\test\\chunk\\"; File chunkFolder = new File(chunkPath); //检查目录是否存在 if (!chunkFolder.exists()) { //不存在就创建 chunkFolder.mkdirs(); } //分块大小 long chunkSize = 1024*1024*1; //分块数量 long chunkNum = (long) Math.ceil(length * 1.0 / chunkSize); //缓冲区大小 byte[] b = new byte[1024]; //使用RandomAccessFile访问文件 try { RandomAccessFile 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 write = new RandomAccessFile(file,"rw"); int len = -1; while ((len = read.read(b)) != -1) { write.write(b, 0, len); //如果分块文件的大小大于等于分块大小就跳过本次循环 if (file.length() >= chunkSize) { break; } } write.close(); System.out.println("完成分块"+i); } } read.close(); } catch (Exception e) { e.printStackTrace(); } }
RandomAccessFile
是 Java 中的一个类,它允许对文件的任意位置进行读写操作。与其他的输入/输出流(如 InputStream
和 OutputStream
)不同,RandomAccessFile
并不属于它们的类系,而是直接继承自 Object
类。它提供了类似于文件系统中的随机访问功能,因此得名“随机访问文件”。
以下是 RandomAccessFile
的一些主要特点和功能:
- 随机访问:
RandomAccessFile
允许你直接跳到文件的任意位置来读写数据。这是通过使用seek(long pos)
方法实现的,它可以将文件的指针移动到指定的位置。 - 读写功能:
RandomAccessFile
既可以从文件中读取数据,也可以向文件中写入数据。它提供了类似于InputStream
的read()
方法和类似于OutputStream
的write()
方法来执行这些操作。 - 文件指针操作:除了
seek(long pos)
方法外,RandomAccessFile
还提供了getFilePointer()
方法来返回文件记录指针的当前位置。 - 访问模式:在创建
RandomAccessFile
对象时,你需要指定一个访问模式,它决定了文件是以只读方式打开还是以读写方式打开。常见的访问模式有 "r"(只读)和 "rw"(读写)。 - 文件操作模式:在 JDK 1.6 及更高版本中,
RandomAccessFile
还支持 "rws" 和 "rwd" 模式。在 "rws" 模式下,每次写入操作都会确保数据被写入到磁盘中;而在 "rwd" 模式下,只有在对文件执行了某些特定的更新操作(如关闭文件或调用flush()
方法)后,数据才会被写入到磁盘中。 - 内存映射文件:虽然
RandomAccessFile
提供了强大的文件访问功能,但在某些情况下,使用 JDK 1.4 引入的“内存映射文件”可能会更高效。内存映射文件允许你将文件的一部分或全部映射到内存中,从而可以像访问内存一样快速地访问文件。
文件合并
文件合并流程:
1、找到要合并的文件并按文件合并的先后进行排序。
2、创建合并文件
3、依次从合并的文件中读取数据向合并文件写入数
文件合并的测试代码 :
//测试文件合并方法 @Test public void testMerge(){ try { //获取源文件 File sourceFile = new File("B:\\workspace\\test\\you.ncm"); //分块文件目录 String chunkPath="B:\\workspace\\test\\chunk\\"; //合并后的文件 File mergeFile = new File("B:\\workspace\\test\\you1.ncm"); if (mergeFile.exists()) { mergeFile.delete(); } //创建新的合并文件 mergeFile.createNewFile(); RandomAccessFile write = new RandomAccessFile(mergeFile,"rw"); //指针指向文件顶端 write.seek(0); //缓冲区 byte[] b = new byte[1024]; //获取分块文件数组 File file = new File(chunkPath); File[] files = file.listFiles(); // 转成集合,便于排序 List<File> fileList = Arrays.asList(files); //使用工具类和自定义比较类进行排序 Collections.sort(fileList, new Comparator<File>() { @Override public int compare(File o1, File o2) { Integer o1Name = Integer.parseInt(o1.getName()); Integer o2Name=Integer.parseInt(o2.getName()); return o1Name-o2Name; } }); //合并文件 for (File file1 : fileList) { RandomAccessFile read = new RandomAccessFile(file1,"r"); int len = -1; while ((len = read.read(b)) != -1) { write.write(b, 0, len); } read.close(); } write.close(); //校验文件 FileInputStream fileInputStream = new FileInputStream(sourceFile); FileInputStream mergeFileStream = new FileInputStream(mergeFile); //取出原始文件的md5 String originalMd5 = DigestUtils.md5Hex(fileInputStream); //取出合并文件的md5进行比较 String mergeFileMd5 = DigestUtils.md5Hex(mergeFileStream); if (originalMd5.equals(mergeFileMd5)) { System.out.println("合并文件成功"); } else { System.out.println("合并文件失败"); } } catch (Exception e) { e.printStackTrace(); } }
视频上传流程
1、前端对文件进行分块。
2、前端上传分块文件前请求媒资服务检查文件是否存在,如果已经存在则不再上传。
3、如果分块文件不存在则前端开始上传
4、前端请求媒资服务上传分块。
5、媒资服务将分块上传至MinIO。
6、前端将分块上传完毕请求媒资服务合并分块。
7、媒资服务判断分块上传完成则请求MinIO合并文件。
8、合并完成校验合并后的文件是否完整,如果不完整则删除文件。
测试将分块文件上传至minio
//将分块文件上传至minio @Test public void uploadChunk(){ String chunkFolderPath = "B:\\workspace\\test\\chunk\\"; File chunkFolder = new File(chunkFolderPath); //分块文件 File[] files = chunkFolder.listFiles(); //将分块文件上传至minio for (int i = 0; i < files.length; i++) { try { UploadObjectArgs uploadObjectArgs = UploadObjectArgs .builder() .bucket("testbucket")//桶名 .object("chunk/" + i)//存储路径+文件名 .filename(files[i].getAbsolutePath()) .build(); minioClient.uploadObject(uploadObjectArgs); System.out.println("上传分块成功"+i); } catch (Exception e) { e.printStackTrace(); } } }
测试通过minio的合并文件
//合并文件,要求分块文件最小5M @Test public void test_merge() throws Exception { List<ComposeSource> sources = new ArrayList<>(); for (int i = 0; i <=7; i++) { ComposeSource composeSource = ComposeSource .builder()//指定分块文件信息 .bucket("testbucket") .object("chunk/" + (Integer.toString(i)))//目标文件信息 .build(); sources.add(composeSource); } ComposeObjectArgs composeObjectArgs = ComposeObjectArgs .builder() .bucket("testbucket") .object("merge01.npm")//目标文件 .sources(sources)//源文件 .build(); //合并文件 minioClient.composeObject(composeObjectArgs); }
测试minio清除分块文件
//清除分块文件 @Test public void test_removeObjects(){ //合并分块完成将分块文件清除 List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i) .limit(2)//循环几次 .map(i -> new DeleteObject("chunk/".concat(Integer.toString(i)))) .collect(Collectors.toList()); RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket("testbucket").objects(deleteObjects).build(); Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs); results.forEach(r->{ DeleteError deleteError = null; try { deleteError = r.get(); } catch (Exception e) { e.printStackTrace(); } }); }
到此这篇关于Java视频断点上传的实现示例的文章就介绍到这了,更多相关Java视频断点上传内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!