1.问题点
平常从客户端上传文件到服务器端,只需要读取 input 的 File 对象,然后将其塞到 FormData 对象中,然后使用 ajax 发送到服务器端,服务器端会有配套的读文件和写文件的操作。
现在遇到一个问题,就是在本地一个进程中,浏览器和其他代码间数据的发送,因为不经过 HTTP,这时候只有将文件的信息读取出来,然后通过二进制或是 base64 编码的格式发送。
HTML5 的 FileReader API 可以在浏览器对用户本地文件进行读取,但是在使用的过程中又遇到了新的问题,比如通过 FileReader.readAsDataURL() ,使用时它会读取一整个文件的信息,加载到内存中,50MB 以内的文件基本不会遇到什么问题,但是当文件达到 300MB 甚至是 1G 以上,直接读取一整个文件,会导致内存超出上限,浏览器这时候就会崩溃,因此如果想要处理大的文件,我们只能将大文件切成一块一块的,将其变成小文件后再去处理,FileReader 也提供了相关的 API,即 FileReader.readAsArrayBuffer() ,下面就看一下使用 FileReader.readAsArrayBuffer() 进行切块的处理吧。
2.大文件切块处理的思路
- 使用 FileReader.readAsArrayBuffer() 读取文件,在读取成功后 result 属性中将包含一个 ArrayBuffer 对象以表示所读取文件的数据。
- 对读取到的 ArrayBuffer 对象进行拆分,放到一个数组中
- 使用 Promise.all 来顺序的处理拆分后的数组(保证顺序传输)
- 使用 Uint8Array 将 ArrayBuffer 拆后后的数组生成 二进制字节数组
- 最后将二进制字节数组转为 base64 编码的字符串
- 传输的是 base64 编码的字符串,到达目的地后,需要使用 base64 解码,才能得到原始的二进制字节信息
FileReader提供的方法 | 说明 |
---|---|
readAsArrayBuffer(file) | 按字节读取文件内容,结果用ArrayBuffer对象表示 |
readAsBinaryString(file) | 按字节读取文件内容,结果为文件的二进制串 |
readAsDataURL(file) | 读取文件内容,结果用data:url的字符串形式表示 |
readAsText(file,encoding) | 按字符读取文件内容,结果用字符串形式表示 |
abort() | 终止文件读取操作 |
FileReader事件 | 说明 |
---|---|
onloadstart | 当读取操作开始时调用 |
当读取操作开始时调用 | 在读取数据过程中周期性调用 |
onabort | 当读取操作被中止时调用 |
onerror | 当读取操作发生错误时调用 |
onload | 当读取操作成功完成时调用 |
onloadend | 当读取操作完成时调用,无论成功,失败或取消 |
3.以下代码实现的功能
- 在特定区域识别拖拽的文件
- 有一个配置项:
小于 20 MB 算小文件,直接整个处理;
大于 20 MB 算大文件,进行切块处理; - 大文件切块后,将依次按照切块的顺序上传
- 注意事项:最后从浏览器传输文件数据的时候,传输的是 base64 编码的字符串,因此传送到目的地后,需要 base64 解码后再进行操作
DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Documenttitle> <style> * { padding: 0; margin: 0; } body { width: 100vw; height: 100vh; } .drag-area-container { width: 800px; height: 800px; border: 1px solid #0f0; } .highlight { border: 10px solid #0f0; } style>head><body> <div class="drag-area-container"> div> <script> (function() { // 禁止浏览器的拖拽默认事件 var globalDragFile = function() { let globalDragArea = document.querySelector('body') globalDragArea.addEventListener('dragover',function(e){ e.preventDefault() }) globalDragArea.addEventListener('drop', function(e) { e.preventDefault() }) } // 识别指定区域的拖动效果 var localDragFile = function() { let localDragArea = document.querySelector('.drag-area-container') localDragArea.addEventListener('dragenter',function(e){ // 拖动文件到指定区域时,添加高亮 localDragArea.classList.add('highlight') e.preventDefault() }) localDragArea.addEventListener('dragover',function(e){ e.preventDefault() }) localDragArea.addEventListener('drop', function(e) { e.preventDefault() // 去除高亮 localDragArea.classList.remove('highlight') var file = e.dataTransfer.files[0] console.log("file = ", file); uploadFile(file) }) } // 处理文件 && 上传文件 var uploadFile = function(file) { // 使用 FileReader 读取文件的数据 var reader = new FileReader() reader.onloadend = function() { var file_result = this.result // ArrayBuffer 数据对象 var file_length = file_result.byteLength // 小于 20 MB 为小文件,则整个读取并上传 // 大于 20 MB 为大文件,则需要将它切成小块,分别上传 var step = 1024 * 1024 * 20 if (file_length < step) { console.log("小文件,直接整个上传 "); handleSmallFile(file_result) } else { console.log("大文件,切块分别上传 "); var block_arr = splitBigFile(file_result, file_length, step) handleBigFile(block_arr).then(function(results) {console.log("大文件,切块上传成功 result = ", results) }) } } reader.readAsArrayBuffer(file) } var handleSmallFile = function(file_result) { // 先读取到 ArrayBuffer,再获取 ArrayBuffer 的 Uint8Array 字节数组形式,最后用 base64 编码字节数组用于传输。 var unit8_data = new Uint8Array(file_result) // 提取二进制字节数组,使用 Uint8Array 表示 var base64_data = binary2base64(unit8_data) // base64 编码 console.log("==== handle upload start ===="); console.log("data = ", base64_data); console.log("==== handle upload end ===="); } // 根据指定的 step 大小,切出来指定的 step 大小的块 var splitBigFile = function(file, file_length, step) { var step_times = Math.ceil(file_length / step) var start = 0 var block_arr = [] for (i = 0; i < step_times; i++) { var block = file.slice(start, start + step) start = start + step block_arr.push(block) } return block_arr } var handleBigFile = async function(big_files) { return Promise.all([].map.call(big_files, function(file, index) { return new Promise(function(resolve, reject) { // 先读取到 ArrayBuffer,再获取 ArrayBuffer 的 Uint8Array 字节数组形式,最后用 base64 编码字节数组用于传输。 var view = new Uint8Array(file) // 提取二进制字节数组,使用 Uint8Array 表示 var base64_data = binary2base64(view) // base64 编码 console.log("==== handle upload start ===="); console.log("block index = ", index); console.log("data = ", base64_data); console.log("==== handle upload end ===="); resolve("Promise file") }) })).then(function(results) { return results; }) } // 二进制字节数组转 base64 编码的字符串 var binary2base64 = function(bi) { let str = ''; for (let i = 0, len = bi.length; i < len; i++) { str += String.fromCharCode(bi[i]); } return btoa(str); } var __main = function() { // 禁止浏览器的拖拽默认事件 globalDragFile() // 识别指定区域的拖动效果 localDragFile() } __main() })() script>body>html>
来源地址:https://blog.csdn.net/weixin_44860226/article/details/129797332