怎麼給文件生成MD5

MD5 的核心是經過算法把任意長度的原始數據映射成128 bit 的數據。把一串數據通過處理,獲得另外一個固定長度的數據。是一種Hash算法,全稱爲 消息摘要算法版本5(Message Digest Algorithm 5)。javascript

不一樣原始數據會有不一樣的 MD5 值。 因此不一樣的文件MD5的值也不同。html

通常在上傳文件的場景裏,會根據MD5實現續傳、秒傳的功能。java

本文簡單寫下怎麼生成 MD5,這裏用到插件spark-md5node

TL;DR

  • 怕麻煩,10M之內生成MD5,直接用法一就行
  • 怕麻煩,30M之內生成MD5,直接用法二就行
  • 再大一點,用法三吧
  • 但法三能夠用在以上全部場景

法一:生成文件的 MD5

生成文件的 MD5,簡單思路以下:git

  • 建立 FileReader 實例,讀文件
  • 讀完以後,成功狀態下,直接使用SparkMD5.hashBinary

生成小文件的MD5

具體代碼在文末。github

缺陷

這種方式,當文件越大的時候,生成 md5 的速度也就越慢,好比 40M 的文件可能須要 1s 才生成 md5。當頁面還有其餘的交互的時候,將會堵塞其餘交互,致使頁面假死狀態。web

舉個例子:加個按鈕,寫個點擊事件。選擇文件以後,當即點擊按鈕,會發現,當文件越大的時候,彈框的速度愈來愈遲鈍。面試

