Java視頻斷點(diǎn)上傳的實(shí)現(xiàn)示例
什么是斷點(diǎn)續(xù)傳
通常視頻文件都比較大,所以對(duì)于媒資系統(tǒng)上傳文件的需求要滿足大文件的上傳要求。http協(xié)議本身對(duì)上傳文件大小沒有限制,但是客戶的網(wǎng)絡(luò)環(huán)境質(zhì)量、電腦硬件環(huán)境等參差不齊,如果一個(gè)大文件快上傳完了網(wǎng)斷了沒有上傳完成,需要客戶重新上傳,用戶體驗(yàn)非常差,所以對(duì)于大文件上傳的要求最基本的是斷點(diǎn)續(xù)傳。
什么是斷點(diǎn)續(xù)傳:
引用百度百科:斷點(diǎn)續(xù)傳指的是在下載或上傳時(shí),將下載或上傳任務(wù)(一個(gè)文件或一個(gè)壓縮包)人為的劃分為幾個(gè)部分,每一個(gè)部分采用一個(gè)線程進(jìn)行上傳或下載,如果碰到網(wǎng)絡(luò)故障,可以從已經(jīng)上傳或下載的部分開始繼續(xù)上傳下載未完成的部分,而沒有必要從頭開始上傳下載,斷點(diǎn)續(xù)傳可以提高節(jié)省操作時(shí)間,提高用戶體驗(yàn)性。
斷點(diǎn)續(xù)傳流程如下圖:
流程如下:
1、前端上傳前先把文件分成塊
2、一塊一塊的上傳,上傳中斷后重新上傳,已上傳的分塊則不用再上傳
3、各分塊上傳完成最后在服務(wù)端合并文件
文件分塊
文件分塊的流程如下:
1、獲取源文件長(zhǎng)度
2、根據(jù)設(shè)定的分塊文件的大小計(jì)算出塊數(shù)
3、從源文件讀數(shù)據(jù)依次向每一個(gè)塊文件寫數(shù)據(jù)。
/** * 文件分塊上傳測(cè)試 */ @Test public void testChunk(){ //獲取源文件 File sourceFile = new File("B:\\workspace\\test\\you.ncm"); //源文件字節(jié)大小 long length = sourceFile.length(); //分塊文件目錄 String chunkPath="B:\\workspace\\test\\chunk\\"; File chunkFolder = new File(chunkPath); //檢查目錄是否存在 if (!chunkFolder.exists()) { //不存在就創(chuàng)建 chunkFolder.mkdirs(); } //分塊大小 long chunkSize = 1024*1024*1; //分塊數(shù)量 long chunkNum = (long) Math.ceil(length * 1.0 / chunkSize); //緩沖區(qū)大小 byte[] b = new byte[1024]; //使用RandomAccessFile訪問文件 try { RandomAccessFile read = new RandomAccessFile(sourceFile, "r"); for (int i = 0; i < chunkNum; i++) { //創(chuàng)建分塊文件 File file = new File(chunkPath + i); //檢查文件是否存在,如果存在就刪除文件 if(file.exists()){ file.delete(); } //創(chuàng)建一個(gè)新文件 boolean newFile = file.createNewFile(); if (newFile){ //向分塊文件中寫數(shù)據(jù) RandomAccessFile write = new RandomAccessFile(file,"rw"); int len = -1; while ((len = read.read(b)) != -1) { write.write(b, 0, len); //如果分塊文件的大小大于等于分塊大小就跳過本次循環(huán) if (file.length() >= chunkSize) { break; } } write.close(); System.out.println("完成分塊"+i); } } read.close(); } catch (Exception e) { e.printStackTrace(); } }
RandomAccessFile
是 Java 中的一個(gè)類,它允許對(duì)文件的任意位置進(jìn)行讀寫操作。與其他的輸入/輸出流(如 InputStream
和 OutputStream
)不同,RandomAccessFile
并不屬于它們的類系,而是直接繼承自 Object
類。它提供了類似于文件系統(tǒng)中的隨機(jī)訪問功能,因此得名“隨機(jī)訪問文件”。
以下是 RandomAccessFile
的一些主要特點(diǎn)和功能:
- 隨機(jī)訪問:
RandomAccessFile
允許你直接跳到文件的任意位置來讀寫數(shù)據(jù)。這是通過使用seek(long pos)
方法實(shí)現(xiàn)的,它可以將文件的指針移動(dòng)到指定的位置。 - 讀寫功能:
RandomAccessFile
既可以從文件中讀取數(shù)據(jù),也可以向文件中寫入數(shù)據(jù)。它提供了類似于InputStream
的read()
方法和類似于OutputStream
的write()
方法來執(zhí)行這些操作。 - 文件指針操作:除了
seek(long pos)
方法外,RandomAccessFile
還提供了getFilePointer()
方法來返回文件記錄指針的當(dāng)前位置。 - 訪問模式:在創(chuàng)建
RandomAccessFile
對(duì)象時(shí),你需要指定一個(gè)訪問模式,它決定了文件是以只讀方式打開還是以讀寫方式打開。常見的訪問模式有 "r"(只讀)和 "rw"(讀寫)。 - 文件操作模式:在 JDK 1.6 及更高版本中,
RandomAccessFile
還支持 "rws" 和 "rwd" 模式。在 "rws" 模式下,每次寫入操作都會(huì)確保數(shù)據(jù)被寫入到磁盤中;而在 "rwd" 模式下,只有在對(duì)文件執(zhí)行了某些特定的更新操作(如關(guān)閉文件或調(diào)用flush()
方法)后,數(shù)據(jù)才會(huì)被寫入到磁盤中。 - 內(nèi)存映射文件:雖然
RandomAccessFile
提供了強(qiáng)大的文件訪問功能,但在某些情況下,使用 JDK 1.4 引入的“內(nèi)存映射文件”可能會(huì)更高效。內(nèi)存映射文件允許你將文件的一部分或全部映射到內(nèi)存中,從而可以像訪問內(nèi)存一樣快速地訪問文件。
文件合并
文件合并流程:
1、找到要合并的文件并按文件合并的先后進(jìn)行排序。
2、創(chuàng)建合并文件
3、依次從合并的文件中讀取數(shù)據(jù)向合并文件寫入數(shù)
文件合并的測(cè)試代碼 :
//測(cè)試文件合并方法 @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(); } //創(chuàng)建新的合并文件 mergeFile.createNewFile(); RandomAccessFile write = new RandomAccessFile(mergeFile,"rw"); //指針指向文件頂端 write.seek(0); //緩沖區(qū) byte[] b = new byte[1024]; //獲取分塊文件數(shù)組 File file = new File(chunkPath); File[] files = file.listFiles(); // 轉(zhuǎn)成集合,便于排序 List<File> fileList = Arrays.asList(files); //使用工具類和自定義比較類進(jìn)行排序 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(); //校驗(yàn)文件 FileInputStream fileInputStream = new FileInputStream(sourceFile); FileInputStream mergeFileStream = new FileInputStream(mergeFile); //取出原始文件的md5 String originalMd5 = DigestUtils.md5Hex(fileInputStream); //取出合并文件的md5進(jìn)行比較 String mergeFileMd5 = DigestUtils.md5Hex(mergeFileStream); if (originalMd5.equals(mergeFileMd5)) { System.out.println("合并文件成功"); } else { System.out.println("合并文件失敗"); } } catch (Exception e) { e.printStackTrace(); } }
視頻上傳流程
1、前端對(duì)文件進(jìn)行分塊。
2、前端上傳分塊文件前請(qǐng)求媒資服務(wù)檢查文件是否存在,如果已經(jīng)存在則不再上傳。
3、如果分塊文件不存在則前端開始上傳
4、前端請(qǐng)求媒資服務(wù)上傳分塊。
5、媒資服務(wù)將分塊上傳至MinIO。
6、前端將分塊上傳完畢請(qǐng)求媒資服務(wù)合并分塊。
7、媒資服務(wù)判斷分塊上傳完成則請(qǐng)求MinIO合并文件。
8、合并完成校驗(yàn)合并后的文件是否完整,如果不完整則刪除文件。
測(cè)試將分塊文件上傳至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)//存儲(chǔ)路徑+文件名 .filename(files[i].getAbsolutePath()) .build(); minioClient.uploadObject(uploadObjectArgs); System.out.println("上傳分塊成功"+i); } catch (Exception e) { e.printStackTrace(); } } }
測(cè)試通過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)))//目標(biāo)文件信息 .build(); sources.add(composeSource); } ComposeObjectArgs composeObjectArgs = ComposeObjectArgs .builder() .bucket("testbucket") .object("merge01.npm")//目標(biāo)文件 .sources(sources)//源文件 .build(); //合并文件 minioClient.composeObject(composeObjectArgs); }
測(cè)試minio清除分塊文件
//清除分塊文件 @Test public void test_removeObjects(){ //合并分塊完成將分塊文件清除 List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i) .limit(2)//循環(huán)幾次 .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(); } }); }
到此這篇關(guān)于Java視頻斷點(diǎn)上傳的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Java視頻斷點(diǎn)上傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
超詳細(xì)講解Java秒殺項(xiàng)目登陸模塊的實(shí)現(xiàn)
這是一個(gè)主要使用java開發(fā)的秒殺系統(tǒng),項(xiàng)目比較大,所以本篇只實(shí)現(xiàn)了登陸模塊,代碼非常詳盡,感興趣的朋友快來看看2022-03-03springboot 項(xiàng)目使用jasypt加密數(shù)據(jù)源的方法
Jasypt 是一個(gè) Java 庫(kù),它允許開發(fā)者以最小的努力為他/她的項(xiàng)目添加基本的加密功能,而且不需要對(duì)密碼學(xué)的工作原理有深刻的了解。接下來通過本文給大家介紹springboot 項(xiàng)目使用jasypt加密數(shù)據(jù)源的問題,一起看看吧2021-11-11使用maven項(xiàng)目pom.xml文件配置打包功能和靜態(tài)資源文件自帶版本號(hào)功能
在Maven項(xiàng)目中,通過pom.xml文件配置打包功能,可以控制構(gòu)建過程,生成可部署的包,同時(shí),為了緩存控制與版本更新,可以在打包時(shí)給靜態(tài)資源文件如JS、CSS添加版本號(hào),這通常通過插件如maven-resources-plugin實(shí)現(xiàn)2024-09-09使用json字符串插入節(jié)點(diǎn)或者覆蓋節(jié)點(diǎn)
這篇文章主要介紹了使用json字符串插入節(jié)點(diǎn)或者覆蓋節(jié)點(diǎn)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08