大文件斷點續傳如何實現?

来源:https://www.cnblogs.com/88223100/archive/2023/03/12/How-to-realize-breakpoint-continuation-of-large-files.html
-Advertisement-
Play Games

最近接到一個新的需求,需要上傳2G左右的視頻文件,用測試環境的OSS試了一下,上傳需要十幾分鐘,再考慮到公司的資源問題,果斷放棄該方案。 一提到大文件上傳,我最先想到的就是各種網盤了,現在大家都喜歡將自己收藏的「小電影」上傳到網盤進行保存。網盤一般都支持斷點續傳和文件秒傳功能,減少了網路波動和網路... ...


各位看官大家好,今天給大家分享的又是一篇實戰文章,希望大家能夠喜歡。

開味菜

最近接到一個新的需求,需要上傳2G左右的視頻文件,用測試環境的OSS試了一下,上傳需要十幾分鐘,再考慮到公司的資源問題,果斷放棄該方案。

一提到大文件上傳,我最先想到的就是各種網盤了,現在大家都喜歡將自己收藏的「小電影」上傳到網盤進行保存。網盤一般都支持斷點續傳和文件秒傳功能,減少了網路波動和網路帶寬對文件的限制,大大提高了用戶體驗,讓人愛不釋手。

說到這,大家先來瞭解一下這幾個概念:

  • 「文件分塊」:將大文件拆分成小文件,將小文件上傳\下載,最後再將小文件組裝成大文件;
  • 「斷點續傳」:在文件分塊的基礎上,將每個小文件採用單獨的線程進行上傳\下載,如果碰到網路故障,可以從已經上傳\下載的部分開始繼續上傳\下載未完成的部分,而沒有必要從頭開始上傳\下載;
  • 「文件秒傳」:資源伺服器中已經存在該文件,其他人上傳時直接返回該文件的URI。

RandomAccessFile

平時我們都會使用FileInputStreamFileOutputStreamFileReader以及FileWriterIO流來讀取文件,今天我們來瞭解一下RandomAccessFile

它是一個直接繼承Object的獨立的類,底層實現中它實現的是DataInputDataOutput介面。該類支持隨機讀取文件,隨機訪問文件類似於文件系統中存儲的大位元組數組。

它的實現基於「文件指針」(一種游標或者指向隱含數組的索引),文件指針可以通過getFilePointer方法讀取,也可以通過seek方法設置。

輸入時從文件指針開始讀取位元組,並使文件指針超過讀取的位元組,如果寫入超過隱含數組當前結尾的輸出操作會導致擴展數組。該類有四種模式可供選擇:

  • r: 以只讀方式打開文件,如果執行寫入操作會拋出IOException;
  • rw: 以讀、寫方式打開文件,如果文件不存在,則嘗試創建文件;
  • rws: 以讀、寫方式打開文件,要求對文件內容或元數據的每次更新都同步寫入底層存儲設備;
  • rwd: 以讀、寫方式打開文件,要求對文件內容的每次更新都同步寫入底層存儲設備;

rw模式下,預設是使用buffer的,只有cache滿的或者使用RandomAccessFile.close()關閉流的時候才真正的寫到文件。

API

1、void seek(long pos):設置下一次讀取或寫入時的文件指針偏移量,通俗點說就是指定下次讀文件數據的位置。

偏移量可以設置在文件末尾之外,只有在偏移量設置超出文件末尾後,才能通過寫入更改文件長度;

2、native long getFilePointer():返回當前文件的游標位置;

3、native long length():返回當前文件的長度;

4、「讀」方法圖片

5、「寫」方法圖片

6、readFully(byte[] b):這個方法的作用就是將文本中的內容填滿這個緩衝區b。如果緩衝b不能被填滿,那麼讀取流的過程將被阻塞,如果發現是流的結尾,那麼會拋出異常;

7、FileChannel getChannel():返回與此文件關聯的唯一FileChannel對象;

8、int skipBytes(int n):試圖跳過n個位元組的輸入,丟棄跳過的位元組;

RandomAccessFile的絕大多數功能,已經被JDK1.4的NIO的「記憶體映射」文件取代了,即把文件映射到記憶體後再操作,省去了頻繁磁碟io

主菜

總結經驗,砥礪前行:之前的實戰文章中過多的粘貼了源碼,影響了各位小伙伴的閱讀感受。經過大佬的點撥,以後將展示部分關鍵代碼,供各位賞析,源碼可在「後臺」獲取。

文件分塊

文件分塊需要在前端進行處理,可以利用強大的js庫或者現成的組件進行分塊處理。需要確定分塊的大小和分塊的數量,然後為每一個分塊指定一個索引值。

