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.rhkb.cn/news/267253.html

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

相关文章

曾桂华:车载座舱音频体验探究与思考| 演讲嘉宾公布

智能车载音频 I 分论坛将于3月27日同期举办&#xff01; 我们正站在一个前所未有的科技革新的交汇点上&#xff0c;重塑我们出行体验的变革正在悄然发生。当人工智能的磅礴力量与车载音频相交融&#xff0c;智慧、便捷与未来的探索之旅正式扬帆起航。 在驾驶的旅途中&#xff0…

智慧治水丨计讯物联水利RTU助推小型水库出险加固工程建设与管理

日前&#xff0c;水利部印发《关于健全小型水库除险加固和运行管护机制的意见》&#xff08;以下简称《意见》&#xff09;&#xff0c;健全小型水库除险加固和运行管护常态化机制&#xff0c;提高小型水库安全管理水平。《意见》提出了“十四五”的两大管理机制&#xff0c;通…

基础小白快速入门c语言--

变量&#xff1a; 表面理解&#xff1a;在程序运行期间&#xff0c;可以改变数值的数据&#xff0c; 深层次含义&#xff1a;变量实质上代表了一块儿内存区域&#xff0c;我们可以将变量理解为一块儿内存区域的标识&#xff0c;当我们操作变量时&#xff0c;相当于操作了变量…

ad18学习笔记十六:如何放置精准焊盘到特定位置,捕抓功能的讲解

网上倒是一堆相关的指导 AD软件熟练度提升&#xff0c;如何设置板框捕捉&#xff1f;_哔哩哔哩_bilibili 关于Altium Designer 20 的捕抓功能的讲解_ad捕捉设置-CSDN博客 AD软件捕捉进阶实例&#xff0c;如何精确的放置布局元器件&#xff1f;_哔哩哔哩_bilibili AD绘制PCB…

Redis--持久化机制详解

什么是redis持久化&#xff1f; Redis持久化是将内存的数据持久化到磁盘上&#xff0c;防止Redis宕机或者断点的时候内存中的数据丢失&#xff0c;把内存中的数据写入到磁盘的过程叫持久化。 Redis持久化的方式&#xff1f; RDB&#xff08;Redis DataBase&#xff09;&…

MySQL 用了哪种默认隔离级别,实现原理是什么?

MySQL 的默认隔离级别是 RR - 可重复读&#xff0c;可以通过命令来查看 MySQL 中的默认隔离级别。 RR - 可重复读是基于多版本并发控制&#xff08;Multi-Version Concurrency Control&#xff0c;MVCC &#xff09;实现的。MVCC&#xff0c;在读取数据时通过一种类似快照的方…

特斯拉一面算法原题

来自太空的 X 帖子 埃隆马斯克&#xff08;Elon Musk&#xff09;旗下太空探索技术公司 SpaceX 于 2 月 26 号&#xff0c;从太空往社交平台 X&#xff08;前身为推特&#xff0c;已被马斯克全资收购并改名&#xff09;发布帖子。 这是 SpaceX 官号首次通过星链来发送 X 帖子&a…

在线上传解压PHP文件代码,压缩/压缩(网站一键打包)支持密码登录

在线上传解压PHP文件代码&#xff0c;压缩/压缩(网站一键打包)支持密码登录 资源宝分享&#xff1a;www.httple.net 如果你没有主机控制面板这个是最好选择&#xff0c;不需要数据库&#xff0c;上传当控制面板使用&#xff0c;无需安装任何扩展&#xff0c;安全高&#xff0c;…

在 Rust 中实现 TCP : 1. 联通内核与用户空间的桥梁

内核-用户空间鸿沟 构建自己的 TCP栈是一项极具挑战的任务。通常&#xff0c;当用户空间应用程序需要互联网连接时&#xff0c;它们会调用操作系统内核提供的高级 API。这些 API 帮助应用程序 连接网络创建、发送和接收数据&#xff0c;从而消除了直接处理原始数据包的复杂性。…

pyspark分布式部署随机森林算法

