前端面试题扩展(二)——大文件上传

原因:

由于上传大文件时,容易碰到以下的问题:

  1. 上传时间比较久
  2. 中间一旦出错就需要重新上传
  3. 一般服务端会对文件的大小进行限制
    这些问题会导致上传时用户体验不好,因此可以通过 分片上传 来解决

原理:

分片上传的原理就像是将一个大蛋糕切成小块一样。
首先,将需要上传的大文件分成许多个小块,每个小块大小相同,然后逐步上传这些小块到服务器。上传时 可以同时上传多块 也可以一块一块上传 服务器会保存这些小块 并记录顺序和位置信息
所有的小块上传完成后 服务器会将这些小块按照顺序拼接并还原成完整文件

优点:

可以减少上传失败的风险 上传失败 需要上传出错的一小块 不需要重新上传整个大文件
可以加快上传的速度

实现过程:

读取文件

1、要实现大文件上传首先要读取本地的文件,读取本地的文件可以通过,把input输入框组件的type属性设置为file,这样就可以通过input选择本地的文件。
2、监听input输入框的change事件,并提供对应的事件处理函数,e.target.files是一个伪数组,不能使用数组的方法,可以通过下标拿到每一项的值,files的第一个元素也就是下标为0的元素,就是我们需要的文件,它是File对象。

文件分片

1、文件分片的核心用的是Blob对象的slice方法,数组也有slice方法,数组里面的slice方法是用来分割数组的,Blob里面的slice是用来分割文件的。我们在上一步获取到的选择的文件是一个File对象,但是slice方法是BloB对象的,那么File对象可不可以用BloB的slice方法呢,其实是可以的,因为File对象就继承与Blob对象。slice的使用方法和数组的slice差不多,也支持传入起始位置和结束位置,Bolb对象的slice传入的字节的起始位置和结束位置。
2、文件分片的函数 。定义一个分片的函数,在读取文件之后,调用这个函数,并把获取到的文件传进去。在分片的时候,我们首先要确定分片的大小是多少。(1MB=1024KB=10241024B,B就是字节),所以分片的时候,通常是按照M兆,开划分的,==每一个分片的大小给它定义成1兆,也就是10241024B,因为slice方法是根据字节来划分的==。所以定义一个变量chunk_size,它的大小就是1024*1024 ,也就是1兆。
首先定义一个变量cur,代表这个文件分到哪了,默认值为0。再定义一个空数组chunks,去接收所有的分片。在while中循环去分片,当已经分包的大小小于文件的整体大小的时候,说明当前文件还没有分完,就可以继续分,file.slice(开始位置,结束位置),开始位置是cur,结束 位置是 cur+chunk_size,用变量files接收,代表一个分片,然后把这个分片通过push到空数组里面,chunks.push(files), 每次循环结束,cur小于+=chunk_size,我们把这个数组return出去,然后调用这个分片函数的时候,就可以打印出这个分片数组,选中一个文件,就可以打印出根据文件大小被切成了多少份。

区分文件

在向服务器上传文件时,可能一次上传了很多文件,此时就需要区分不同的文件。可以根据文件内容生产出一个惟一的hash值,文件内容发生变化,hash值就会发生变化,通过这个特点,还可以实现秒传的功能。因为相同内容的文件hsh值相同,所以当我把一个文件上传服务器的时候,服务器会拿到一个hash值,当其他人上传同样文件的时候,服务器就会比较两个文件的hash值,如果一样,说明服务器此时已经有对应的文件了,此时服务器就会直接向用户抛出‘上传成功’,用户的感觉是秒传,但实际上是并没有上传。
如何计算文件的hash值,使用的是一个包来计算文件的hash值,spark-md5。所以第一步安装这个包,pnpm add spark-md5。安装完这个包就可以在这个项目里面用了。既然要计算文件的hash值,那所有的文件内容都要参与运输,一个G的文件需要大约10多秒,会影响用户体验,所以采用切片的方式计算hash值:1、第一个和最后一个切片的全部内容都参与计算。2、中间剩余切片分别在前面、后面和中间取两个字节参与计算。这样就不是所有的内容都参与计算,就不会耗费很长的时间
提供一个计算hash值的函数,并在获取文件切片之后,调用这个函数。并把存储切片数组传进去。拿到切片数组之后,通过forEach进行遍历,拿到数组下标为0和最后一个下标的,也就是第一个和最后一个切片,把他放到一个空数组targets里面,这个数组用来储存参与hash值计算的切片。切片的前两个字节和后两个字节和中间的两个字节通过slice截取

前面两个字节: targets.push(chunk.slice(0,2))
最后两个字节: targets.push(chunk.slice(CHUNK_SIZE-2,CHUNK_SIZE))
前两个字节:targets.push(chunk.slice(CHUNK_SIZE/2,CHUNK_SIZE/2+2))

计算hash值

需要用到SparkMD5 身上的构造函数ArrayBuffer() /ˈbʌfə®/ ,创建一个spark对象
const spark = new SparkMD5.ArrayBuffer();
还需要创建一个fileReader = new FileReader() ,这样需要用到的对象就创建好了。
然后需要用fileReader身上的readAsArrayBuffer()方法,把用来存储参与hash计算的切片数组传给new Blob(targets),把他转换成Blob对象,然后把Blob对象作为参数传给fileReader身上的readAsArrayBuffer()方法,把他转换成ArrayBuffer对象,就可以读取它了

上传切片

定义一个上传分片的函数,并在获取hash值之后调用,把分片数组作为参数传过去。在上传分片的函数里面去书写上传分片的逻辑
接下来处理 ,定义最大的并发请求数 const max = 6
因为每一个FormData对象都要上传,所以还有一个下标,用来标识我现在上传到第几个了 let index = 0
然后我们写一个while循环,去一次上传FormData对象,条件就是下标index 小于formDatas数组的长度。如果满足这个条件,说明文件还没有上传完。
给每一个请求,建立一个请求池,const taskPool = [ ] , 请求池就是一个数组,相当于每个请求池最多只要6个,完成一个请求,就添加一个新的请求。 然后我们可以模拟用fetch发起请求,上传第几个FormData对象,就可以通过下标区分。现在上传的任务有了,就需要把他们添加到taskPool请求池数组里面。然后每一个请求完成之后,还需要把请求从请求池数组里面移除出来。移出来就可以用taskPool.splice。并且还需要有一个判断,判断请求池的长度,如果已经有6个请求了,是不是还需要等,等可以使用promise.rece()方法
如果最后请求池里面还有请求,为了以防万一,调一个Promise.all(),保证所有的请求都完成。
在这里插入图片描述

  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值