為了防止上傳文件的分塊與其它文件混淆,採用文件的md5值來進行區分,該值也可以用來校驗伺服器上是否存在該文件以及文件的上傳狀態。

  • 如果文件存在,直接返迴文件地址;
  • 如果文件不存在,但是有上傳狀態,即部分分塊上傳成功,則返回未上傳的分塊索引數組;
  • 如果文件不存在,且上傳狀態為空,則所有分塊均需要上傳。
fileRederInstance.readAsBinaryString(file);
fileRederInstance.addEventListener("load", (e) => {
    let fileBolb = e.target.result;
    fileMD5 = md5(fileBolb);
    const formData = new FormData();
    formData.append("md5", fileMD5);
    axios
        .post(http + "/fileUpload/checkFileMd5", formData)
        .then((res) => {
            if (res.data.message == "文件已存在") {
                //文件已存在不走後面分片了,直接返迴文件地址到前臺頁面
                success && success(res);
            } else {
                //文件不存在存在兩種情況,一種是返回data:null代表未上傳過 一種是data:[xx,xx] 還有哪幾片未上傳
                if (!res.data.data) {
                    //還有幾片未上傳情況,斷點續傳
                    chunkArr = res.data.data;
                }
                readChunkMD5();
            }
        })
        .catch((e) => {});
});

在調用上傳介面前,通過slice方法來取出索引在文件中對應位置的分塊。

const getChunkInfo = (file, currentChunk, chunkSize) => {
       //獲取對應下標下的文件片段
       let start = currentChunk * chunkSize;
       let end = Math.min(file.size, start + chunkSize);
       //對文件分塊
       let chunk = file.slice(start, end);
       return { start, end, chunk };
   };

之後調用上傳介面完成上傳。

斷點續傳、文件秒傳

後端基於spring boot開發,使用redis來存儲上傳文件的狀態和上傳文件的地址。

如果文件完整上傳,返迴文件路徑;部分上傳則返回未上傳的分塊數組;如果未上傳過返回提示信息。

在上傳分塊時會產生兩個文件,一個是文件主體,一個是臨時文件。臨時文件可以看做是一個數組文件,為每一個分塊分配一個值為127的位元組。

校驗MD5值時會用到兩個值:

  • 文件上傳狀態:只要該文件上傳過就不為空,如果完整上傳則為true,部分上傳返回false
  • 文件上傳地址:如果文件完整上傳,返迴文件路徑;部分上傳返回臨時文件路徑。
/**
 * 校驗文件的MD5
 **/
public Result checkFileMd5(String md5) throws IOException {
    //文件是否上傳狀態:只要該文件上傳過該值一定存在
    Object processingObj = stringRedisTemplate.opsForHash().get(UploadConstants.FILE_UPLOAD_STATUS, md5);
    if (processingObj == null) {
        return Result.ok("該文件沒有上傳過");
    }
    boolean processing = Boolean.parseBoolean(processingObj.toString());
    //完整文件上傳完成時為文件的路徑,如果未完成返回臨時文件路徑(臨時文件相當於數組,為每個分塊分配一個值為127的位元組)
    String value = stringRedisTemplate.opsForValue().get(UploadConstants.FILE_MD5_KEY + md5);
    //完整文件上傳完成是true,未完成返回false
    if (processing) {
        return Result.ok(value,"文件已存在");
    } else {
        File confFile = new File(value);
        byte[] completeList = FileUtils.readFileToByteArray(confFile);
        List<Integer> missChunkList = new LinkedList<>();
        for (int i = 0; i < completeList.length; i++) {
            if (completeList[i] != Byte.MAX_VALUE) {
                //用空格補齊
                missChunkList.add(i);
            }
        }
        return Result.ok(missChunkList,"該文件上傳了一部分");
    }
}

說到這,你肯定會問:當這個文件的所有分塊上傳完成之後,該怎麼得到完整的文件呢?接下來我們就說一下分塊合併的問題。

分塊上傳、文件合併

上邊我們提到了利用文件的md5值來維護分塊和文件的關係,因此我們會將具有相同md5值的分塊進行合併,由於每個分塊都有自己的索引值,所以我們會將分塊按索引像插入數組一樣分別插入文件中,形成完整的文件。

分塊上傳時,要和前端的分塊大小、分塊數量、當前分塊索引等對應好,以備文件合併時使用,此處我們採用的是「磁碟映射」的方式來合併文件。

 //讀操作和寫操作都是允許的
RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
//它返回的就是nio通信中的file的唯一channel
FileChannel fileChannel = tempRaf.getChannel();

//寫入該分片數據   分片大小 * 第幾塊分片獲取偏移量
long offset = CHUNK_SIZE * multipartFileDTO.getChunk();
//分片文件大小
byte[] fileData = multipartFileDTO.getFile().getBytes();
//將文件的區域直接映射到記憶體
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
mappedByteBuffer.put(fileData);
// 釋放
FileMD5Util.freedMappedByteBuffer(mappedByteBuffer);
fileChannel.close();

