架构师_程序员

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 287|回复: 1

[HTML/HTML5] 使用FileReader.readAsArrayBuffer()在浏览器中处理大文件

[复制链接]
跳转到指定楼层
楼主
发表于 2019-6-2 20:35:17
zu
HTML5的FileReader API可以让客户端浏览器对用户本地文件进行读取,这样就不再需要上传文件由服务器进行读取了,这大大减轻了服务器的负担,也节省了上传文件所需要的时间。不过在实践中我发现用FileReader.readAsText()可以轻易地处理一个300k的日志文件,但当日志文件有1G、甚至2G那么大,浏览器就会崩溃。这是因为readAsText()会一下子把目标文件加载至内存,导致内存超出上限。所以如果Web应用常常需要处理大文件时,我们应该使用FileReader.readAsArrayBuffer()来一块一块读取文件。

测试场景

我们的场景很简单,就是使用JavaScript来获取某个IIS日志的时间范围

样例IIS日志:

#Software: Microsoft Internet Information Services 10.0
#Version: 1.0
#Date: 2016-08-18 06:53:55
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken
2016-08-18 06:53:55 ::1 GET / - 80 - ::1 Mozilla/5.0+(Windows+NT+10.0;+WOW64;+Trident/7.0;+rv:11.0)+like+Gecko - 200 0 0 476
2016-08-18 06:53:55 ::1 GET /iisstart.png - 80 - ::1 Mozilla/5.0+(Windows+NT+10.0;+WOW64;+Trident/7.0;+rv:11.0)+like+Gecko http://localhost/ 200 0 0 3
2016-08-18 08:45:34 10.172.19.198 GET /test/pac/wpad.dat - 80 - 10.157.21.235 Mozilla/5.0+(Windows+NT+6.1;+Win64;+x64;+Trident/7.0;+rv:11.0)+like+Gecko - 404 3 50 265
2016-08-18 08:46:44 10.172.19.198 GET /test/pac/wpad.dat - 80 - 10.157.21.235 Mozilla/5.0+(Windows+NT+6.1;+Win64;+x64;+Trident/7.0;+rv:11.0)+like+Gecko - 200 0 0 6
我们的目标就是获取该段日志的时间范围:

开始时间:2016-08-18 06:53:55
结束时间:2016-08-18 08:46:44

使用readAsText()的实现

使用readAsText()实现是比较简单的,得到整个文件的字符串后,从头获取每一行的前19个字符,判断是否满足日期的格式,如果满足那么这19个字符就是开始时间,同理从尾部遍历每一行来获取结束时间,代码如下:


  1. <input type="file" id="file" />
  2. <button id="get-time">Get Time</button>
  3. <script>
  4.     document.getElementById('get-time').onclick = function () {
  5.         let file = document.getElementById('file').files[0];
  6.         let fr = new FileReader();
  7.         fr.onload = function (e) {
  8.             let startTime = getTime(e.target.result, false);
  9.             let endTime = getTime(e.target.result, true);
  10.             alert(`Log time range: ${startTime} ~ ${endTime}`);
  11.         }
  12.         fr.readAsText(file);
  13.     }
  14.     function getTime(text, reverse) {
  15.         let timeReg = /\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}/;
  16.         for (let i = reverse ? text.length - 1 : 0; reverse ? i > -1 : i < text.length; reverse ? i-- : i++) {
  17.             if (text[i].charCodeAt() === 10) {
  18.                 let snippet = text.substr(i + 1, 19);
  19.                 if (timeReg.exec(snippet)) {
  20.                     return snippet;
  21.                 }
  22.             }
  23.         }
  24.     }
  25. </script>
复制代码
样例IIS日志(大小:1k)的运行结果符合我们的预期。



可是一旦我们选择了一个较大的IIS日志(大小:2G),浏览器就崩溃了。原因就是readAsText()会先把整个文件加载到内存中,那么如果文件太大,内存就不够用了,浏览器进程就会崩溃。


使用readAsArrayBuffer()的实现

由于JavaScript中的File对象继承自Blob,所以我们完全可以用Blob.slice()方法将文件切成小块来处理,大致思路是:

先取文件的前10k内容,转换成文本
从头获取每一行的前19个字符,判断是否满足日期的格式,如果满足那么这19个字符就是开始时间
再取文件尾部的10k内容,转换成文本
同理从尾部内容遍历每一行来获取结束时间

代码如下:


  1. <input type="file" id="file" />
  2. <button id="get-time">Get Time</button>
  3. <script>
  4.     document.getElementById('get-time').onclick = function () {
  5.         let file = document.getElementById('file').files[0];
  6.         let fr = new FileReader();
  7.         let CHUNK_SIZE = 10 * 1024;
  8.         let startTime, endTime;
  9.         let reverse = false;
  10.         fr.onload = function () {
  11.             let buffer = new Uint8Array(fr.result);
  12.             let timeReg = /\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}/;
  13.             for (let i = reverse ? buffer.length - 1 : 0; reverse ? i > -1 : i < buffer.length; reverse ? i-- : i++) {
  14.                 if (buffer[i] === 10) {
  15.                     let snippet = new TextDecoder('utf-8').decode(buffer.slice(i + 1, i + 20));
  16.                     if (timeReg.exec(snippet)) {
  17.                         if (!reverse) {
  18.                             startTime = snippet;
  19.                             reverse = true;
  20.                             seek();
  21.                         } else {
  22.                             endTime = snippet;
  23.                             alert(`Log time range: ${startTime} ~ ${endTime}`);
  24.                         }
  25.                         break;
  26.                     }
  27.                 }
  28.             }
  29.         }
  30.         seek();
  31.         function seek() {
  32.             let start = reverse ? file.size - CHUNK_SIZE : 0;
  33.             let end = reverse ? file.size : CHUNK_SIZE;
  34.             let slice = file.slice(start, end);
  35.             fr.readAsArrayBuffer(slice);
  36.         }
  37.     }
  38. </script>