前言 分布式算法的文章我早就想写了&#xff0c;但是一直比较忙&#xff0c;没有写&#xff0c;最近一个项目又用到了&#xff0c;就记录一下运用Spark部署机器学习分类算法-随机森林的记录过程&#xff0c;写了一个demo。 基于pyspark的随机森林算法预测客户 本次实验采用的…

前端sql条件拼接js工具

因为项目原因&#xff0c;需要前端写sql&#xff0c;所以弄了一套sql条件拼接的js工具 ​ /*常量 LT : " < ", LE : " < ", GT : " > ", GE : " > ", NE : " ! ", EQ : " ", LIKE : " like &qu…

2024有哪些免费的mac苹果电脑深度清理工具?CleanMyMac X

苹果电脑用户们&#xff0c;你们是否经常感到你们的Mac变得不再像刚拆封时那样迅速、流畅&#xff1f;可能是时候对你的苹果电脑进行一次深度清理了。在这个时刻&#xff0c;拥有一些高效的深度清理工具就显得尤为重要。今天&#xff0c;我将介绍几款优秀的苹果电脑深度清理工具…

浅谈 Linux 孤儿进程和僵尸进程

文章目录 前言孤儿进程僵尸进程 前言 本文介绍 Linux 中的 孤儿进程 和 僵尸进程。 孤儿进程 在 Linux 中&#xff0c;就是父进程已经结束了&#xff0c;但是子进程还在运行&#xff0c;这个子进程就被称作 孤儿进程。 需要注意两点&#xff1a; 孤儿进程最终会进入孤儿院…

数据结构-带头双向循环链表

文章目录 一.头结点二.双链表1双链表的概念与结构2.与单链表相比 三.循环链表1.关于循环链表2.循环链表的优点 四.带头双向循环链表1.带头双向循环链表2.结构图3.实现 五.代码一览 一.头结点 在链表中设置头结点的作用是什么 标识链表:头结点是链表的特殊节点,它的存在能够明确…

springboot基于web的网上摄影工作室的开发与实现论文

网上摄影工作室 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了网上摄影工作室的开发全过程。通过分析网上摄影工作室管理的不足&#xff0c;创建了一个计算机管理网上摄影工作室的方案。文章介绍了网上摄影工…

1.亿级积分数据分库分表:总体方案设计

项目背景 以一个积分系统为例&#xff0c;积分系统最核心的有积分账户表和积分明细表&#xff1a; 积分账户表&#xff1a;每个用户在一个品牌下有一个积分账户记录&#xff0c;记录了用户的积分余额&#xff0c;数据量在千万级积分明细表&#xff1a;用户每次积分发放、积分扣…

gitlab添加ssh公钥

一&#xff1a;生成公钥 桌面鼠标右击打开 Open Git Bash here (前提是安装了Git)&#xff1b; 2.输入命令 ssh-keygen -t rsa -C "123*****90qq.com"来生成新的密钥对,将其中的"123*****90qq.com"替换为你自己的电子邮件地址。 命令&#xff1a;ssh-keyg…

MWC 2024丨美格智能推出5G RedCap系列FWA解决方案,开启5G轻量化新天地

2月27日&#xff0c;在MWC 2024世界移动通信大会上&#xff0c;美格智能正式推出5G RedCap系列FWA解决方案。此系列解决方案具有低功耗、低成本等优势&#xff0c;可以显著降低5G应用复杂度&#xff0c;快速实现5G网络接入&#xff0c;提升FWA部署的经济效益。 RedCap技术带来了…

Linux之嫁衣神功

前言&#xff1a;此博客内容全部转载他人&#xff0c;无一原创&#xff0c;初衷转播优质内容 1 挂载的作用 扩展存储空间 将额外的存储设备连接到Linux系统中&#xff0c;扩展系统的存储容量。 实现数据共享 不同计算机之间可以共享文件和数据&#xff0c;实现更高效的协作…

强大!信息安全技术导图全汇总!共200多张(附下载)

从网络上搜集整理了200多张信息安全技术导图&#xff0c;文末有免费领取方式。 详细文件目录 APT 攻击/ APT 攻击.png APT攻防指南基本思路v1.0-SecQuan.png Red Teaming Mind Map.png Windows常见持久控制.png 发现与影响评估.jpg …