最近做大文件断点续传需求时,需要将大文件生成hash,如果直接生成,速度会变得很慢。
优化方式:将大文件先切片,每一片都启动一个webWorker来计算hash,最后将所有的hash合并到一起再重新生成一个新的hash。
js
async function calculateMD5All(file: File) {
const chunkSize = 100000000; // 100MB
const chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
const promiseAll: Promise<unknown>[] = [];
function loadNext() {
const start = currentChunk * chunkSize;
const end = Math.min(start + chunkSize, file.size);
promiseAll.push(creatWebworker(file.slice(start, end)));
currentChunk++;
if (currentChunk < chunks) {
loadNext();
}
}
loadNext();
const arr = await Promise.all(promiseAll);
const hash = SparkMD5.hash(arr.join(','));
return hash;
}
方式1: 原生js引入
ini
// 引入worker.js,我是vite+vue3项目,worker.js放在项目的public目录下
function creatWebworker(chunk: Blob) {
// worker.js的地址可以用绝对地址也可以是相对地址,因为我直接放在public目录下,部署后就在项目根路径下
const worker = new Worker('/worker.js');
worker.onmessage = (e) => {
const hash = e.data;
resolve(hash);
};
worker.postMessage(chunk);
}
/**
* worker.js
*/
// spark-md5.min.js的地址我也是放在public项目下,所以这里可以写相对地址,注意:importScripts引入的js必须和当前项目的域名同源。
importScripts('/spark-md5.min.js');
self.onmessage = function (e) {
const chunk = e.data;
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(chunk);
fileReader.onload = function (e) {
spark.append(e.target?.result);
const hash = spark.end();
self.postMessage(hash);
};
};
方式2:Blob URL引入
ini
// 这种方式是使用使用Blob URL,其实只是将1中的worker.js变成了模板字符串。注意要注意一点,和1有点不同的是importScripts中的路径只能是通域名下的绝对路径
const workerCode = `
importScripts('https://***/spark-md5.min.js');
self.onmessage = function (e) {
const chunk = e.data;
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(chunk);
fileReader.onload = function (e) {
spark.append(e.target?.result);
const hash = spark.end();
self.postMessage(hash);
};
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
worker.onmessage = (e) => {
const hash = e.data;
resolve(hash);
};
worker.postMessage(chunk);
方式3:ESModule引入 (推荐这种)
ini
// worker.ts文件直接写在主文件同级目录下,创建worker时,设置type:module
function creatWebworker(chunk: Blob) {
return new Promise((resolve, reject) => {
const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
worker.onmessage = (e) => {
const hash = e.data;
resolve(hash);
};
worker.postMessage(chunk);
});
}
// worker.ts
import SparkMD5 from 'spark-md5';
self.onmessage = function (e) {
const chunk = e.data;
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(chunk);
fileReader.onload = function (e) {
spark.append(e.target?.result as ArrayBuffer);
const hash = spark.end();
self.postMessage(hash);
};
};