复制代码
使用了readAsArrayBuffer()后,即使是2G多的IIS日志,我们也能在很短的时间内获得我们想要的结果。






上一篇:php调用短信验证码接口
下一篇:export命名导出与默认导出
帖子永久地址: 

架构师_程序员 - 论坛版权1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关
2、本站所有主题由该帖子作者发表,该帖子作者与架构师_程序员享有帖子相关版权
3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和架构师_程序员的同意
4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任
5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意
7、架构师_程序员管理员和版主有权不事先通知发贴者而删除本文

码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
沙发
 楼主| 发表于 2019-6-2 20:39:24
  1. importScripts('core-min.js');
  2. importScripts('lib-typedarrays-min.js');
  3. importScripts('md5.js');
  4. importScripts('sha1.js');
  5. importScripts('sha224.js');
  6. importScripts('sha256.js');
  7. importScripts('sha384.js');
  8. importScripts('sha512.js');
  9. importScripts('sha3.js');

  10. self.algo = null;

  11. self.addEventListener('message', function(evt){
  12.                 if(evt.data.algo==="MD5"){
  13.                         self.algo = CryptoJS.algo.MD5.create();
  14.                 }
  15.                 else if(evt.data.algo.toUpperCase()==="SHA1"){
  16.                         self.algo = CryptoJS.algo.SHA1.create();
  17.                 }
  18.                 else if(evt.data.algo.toUpperCase()==="SHA2-224"){
  19.                         self.algo = CryptoJS.algo.SHA224.create();
  20.                 }
  21.                 else if(evt.data.algo.toUpperCase()==="SHA2-256"){
  22.                         self.algo = CryptoJS.algo.SHA256.create();
  23.                 }
  24.                 else if(evt.data.algo.toUpperCase()==="SHA2-384"){
  25.                         self.algo = CryptoJS.algo.SHA384.create();
  26.                 }
  27.                 else if(evt.data.algo.toUpperCase()==="SHA2-512"){
  28.                         self.algo = CryptoJS.algo.SHA512.create();
  29.                 }
  30.                 else if(evt.data.algo.toUpperCase()==="SHA3-224"){
  31.                         self.algo = CryptoJS.algo.SHA3.create();
  32.                         self.algo.init({outputLength: 224});
  33.                 }
  34.                 else if(evt.data.algo.toUpperCase()==="SHA3-256"){
  35.                         self.algo = CryptoJS.algo.SHA3.create();
  36.                         self.algo.init({outputLength: 256});
  37.                 }
  38.                 else if(evt.data.algo.toUpperCase()==="SHA3-384"){
  39.                         self.algo = CryptoJS.algo.SHA3.create();
  40.                         self.algo.init({outputLength: 384});
  41.                 }
  42.                 else if(evt.data.algo.toUpperCase()==="SHA3-512"){
  43.                         self.algo = CryptoJS.algo.SHA3.create();
  44.                         self.algo.init({outputLength: 512});
  45.                 }
  46.                 else{
  47.                         self.algo = CryptoJS.algo.MD5.create();
  48.                 }
  49.                 hashFile(evt.data.file, self);
  50. }, false);

  51. function hashFile(file, worker){
  52.         var chunkSize = 2097152, start = 0, end, fileReader;
  53.     handle_load_blob=function(e){
  54.                 worker.algo.update(CryptoJS.lib.WordArray.create(e.target.result));
  55.                 if(end===file.size){
  56.                         worker.postMessage({'result':worker.algo.finalize().toString()});
  57.                 } else {
  58.                         worker.postMessage({'progress': (end/file.size*100).toFixed(2)});
  59.                         start += chunkSize;
  60.                         end = start + chunkSize;
  61.                         end = end>file.size? file.size: end;
  62.                         fileReader.readAsArrayBuffer(file.slice(start, end));
  63.                 }
  64.     }

  65.         end = start + chunkSize;
  66.         end = end>file.size? file.size: end;

  67.         fileReader = new FileReader();
  68.         fileReader.onload = handle_load_blob;
  69.         fileReader.readAsArrayBuffer(file.slice(start, end));
  70. }
复制代码
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

免责声明:
码农网所发布的一切软件、编程资料或者文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。

Mail To:help@itsvse.com

QQ|Archiver|手机版|小黑屋|架构师 ( 鲁ICP备14021824号-2 )|网站地图

GMT+8, 2019-7-17 11:15

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表