JS利用Worker多线程大文件切片上传

在做前端上传时,会遇到上传大文件,大文件就要进行分片上传,我们整理下思路,实现一个分片上传,最终我们要拿到每一个分片的hash值,index 分片索引,以及分片blob,如下:
在这里插入图片描述

一、实现切片

index.html: 我们先创建一个html文件,用于处理选择文件,进而分片,这里利用spark-md5获取文件hash值

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>文件分片</title>
</head>
<body>
<input type="file" id="input"></input>
<script type="module">import { createChunk } from './main.js'document.getElementById('input').onchange = event => {let files = event.target.filesif (files.length) {// 进行分片createChunk(files[0])}}
</script>
</body>
</html>

main.js 默认一个分片5M,这里要向上取整,计算出分片数量

import SparkMD5 from 'https://esm.sh/spark-md5@3.0.2'const CHUNK_SIZE = 1024 * 1024 * 5 // 分片大小5M/*** @description 进行分片* @param {Object} file: 当前要处理的任务对象*/
function createChunk(file) {// 计算分片数量let count = Math.ceil(file.size / CHUNK_SIZE)console.log(count)
}

在这里插入图片描述
然后循环处理

/*** @description 进行分片* @param {Object} file: 当前要处理的任务对象*/
function createChunk(file) {// 计算分片数量let count = Math.ceil(file.size / CHUNK_SIZE)for (let i = 0; i < count; i++) {// 分片splitChunk(file, i, CHUNK_SIZE)}
}

对文件进行切片,利用file.slice(start, end)对文件进行切片处理

/*** @description 切片* @param {Object} file: file对象* @param {Number} i: 当前处理的第几个任务* @param {Number} size: 一个切片的大小*/
function splitChunk(file, i, size) {let start = i * sizelet end = (i + 1) * sizelet blob = file.slice(start, end)console.log(blob)
}

可以看到,切为若干份
在这里插入图片描述

然后获取切片的hash值,并且记录切片的起始/结束位置和切片索引index,最终返回promise

/*** @description 切片* @param {Object} file: file对象* @param {Number} i: 当前处理的第几个任务* @param {Number} size: 一个切片的大小*/
function splitChunk(file, i, size) {return new Promise((resolve, reject) => {let start = i * sizelet end = (i + 1) * sizelet blob = file.slice(start, end)// 获取文件hash值getFileHash(blob).then(res => {resolve({blob: blob,start,end,index: i,hash: res})})})
}/*** @description 获取文件hash值* @param {Object} file: 源文件信息*/
function getFileHash(file) {let spark = new SparkMD5.ArrayBuffer()return new Promise(function (resolve, reject) {let fileReader = new FileReader()fileReader.onload = function (e) {let buffer = e.target.resultif (file.size != buffer.byteLength) {reject("获取文件hash失败,按理说不可能");} else {spark.append(buffer) // 解析 Bufferresolve(spark.end())}};fileReader.onerror = function () {reject("文件初始化读取Buffer失败");};fileReader.readAsArrayBuffer(file);});
}

然后对返回的结果进行批处理,这里我们可以打印下处理的时间

/*** @description 进行分片* @param {Object} file: 当前要处理的任务对象*/
function createChunk(file) {let promises = []console.time('splitChunk')// 计算分片数量let count = Math.ceil(file.size / CHUNK_SIZE)for (let i = 0; i < count; i++) {// 分片promises.push(splitChunk(file, i, CHUNK_SIZE))}Promise.all(promises).then(res => {console.log(res)console.timeEnd('splitChunk')})
}

可以看到,处理了4秒左右,主要是花费在处理文件hash上,获取文件hash为同步任务,且占用主线程,这对于单线程的js来讲,如果文件过大,会影响当前用户的使用体验
在这里插入图片描述

二、多线程优化

上面我们已经实现切片,但是如果文件过大,带来的影响较大,所以我们可以利用js Worker进行多线程优化,这里我们判断客户端的cpu内核数量,进而开启对应数量的线程

利用浏览器navigator.hardwareConcurrency获取当前cpu内核数量,可以看到,本机的内核数为8
在这里插入图片描述
这里处理线程的开启数量

import SparkMD5 from 'https://esm.sh/spark-md5@3.0.2'const CHUNK_SIZE = 1024 * 1024 * 5 // 分片大小5M
const KERNEL_COUNT = navigator.hardwareConcurrency || 4 // 内核数量,如果取不到则为4/*** @description 进行分片* @param {Object} file: 当前要处理的任务对象*/
function createChunk(file) {// 计算分片数量let count = Math.ceil(file.size / CHUNK_SIZE)// 计算线程开启数量let workerCount = Math.ceil(count / KERNEL_COUNT)// 计算线程开启数量let workerCount = Math.ceil(count / KERNEL_COUNT)for (let i = 0; i < workerCount; i++) {// 创建一个线程,并且分配任务,这里要指定为module模块化let worker = new Worker('./worker.js', { type: "module" })// 因为线程数量是向上取整,有除不尽的情况,这里要处理下结束的chunkIndex,如果最后一个chunk大于总chunk数,则写死let end = (i + 1) * KERNEL_COUNTif (end > count) {end = count}// 分配任务worker.postMessage({file,CHUNK_SIZE,startChunkIndex: i * KERNEL_COUNT,endChunkIndex: end})// 接收处理结果worker.onmessage = e => {}}
}

多线程查看接收到的任务,一共开启8个线程,进行切片
worker.js:线程worker

// 处理线程收到消息
onmessage = e => {let {file,CHUNK_SIZE,startChunkIndex,endChunkIndex} = e.dataconsole.log(file, CHUNK_SIZE, startChunkIndex, endChunkIndex)
}

在这里插入图片描述
worker.js:接下来,我们进行切片,然后返回切片结果

import { splitChunk } from './main'// 处理线程收到消息
onmessage = async e => {let {file,CHUNK_SIZE,startChunkIndex,endChunkIndex} = e.datalet promises = []for (let i = startChunkIndex; i < endChunkIndex; i++) {promises.push(splitChunk(file, i, CHUNK_SIZE))}let chunks = await Promise.all(promises)console.log(chunks)postMessage(chunks)
}

打印下,可以看到8个线程各自处理切片,一共57个切片,最后一个线程只有一个切片任务, 7 * 8 + 1 = 57
在这里插入图片描述
main.js:然后我们接收处理结果,然后保存起来,因为这里的线程谁先完事儿是未知数,所以需要特殊处理下:

/*** @description 进行分片* @param {Object} file: 当前要处理的任务对象*/
export function createChunk(file) {return new Promise(((resolve, reject) => {let promises = []// 结果let result = []// 计算分片数量let count = Math.ceil(file.size / CHUNK_SIZE)// 计算线程开启数量let workerCount = Math.ceil(count / KERNEL_COUNT)// 当前线程执行完毕的数量let finishCount = 0for (let i = 0; i < workerCount; i++) {// 创建一个线程,并且分配任务let worker = new Worker('./worker.js', { type: "module" })// 开始let start = i * KERNEL_COUNT// 因为线程数量是向上取整,有除不尽的情况,这里要处理下结束的chunkIndex,如果最后一个chunk大于总chunk数,则写死let end = (i + 1) * KERNEL_COUNTif (end > count) {end = count}// 分配任务worker.postMessage({file,CHUNK_SIZE,startChunkIndex: start,endChunkIndex: end})// 接收处理结果worker.onmessage = e => {// 这里为了避免顺序乱,取当前的执行索引for (let i = start; i < end; i++) {result[i] = e.data[i - start]}worker.terminate() // 结束任务finishCount ++ // 完成数量++if (finishCount === workerCount) {resolve(result)}}}}))
}

index.html:我们可以打印下结果

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>文件分片</title>
</head>
<body>
<input type="file" id="input"></input>
<script type="module">import { createChunk } from './main.js'document.getElementById('input').onchange = async event => {let files = event.target.filesif (files.length) {console.time('splitChunk')// 进行分片let res = await createChunk(files[0])console.log(res)console.timeEnd('splitChunk')}}
</script>
</body>
</html>

可以看到,效率极大提升,由4秒提升到1秒,最主要不影响用户体验
在这里插入图片描述
在这里插入图片描述
断点续传就更简单了,服务端记录任务进度即可,这里就不说了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/502920.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Web安全靶场】sqli-labs-master 54-65 Challenges 与62关二分法和like模糊搜索

sqli-labs-master 54-65 Challenges 其他关卡和靶场见专栏… 文章目录 sqli-labs-master 54-65 Challenges第五十四关-联合注入第五十五关-联合注入第五十六关-联合注入第五十七关-联合注入第五十八关-报错注入第五十九关-报错注入第六十关-报错注入第六十一关-报错注入第六十…

【前端素材】推荐优质后台管理系统网页my-Task平台模板(附源码)

一、需求分析 1、系统定义 后台管理系统是一种用于管理网站、应用程序或系统的工具&#xff0c;通常由管理员使用。后台管理系统是一种用于管理和控制网站、应用程序或系统的管理界面。它通常被设计用来让网站或应用程序的管理员或运营人员管理内容、用户、数据以及其他相关功…

深度解读篇章:剖析构建互联网大厦的基石——TCP/IP协议全貌

&#x1f440;&#x1f440;&#x1f440; 引言 今天&#xff0c;我们一同揭幕的是驱动全球互联网脉搏跳动的核心机密——TCP/IP协议体系。没有它&#xff0c;就不会有现今这般高效便捷的网络生活体验&#xff0c;无论在线教育、远程办公&#xff0c;抑或是电子商务、社交媒体…

Filebeat将csv导入es尝试

一、安装 在docker中安装部署ELKfilebeat 二、主要配置 - type: log # Change to true to enable this input configuration. enabled: true # Paths that should be crawled and fetched. Glob based paths. paths: - /home/centos/pip_v2.csv #源路径 #…

生成式人工智能治理:入门的基本技巧

GenAI 以前所未有的速度调解并扰乱了“一切照旧”&#xff0c;同时带来了令人难以置信的力量&#xff0c;但也带来了不可否认的责任。当然&#xff0c;现代企业非常熟悉技术进步。然而&#xff0c;人工智能的到来&#xff08;和实施&#xff09;无疑引起了相当大的冲击&#xf…

element-plus+vue3表单含图片(可预览)(线上图片)

一、要实现的效果&#xff1a; 二、如果期间出现这样的效果&#xff08;表格穿透过来了&#xff09;&#xff0c;加上了这行代码就可以了&#xff1a; preview-teleported“true” 如果仅测试用&#xff0c;建议使用线上图片链接的形式&#xff0c;免得本地地址不生效&#xf…

12-Linux部署Zookeeper集群

Linux部署Zookeeper集群 简介 ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件&#xff0c;提供的功能包括&#xff1a;配置维护、域名服务、分布式同步、组服务等。…

Neo4j aura 官方网站快速入门新手教精读-从官方教程学习知识图谱

Neo4j 官方网站快速入门新手教精读 本文旨在为Neo4j新手提供一份全面的入门指南。除了基础的文本解释&#xff0c;我在里面还插入了每一步骤的详细截图或者自己画的图&#xff0c;从官方了解知识肯定比自己乱看要权威一些&#xff0c;有看不懂的不要纠结了解大概意思即可&#…

Docker与虚拟机比较

在对比Docker和虚拟机前&#xff0c;先简单了解下虚拟化&#xff0c;明确Docker和虚拟机分别对应的虚拟化级别&#xff0c;然后对Docker和虚拟机进行比较。需要注意的是&#xff0c;Docker和虚拟机并没有什么可比性&#xff0c;而是Docker使用的容器技术和虚拟机使用的虚拟化技…

idea 多模块A模块调用了B模块的Jar包,而非本地源码

1&#xff0c;问题描述 对于多模块的互相调用&#xff0c;比如模块A&#xff0c;模块B&#xff0c;模块C&#xff0c; 这在本地都是可以编辑进行开发的源码&#xff0c; 按理说是模块A可以直接点进模块B的本地源码&#xff0c; 但是不知道什么原因&#xff0c;导致模块A点进…

Java 学习和实践笔记(26):组合(component)的含义以及与继承(extends)的关系

组合的两个作用&#xff1a; 1&#xff09;通过将父类对象作为子类的属性 2&#xff09;通过第1点的作用&#xff0c;实现了代码复用。 示例代码&#xff1a; public class TestComponent {public static void main(String[] args) {Student2 s1 new Student2("jason&…

聚观早报 | 爱奇艺2023年Q4财报;苹果将加大AI投入

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 3月1日消息 爱奇艺2023年Q4财报 苹果将加大AI投入 意大利正与多家车企谈判 多家企业与百度达成合作 比亚迪宋PL…