前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >大文件切片上传优化,子线程计算文件hash,pLimit库并发控制上传

大文件切片上传优化,子线程计算文件hash,pLimit库并发控制上传

作者头像
老K博客
发布2024-06-01 10:23:27
950
发布2024-06-01 10:23:27
举报
文章被收录于专栏:老K博客老K博客

生成hash

无论是客户端还是服务端,都要用到文件和切片的 hash,生成 hash 最简单的方法是 文件名 + 切片下标,但是如果文件名一旦修改,生成的 hash 就会失效。事实上只要文件内容不变, hash 就不应该变化,所以我们根据文件内容生成 hash。

这里我们选用 spark-md5库,它可以根据文件内容计算出文件的hash值。

代码语言:javascript
复制
imort SparkMD5 from 'spark-md5.min.js'
/**
 * 生成文件hash
 */
const chunkHash = async () => {
    message.innerText = "生成hash开始" // 生成文件hash开始
    const hash = await getFileHash(chunksList)// 生成文件hash
    message.innerText = hash // 显示hash值
    return hash
}

/**
 *
 * 获取全部文件内容hash
 * @param {any} fileList
 */
 async function getFileHash(fileList) {
    const spark = new SparkMD5.ArrayBuffer()
    const result = fileList.map((item, key) => getFileContent(item))
    try {
        const contentList = await Promise.all(result)
        for (let i = 0; i < contentList.length; i++) {
            spark.append(contentList[i])
        }

        return spark.end() // 返回hash总值
    } catch (e) {
        console.log(e)
    }
    }

/**
 *
 * 获取全部文件内容
 * @param {any} file:Blob
 * @returns
 */
function getFileContent(file) {
    return new Promise((resolve, reject) => {
        const fileReader = new FileReader()
        //读取文件内容
        fileReader.readAsArrayBuffer(file)
        fileReader.onload = e => {
            resolve(e.target.result)
        }
        fileReader.onerror = e => {
            reject(fileReader.error)
            fileReader.abort()
        }
    })
 }

如果上传的文件过大时,读取文件内容计算hash非常耗时,并且会引起 UI 阻塞,导致页面假死,所以我们使用 web-workerworker 线程计算 hash,这样仍可以在主界面正常做交互。( web-worker 使用方式不清楚的参考MDN介绍)具体做法如下:

代码语言:javascript
复制
/**
 * 生成hash
 */
const calculateHash = (fileList) => {
    message.innerText = "计算hash...";
    return new Promise((resolve, reject) => {
        window.w = new Worker('../js/setMd5.js')
        // 接收子线程内容
        window.w.onmessage = ev => {
            message.innerText = "";
            resolve(ev.data)
            w.terminate() // 停止子线程
        }
        // 发生错误,终止子线程
        window.w.onerror = err => {
            w.terminate()
            reject(error)
            console.log(error.filename, error.lineno, error.message) // 发生错误的文件名、行号、错误内容
        }
        // 发送信息
        window.w.postMessage(fileList)
    })
}

setMd5.js文件:

代码语言:javascript
复制
// 引入spaprk-md5库
self.importScripts('./spark-md5.min.js')

//接受主进程发送过来的数据
self.onmessage = function (e) {
    const fileChunkList = e.data

    getFileHash(fileChunkList)
        .then(hash => {
            self.postMessage({
                hash: hash,
            })
        })
        .catch(() => {
            self.postMessage({
                error: 'crate hash error',
            })
        })
}

/**
 *
 * 获取全部文件内容hash
 * @param {any} fileList
 */
async function getFileHash(fileList) {
    const spark = new SparkMD5.ArrayBuffer()
    const result = fileList.map((item, key) => getFileContent(item))

    try {
        const contentList = await Promise.all(result)
        for (let i = 0; i < contentList.length; i++) {
            spark.append(contentList[i])
        }
        return spark.end()
    } catch (e) {
        console.log(e)
    }
}

/**
 *
 * 获取全部文件内容
 * @param {any} file
 * @returns
 */
function getFileContent(file) {
    return new Promise((resolve, reject) => {
        const fileReader = new FileReader()
        //读取文件内容
        fileReader.readAsArrayBuffer(file)
        fileReader.onload = e => {
            resolve(e.target.result)
        }
        fileReader.onerror = e => {
            reject(fileReader.error)
            fileReader.abort()
        }
    })
}

流程图

16659ae3e7f674.png
16659ae3e7f674.png

hash值+索引号命名切片

在切片上传uploadChunks方法中调用生成文件hash代码得到hash值,将hash值+索引号作为切片名字上传.

16659ae67cbe6e.png
16659ae67cbe6e.png

并发控制切片上传

并发控制具体实现我们在"面试官:为什么网盘上传多个视频文件不能一起上传,80%人回答不清楚!"一文中有详细介绍,可以通过自己封装并发控制函数实现,也可以使用pLimit库实现。

代码语言:javascript
复制
/**
 * 限制多个并发任务,只能同时执行maxCount个
 * maxCount: 最大并发数
 */
function harexsLimit(maxCount) {
    let activeCount = 0 // 激活任务数
    let waitTask = [] // 任务队列

    const execute = (asyncFn, ...args) => {
        return new Promise((resolve, reject) => {
            const task = create(asyncFn, args, resolve, reject)
            if (activeCount >= maxCount) {
                waitTask.push(task)
            } else {
                task()
            }
        })
    }
    /**
     * 创建待执行任务
     */
    const create = (asyncFn, args, resolve, reject) => {
        return () => {
            asyncFn(...args).then(resolve).catch(reject).finally(() => {
                activeCount--
                // 每执行完一个任务启动任务任务队列下个任务
                if (waitTask.length) {
                    waitTask.shift()() //执行任务
                }
            })
            activeCount++
        }
    }

    return execute
}
16659aeab0c090.png
16659aeab0c090.png

总结

大文件切片生成hash时,如果文件过大,hash值计算会比较慢,还有一种方式就是计算抽样 Hash,减少计算的字节数可以大幅度减少耗时;在前文的代码中,我们是将大文件切片后,全量传入 spark-md5.min.js 中来根据文件的二进制内容计算文件的 hash 的。那么,举个例子,我们可以这样优化: 文件切片以后,取第一个和最后一个切片全部内容,其他切片的取首中尾 三个地方各2各字节来计算 hash。这样来计算文件 hash 会快很多。

本文共 659 个字数,平均阅读时长 ≈ 2分钟

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024年05月31日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 生成hash
  • hash值+索引号命名切片
  • 并发控制切片上传
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档