<input id="upload" type="file" onchange="selectLocalFile" />
<!-- 這裏加個按鈕,選擇文件完以後 -->
<button onclick="alert(1)">測試線程堵塞</button>
<script> upload.onchange = async (e) => { const file = e.target.files[0]; console.time("timeCreateMd5"); const md5 = await createFileMd5(file); console.log(file.size); // 會打印timeCreateMd5: 959.31396484375 ms console.timeEnd("timeCreateMd5"); }; </script>
複製代碼

法二:在worker中生成md5

因此咱們使用 web-workerworker 線程計算 hash,這樣用戶仍能夠在主界面正常的交互,不會引發堵塞。
當前頁面的修改,以下:算法

md5

新增在worker裏執行的hash.js文件以下:markdown

self.importScripts("https://unpkg.com/spark-md5@3.0.1/spark-md5.min.js");

// 生成文件 hash
self.onmessage = async e => {
  const file = e.data
  const md5 = await createFileMd5(file)
  self.postMessage(md5);
};

function createFileMd5(file){
  // ...
  // 同以前的,但如下須要修改,增長self的前綴
  isSuccess
        ? resolve(self.SparkMD5.hashBinary(result))
        : reject(new Error("讀取出錯了"));
}

複製代碼

到這,其實雖然生成大文件的md5耗費時間長,但起碼不會堵塞頁面主線程了。

也有缺陷

能夠看到計算md5是將整個文件讀完纔看到,這樣當文件過大是極其耗內存的。因此須要分片讀取生成md5

法三:將文件分片生成md5

仔細看spark-md5,其實做者也是推薦分片讀取,相似nodejs裏面的流同樣,這樣不須要佔據大量內存。

先將文件,按照必定大小分塊chunk,這邊直接使用File.slice了。

而後將chunks傳給另外一個線程計算md5,這邊大文件可能須要進度條,因此有一個進度,按需求使用。

Chunks hash

附註:代碼

代碼:生成小文件的 MD5

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input id="upload" type="file" onchange="selectLocalFile" />
    <script src="https://unpkg.com/spark-md5@3.0.1/spark-md5.min.js"></script>
    <script> const upload = document.querySelector("#upload"); upload.onchange = async (e) => { const file = e.target.files[0]; const md5 = await createFileMd5(file); console.log(md5); }; function createFileMd5(file) { return new Promise((resolve, reject) => { // 建立FileReader實例 const fileReader = new FileReader(); // 開始讀文件 fileReader.readAsBinaryString(file); // 文件讀完以後,觸發load事件 fileReader.onload = (e) => { // e.target就是fileReader實例 console.log(e.target); // result是fileReader讀到的部分 const result = e.target.result; // 若是讀到的長度和文件長度一致,則讀取成功 const isSuccess = file.size === result.length; // 讀取成功,則生成MD5,扔出去。失敗就報錯 isSuccess ? resolve(SparkMD5.hashBinary(result)) : reject(new Error("讀取出錯了")); }; // 讀取過程當中出錯也直接報錯 fileReader.onerror = () => reject(new Error("讀取出錯了")); }); } </script>
  </body>
</html>
複製代碼

代碼:在worker中生成md5

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input id="upload" type="file" onchange="selectLocalFile" />
    <button onclick="alert(1)">測試線程堵塞</button>
    <script> const upload = document.querySelector("#upload"); upload.onchange = async (e) => { const file = e.target.files[0]; console.time("timeCreateMd5"); const md5 = await createFileMd5InWorker(file); console.log(file.size); console.timeEnd("timeCreateMd5"); }; // 生成文件 md5(web-worker) function createFileMd5InWorker(file) { return new Promise((resolve) => { // 新建worker線程,執行hash.js const worker = new Worker("./hash.js"); // 給線程傳file worker.postMessage(file); // 當線程傳消息的時候,接受消息 worker.onmessage = (e) => { const md5 = e.data; md5 && resolve(md5) }; }); } </script>
  </body>
</html>

複製代碼
// hash.js
self.importScripts("https://unpkg.com/spark-md5@3.0.1/spark-md5.min.js");

// 生成文件 md5
self.onmessage = async e => {
  const file = e.data
  const md5 = await createFileMd5(file)
  self.postMessage(md5);
  self.close()
};

function createFileMd5(file) {
  return new Promise((resolve, reject) => {
    // 建立FileReader實例
    const fileReader = new FileReader();
    // 開始讀文件
    fileReader.readAsBinaryString(file);
    // 文件讀完以後,觸發load事件
    fileReader.onload = (e) => {
      // e.target就是fileReader實例,這裏用this也是指fileReader實例
      console.log(e.target);
      // result是fileReader讀到的部分
      const result = e.target.result;
      // 若是讀到的長度和文件長度一致,則讀取成功
      const isSuccess = file.size === result.length;
      // 讀取成功,則生成MD5,扔出去。失敗就報錯
      isSuccess
        ? resolve(self.SparkMD5.hashBinary(result))
        : reject(new Error("讀取出錯了"));
    };
    // 讀取過程當中出錯也直接報錯
    fileReader.onerror = () => reject(new Error("讀取出錯了"));
  });
}

複製代碼

代碼:分片讀取生成md5

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input id="upload" type="file" onchange="selectLocalFile" />
    <button onclick="alert(1)">測試線程堵塞</button>
    <script> const upload = document.querySelector("#upload"); upload.onchange = async (e) => { const file = e.target.files[0]; const chunks = createFileChunk(file) console.time("timeCreateMd5"); // 這裏注意放chunks const {md5} = await createFileMd5InWorker(chunks); console.log(file.size); console.timeEnd("timeCreateMd5"); }; // 生成文件切片 function createFileChunk(file, size = 4 * 1024 * 1024) { let chunks = []; let cur = 0; while (cur < file.size) { chunks.push(file.slice(cur, cur + size)); cur += size; } return chunks; } // 生成文件 hash(web-worker) function createFileMd5InWorker(fileChunks) { return new Promise((resolve) => { const worker = new Worker("./hash.js"); worker.postMessage({ fileChunks }); worker.onmessage = (e) => { // 這邊加了進度條 這裏的進度條,看須要顯示 const { percentage, hash } = e.data; console.log(percentage) // 計算出hash以後,扔出去 hash &&resolve(hash); }; }); } </script>
  </body>
</html>

複製代碼
// 直接copy的 https://juejin.cn/post/6844904046436843527#heading-17
self.importScripts("./js/lib/spark-md5.min.js"); // 導入腳本

// 生成文件 hash
self.onmessage = e => {
  const { fileChunks } = e.data;
  console.log(fileChunks)
// const { fileChunks } = e.data;
  const spark = new self.SparkMD5.ArrayBuffer();
  let percentage = 0;
  let count = 0;
  const loadNext = index => {
    const reader = new FileReader();
    reader.readAsArrayBuffer(fileChunks[index]);
    reader.onload = e => {
      count++;
      spark.append(e.target.result);
      if (count === fileChunks.length) {
        self.postMessage({
          percentage: 100,
          hash: spark.end()
        });
        self.close();
      } else {
        percentage += 100 / fileChunks.length;
        self.postMessage({
          percentage
        });
        loadNext(count);
      }
    };
  };
  loadNext(0);
};


複製代碼

引用

相關文章
相關標籤/搜索