主頁 > 移動端開發 > 實戰篇:斷點續傳?檔案秒傳?手擼大檔案上傳

實戰篇:斷點續傳?檔案秒傳?手擼大檔案上傳

2021-09-21 11:04:46 移動端開發

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

開味菜

最近接到一個新的需求,需要上傳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、「讀」方法e762eb1ca0cac180c89bc153a809b5f1.png

5、「寫」方法f5f360ad14de977487a17a5f89d50c24.png

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");
    }
}
?

回復break可獲取完整原始碼呦!

?

以上就是今天的全部內容了,如果你有不同的意見或者更好的idea,歡迎聯系阿Q,添加阿Q可以加入技術交流群參與討論呦!

后臺留言領取java干貨資料:學習筆記與大廠面試題

「內容推薦」

  • 「JVM合集」

  • 「ElastricSearch合集ing」

  • 「Redis的小操作」

  • 「實際專案教學:身份/權限驗證」

覺得還不錯?記得一鍵四連呦👇

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/301800.html

標籤:其他

上一篇:Android Studio無法檢測到虛擬機的解決方法

下一篇:??Android Studio實作中華字典APP??

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more