每當完成一次分塊的上傳,還需要去檢查文件的上傳進度,看文件是否上傳完成。

RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw");
//把該分段標記為 true 表示完成
accessConfFile.setLength(multipartFileDTO.getChunks());
accessConfFile.seek(multipartFileDTO.getChunk());
accessConfFile.write(Byte.MAX_VALUE);

//completeList 檢查是否全部完成,如果數組裡是否全部都是(全部分片都成功上傳)
byte[] completeList = FileUtils.readFileToByteArray(confFile);
byte isComplete = Byte.MAX_VALUE;
for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
    //與運算, 如果有部分沒有完成則 isComplete 不是 Byte.MAX_VALUE
    isComplete = (byte) (isComplete & completeList[i]);
}
accessConfFile.close();

然後更新文件的上傳進度到Redis中。

//更新redis中的狀態:如果是true的話證明是已經該大文件全部上傳完成
if (isComplete == Byte.MAX_VALUE) {
    stringRedisTemplate.opsForHash().put(UploadConstants.FILE_UPLOAD_STATUS, multipartFileDTO.getMd5(), "true");
    stringRedisTemplate.opsForValue().set(UploadConstants.FILE_MD5_KEY + multipartFileDTO.getMd5(), uploadDirPath + "/" + fileName);
} else {
    if (!stringRedisTemplate.opsForHash().hasKey(UploadConstants.FILE_UPLOAD_STATUS, multipartFileDTO.getMd5())) {
        stringRedisTemplate.opsForHash().put(UploadConstants.FILE_UPLOAD_STATUS, multipartFileDTO.getMd5(), "false");
    }
    if (!stringRedisTemplate.hasKey(UploadConstants.FILE_MD5_KEY + multipartFileDTO.getMd5())) {
        stringRedisTemplate.opsForValue().set(UploadConstants.FILE_MD5_KEY + multipartFileDTO.getMd5(), uploadDirPath + "/" + fileName + ".conf");
    }
}

作者|阿Q說代碼

本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/How-to-realize-breakpoint-continuation-of-large-files.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天看到個有趣的網站,給大家分享一下。 該網站的功能很神奇,可以實現編程語言的轉化。感覺在一些場景之下還是有點作用的,比如你原來跟我一樣是做Java的,因為工作需要突然轉Go。這個時候用你Java的經驗 + 這個工具,或許可以起到一定的幫助作用。 工具的使用也很簡單,只需要在左側黏貼你想轉換的原始代 ...
  • 一、初識爬蟲,requests使用 requests介紹: Request支持HTTP連接保持和連接池,支持使用cookie保持會話,支持文件上傳,支持自動響應內容的編碼,支持國際化的URL和POST數據自動編碼。requests會自動實現持久連接keep-alive # 導入模塊 import r ...
  • 資料庫的重要性不言而喻,不管是什麼系統,什麼應用軟體,也不管它們是 Windows 上的應用程式,還是 Web 應用程式,存儲(持久化)和查詢(檢索)數據都是核心的功能。 大家學習資料庫時,比如 MySQL 這個資料庫管理系統,都是在 CLI(Command Line Interface)上操作數據... ...
  • Tomcat 是 Apache 軟體基金會(Apache Software Foundation)的一個開源項目,實現了 Servlet 及 JSP 規範,可以用來部署 WEB 應用及 WebService;本文主要介紹其基本概念。 1、Tomcat 安裝 安裝 Tomcat 之前需要先安裝 Jav ...
  • 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 特效 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> O ...
  • java 線程池預設提供了幾種拒絕策略: 這幾個策略都實現了RejectedExecutionHandler,拿DiscardOldestPolicy來說,查看源碼: 核心代碼只有2行: e.getQueue().poll() 從列表裡彈出1個(最早的)任務,以便讓隊列空出1個位置 e.execut ...
  • 什麼是Git? Git是一個版本控制系統,用於跟蹤電腦文件的變化。Git是一個跟蹤電腦文件變化的版本控制系統,用於幫助協調一個項目中幾個人的工作,同時跟蹤一段時間的進展。換句話說,我們可以說它是一個促進軟體開發中源代碼管理的工具。 Git和SVN的區別 Git是分散式版本控制系統,SVN是集中式 ...
  • 廣發基金外包面試題 說說Spring AOP 說說stream流的常用API 線程池的參數 MCVV,什麼是普通讀和快照讀 redis分片模式是什麼,redis事務,分片模式下事務會生效嗎 說說rocket mq分散式事務是怎麼做的 為什麼要劃分session,怎麼解決耦合問題 說說是怎麼解決分散式 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...