文件分片上传

文章目录

  • 为什么大文件要分片上传呢?
  • 整个过程
  • OK,我们大文件上传光有分片就完事儿了吗?
  • 代码实现:

今天面试的时候,说到大文件上传这里,面试官大佬问了分片上传,但是我只知道一些浅薄的分片上传。现在来详细学一下上传方式吧!

为什么大文件要分片上传呢?

是因为文件一旦过大,如果仅当成一次请求发送的话,这个请求的时间就会非常的长,一旦请求的过程中出现一些问题,比方说网络断开了,那么就不得不把整个文件重新上传一遍,那这样代价就太大了, 也非常浪费资源,所以说我们在做大文件上传的时候往往会对文件进行分片。

整个过程

就是在客户端这边,首先把整个的大文件数据分成一个一个的数据小块,可以把每一块想象成一个单独的小文件,然后利用单文件上传,把这些小文件以此上传到服务器,当最后把文件全部传输完成之后,在服务器端使用程序把整个文件的小数据组装起来(后端负责的),形成一个完整的文件。

分片上传很快,瞬间就可以完成。

无论是file文件还是Blob文件,里面其实只是保存了文件的基本信息,比方说,file保存了文件的大小(size),文件的类型(type),文件的名字(name),位置这些基本信息,并没有保存文件的数据,Blob保存了文件的大小和类型,所以我们对文件进行分片其实就是一个简单的数据运算而已,如果说我们将来需要读取他的数据的时候,需要用到FileReader,才能真正的把数据去读取出来,这就是说分片的数据为什么是非常非常快的。

OK,我们大文件上传光有分片就完事儿了吗?

假设我们上传分片,上传到一半的时候,断网了,要接着上传,那之前上传的分片肯定就不用上传了,这就属于文件秒传。
OK,现在来看一下基本原理:
就是跟服务器的一次对话,比方说我现在刷新了页面,我要重新上传这个文件,我就问一下服务器,“我要给你上传一个文件,这个文件呢,你告诉我是什么情况,我上传过了没有啊还有哪些分片没有上传”服务器一看,这些呢之前已经上传过这些分片了,那我就告诉了还有哪些编号的分片你还需要传递给我,通过一个ajax请求,客户端就知道接下来文件该怎么处理,在交互过程中,客户端必须告诉服务器一个关键的信息,
那什么叫 “ 这个文件呢?”
需要用一个东西来描述这个文件,那怎么来描述这个文件呢,首先肯定不能使用文件名,因为文件名容易重复,用路径也不行,因为路径会变化,所以需要找一个唯一的能代表这个文件的东西,就是文件hash,

哈希是一种算法,它可以把任何数据,不管是二进制也好还是一个字符串也好,换算成一个固定长度的字符串,这个换算是单向的,就是从数据到这个hash值,他是不可逆的,也就是说不能通过字符串反向推导出数据是啥,这有什么意义呢?哈希还有个特点,它对数据变化非常敏感,哪怕两个数据里面只换算了一个字节,那整个换算结果就完全不一样了,那么也就意味着,我们可以利用这个hash值,来代表文件的整个数据

上传的时候告诉你,我现在上传的文件的hash值是啥,服务器你记录一下,下一次我再重传的时候,我再告诉你,你看一下这个hash值之前 有没有传过,还有哪些分片还要上传。

通过hash值就能代表整个文件的内容,从数据到hash值的过程称之为hash算法,hash算法有很多,我们常用的是md5,关键点来了
我们如何在客户端计算出这个文件的hash值?
嘿嘿不知道算法的可以使用第三方库(反正我不知道)
spark-md5可以npm安装,也可以页面上script标签引入.

代码实现:

<input type="file"/>
<script src="这里引入一下spark-md5.js路径,我可以假装在这里引入了,你可以假装看见"></script>
<script>
const inp=document.querySelector('input');
inp.onchange= async(e)=>{
	const file = inp.files[0];
	if(!file){
	return;
	}
	const chunks=createChunks(file,10*1024*1024);
	//调用一下
	coust result=await hash(chunks);
	console.log(result);
	//const piece=file.slice(0,100);//取0~99个数据
	//console.log(piece);//Blob类型,也是表示文件数据的,
	//也就是说用ajax请求的时候可以直接把这个东西发到服务器,它的用法跟file对象的用法是一样的
}

//引入spark-md5之后要做的事情不推荐一次性计算整个文件的hash,因为要计算hash值必须拿到文件的数据,
//全部拿到要是这个文件数据很大多费劲啊,把这些数据全部读取到内存里面然后去计算hash值,内存吃不消啊,
//所以我们的hash要用分快去算,这里叫做增量算法 (就是先用其中一块数据计算出一个结果,然后计算过后这块数据就不要了,接下来就是下一个数据来了,再跟之前的结果一起计算一个新的结果,然后不要酱紫内存就不会撑爆,)
function hash(chunks){ 
// 因为需要时间 所以封装一个异步函数
return new Promise(resolve=>{

//写一个递归的函数,读取第几个分快
	function _read(i){
	//终止条件
	if(i>=chunks.length){
	//告诉它完事儿了得到一个hash
	reso(spark.end())
	return;//读完了呗
	}
	const blob=chunks[i];
	const reader = new FileReader();
	//读取的过程是异步的,所以有个方法叫onload
	reader.onload = e =>{
		const bytes=e.target.result//读取到字节数组,并使用增量hash进行计算用第三方库
		//吧一组字节加到这个运算当中
		spark.append(bytes);
		//加入下一个分片
		_read(i+1);
	}
	//读取它的字节数
	reader.readAsArrayBuffer(blob);
	}
	_read(0)
})

}
function creatChunks(file,chunkSize){//写个工具函数,传递一个file对象,然后再传进来一个每个切片的大小,
//单位是字节,来完成切片
//先创建好一个数组来获取每一个切片
	const result=[];
//然后就使用循环了,每取完一次,往前跳进一个切片的长度
	for(let i=0;i<file.size;i+=chunkSize){
	
	//把每次切片的结果放到result数组里面取
		result.push(file.slice(i,i+chunkSize);)
	}
		return result;
	}

</script>

在实际开发中,运算过程一般是不会放到主线程里面的,运算时间比较长,我们可以利用web worker单独开一个线程去处理这个事情,这样避免浏览器卡死。

但是如果放到单独的线程里面去还是可能会导致卡顿,因为他是一个CPU特别密集的任务,所以很多b站抖音很多站点还当发现文件特别大的时候,先粗略对文件分成一个大块,单独去计算每个大块,这样计算起来就非常快了,先计算大块,当空闲的时候再计算大块里面的hash值。

你可能感兴趣的:(前端)