计算文件 MD5

1 效果展示

在这里插入图片描述

本篇文章的重点:拿到 File 对象之后,如何计算文件的 md5 值?几千个文件同时计算,页面卡顿崩溃怎么解决?

不知道如何获取 File 对象可以看这篇文章:上传三合一:拖拽上传、上传文件、上传文件夹,一次搞定!,这里不再复述了。

2 分析

懒了,不一步步说明了,直接放结论。

2.1 计算 MD5

直接用轮子 js-spark-md5

2.2 解决页面卡顿问题

同时计算很多文件,页面卡顿崩溃,怎么办?

主要有两方面的压力:计算压力和渲染压力。

2.2.1 计算压力:

缓解计算压力,有下面三种策略:

策略
分块 缓解大文件造成的计算压力
文件很大时,一次性读取整个文件非常非常耗时,而且读取时页面会卡死。
所以必须要分块读取文件,并计算md5。( js-spark-md5 支持分块)
排队 缓解文件多造成的计算压力
同一时刻,只允许运行最多 n 个计算任务,后来的在等待队列中排队。
多线程 使用 Web Worker 多线程计算md5。(安装 worker-loader 包以在 webpack 中使用 Web Worker)
注意worker用完必须杀掉。不然数量多了之后,即使没在执行计算任务,浏览器也会崩

分块和排队都必须用。多线程可用可不用。

2.2.2 渲染压力:

如果像开头的效果图一样,把所有文件都展示在页面上。那么文件一多,渲染压力就会很大。

在真实的业务中,渲染压力会更大。举例说一个场景:

  • 选择要上传的文件后,先计算MD5,之后分块上传文件。
  • 上传列表中要展示所有文件的状态,是正在校验可用空间,还是正在排队算MD5等等。(这会导致,每个文件的每次状态改变,浏览器都要重新渲染)
  • 因为是分块上传,所以有上传进度,进度百分比也要在上传列表中展示。(这会导致,每个任务的上传进度每次改变,浏览器都要重新渲染)

这个场景下,浏览器在上传过程中每时每刻都在不停地重新渲染,卡卡卡。

计算压力可以通过分块、排队、多线程来缓解,但渲染压力就没办法了。无论如何,几百几千个div一下子添加进来,并且一直在变化,浏览器肯定会卡的。

解决方法只有一个,就是不要显示太多文件。可以限制单次上传数量,或者上传列表添加分页功能,等等。

3 代码

ComputingMd5Factory.js:负责排队,给每个任务安排一个 Web Worker


import ComputingMd5Worker from 'worker-loader!./ComputingMd5Worker.js';

class ComputingMd5Factory{
    constructor() {
        // 等待队列
        this.waitingList = [];
        // 正在工作的数量
        this.workingCount = 0;
        // 最大同时工作的数量
        this.maxWorkingCount = 1;
    }
    addTask(file) {
        if (this.workingCount < this.maxWorkingCount) {
            this.workingCount++;
            this.computeMd5(file);
        } else {
            this.waitingList.push(file);
        }
    }
    // 用 worker 计算 md5
    computeMd5(file) {
        const worker = new ComputingMd5Worker();
        worker.postMessage({ file });
        worker.addEventListener("message", e => {
            if (e.data.isSuccessful) {
                file.md5 = e.data.md5;
                if (this.waitingList.length > 0) {
                    // 不释放 workingCount ,直接把等待队列第一个拿进来.
                    // 这样做是为了防止:释放 workingCount 后的瞬间有新任务 addTask ,插队了
                    let newTask = this.waitingList.shift();
                    this.computeMd5(newTask);
                } else {
                    this.workingCount--;
                }
                // 立刻终止worker
                worker.terminate();
            } else if (e.data.isFailed) {
                // 立刻终止worker
                worker.terminate();
            }
        });
    }
}
export default ComputingMd5Factory;

ComputingMd5Worker.js:worker文件,分块读取文件计算 md5。


import SparkMD5 from 'spark-md5'

const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;

// Read in chunks of 2MB
const chunkSize = 1024 * 1024 * 1024;

self.addEventListener("message", e => {
    computeMd5(e.data.file).then(md5 => {
        self.postMessage({
            isSuccessful: true,
            md5: md5
        });
    }).catch(message => {
        self.postMessage({
            isFailed: true,
            message
        });
    })
});

function computeMd5(file) {
    return new Promise((resolve, reject) => {
        var chunksCount = Math.ceil(file.size / chunkSize);
        var currentChunk = 0;
        var spark = new SparkMD5.ArrayBuffer();
        var fileReader = new FileReader();
        fileReader.onload = e => {
            spark.append(e.target.result);
            currentChunk++;
            if (currentChunk < chunksCount) {
                loadNext();
            } else {
                const md5 = spark.end();
                // 成功了
                resolve(md5);
            }
        };
        fileReader.onerror = e => {
            // 出错了
            reject("Something went wrong when reading the file");
        };
        function loadNext() {
            var start = currentChunk * chunkSize;
            var end = Math.min(start + chunkSize, file.size);

            fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
        }
        loadNext();
    });
}
发布了13 篇原创文章 · 获赞 13 · 访问量 4496

猜你喜欢

转载自blog.csdn.net/tangran0526/article/details/100888776