• 技术文章 >web前端 >js教程

    浅析Nodejs怎么进行大文件读写

    青灯夜游青灯夜游2022-09-28 20:09:28原创462

    大前端零基础入门到就业:进入学习

    笔者最近在做一些node端的文件读写和分片上传工作,在这个过程中,发现node读取的文件如果超过2G,超过了读取Blob最大值,会出现读取异常,此外在node中读写文件也受服务器RAM的限制等,需要分片读取,本人记录一下遇到的问题以及解决问题的经过。【相关教程推荐:nodejs视频教程

    • node中的文件读写
    • node文件读写RAM和Blob大小的限制
    • 其他

    一、node中的文件读写

    1.1 常规文件读写

    常规的,如果我们要读取一个比较小的文件,可以直接通过:

    const fs = require('fs')
    let data = fs.readFileSync("./test.png")
    console.log(data,123)
    //输出data = <Buffer 89 50 4e ...>

    一般而言,同步的方法不是很推荐,因为js/nodejs是单线程的,同步的方法会阻塞主线程。最新版的node直接提供了fs.promise,可以结合async/await直接使用:

    const fs = require('fs')
    const readFileSync = async () => {
        let data = await fs.promises.readFile("./test.png")
        console.log(data,123)
    }
    readFileSync()
    //输出data = <Buffer 89 50 4e ...>

    这里通过异步的方法调用不会阻塞主线程,多个文件读取的IO也可以并行进行等。

    1.2 Stream文件读写

    常规的文件读写,我们会把文件一次性的读取到内存中,这种方法时间效率和内存效率都很低,时间效率低是指必须要一次性读取完毕后才能执行后续才做,内存效率低是指必须把这个文件都一次性读取放入内存中,很占用内存。因此这种情况下,我们一般使用Stream来进行文件的读取:

    const fs = require('fs')
    const readFileTest = () => {
        var data = ''
        var rs = fs.createReadStream('./test.png');
        rs.on('data', function(chunk) {
            data += chunk;
            console.log(chunk)
         });
        rs.on('end',function(){
            console.log(data);
        });
        rs.on('error', function(err){
            console.log(err.stack);
         });
    }
    readFileTest()
    // data = <Buffer 89 50 64 ...>

    通过Steam来进行文件读写,可以提高内存效率和时间效率。

    Stream的文件还支持第二种写法:

    const fs = require('fs')
    const readFileTest = () => {
        var data = ''
        var chunk;
        var rs = fs.createReadStream('./test.png');
        rs.on('readable', function() {
        while ((chunk=rs.read()) != null) {
            data += chunk;
        }});
        rs.on('end', function() {
            console.log(data)
        });
    };
    readFileTest()

    二、node文件读写RAM和Blob大小的限制

    2.1 基础问题

    在读取大文件时,会有读取文件大小的限制,比如我们现在在读取一个2.5G的视频文件:

    const fs = require('fs')
    const readFileTest = async () => {
        let data = await fs.promises.readFile("./video.mp4")
        console.log(data)
    }
    readFileTest()

    执行上述的代码会报错:

    RangeError [ERR_FS_FILE_TOO_LARGE]: File size (2246121911) is greater than 2 GB

    我们可能会想到,通过设置option,NODE_OPTIONS='--max-old-space-size=5000',此时5000M>2.5G,但是报错还是没有消失,也就是说通过Options无法改变node读取文件的大小限制。

    上述是常规的方式读取大文件,如果通过Steam的方式读取还会有文件大小的限制嘛? 比如:

    const fs = require('fs')
    const readFileTest = () => {
        var data = ''
        var rs = fs.createReadStream('./video.mp4');
        rs.on('data', function(chunk) {
            data += chunk;
         });
        rs.on('end',function(){
            console.log(data);
        });
        rs.on('error', function(err){
            console.log(err.stack);
         });
    }
    readFileTest()

    如上方式读取一个2.5G的文件不会有异常,不过要注意的是这边有一个报错:

    data += chunk;
                    ^
    
    RangeError: Invalid string length

    此时是因为data的长度超过了最大限制,比如2048M等。因此在用Steam处理的时候,在对读取结果的保存时,要注意文件的大小,千万不能超过默认的Buffer的最大值。上述这种情况,我们不用data += chunk将数据全部保存在一个大的data中,我们可以边读取边处理。

    2.2 分片读取

    createReadStream在读取文件的过程中,其实也可以分段读取,这种分段读取的方法也可以做为大文件读取的备选项。特别是在并发读取的时候有一定的优点,可以提升文件读取和处理的速度。

    createReadStream接受第二个参数{start,end}。我们可以通过fs.promises.stat来获取文件的大小,然后确定分片,最后分片一次读取,比如:

    1. 获取文件大小
    const info = await fs.promises.stat(filepath)
       const size = info.size
    1. 按照指定的SIZE分片(比如128M一个分片)
      const SIZE = 128 * 1024 * 1024
      let sizeLen = Math.floor(size/SIZE)
        let total = sizeLen +1 ;
        for(let i=0;i<=sizeLen;i++){
          if(sizeLen ===i){
            console.log(i*SIZE,size,total,123)
            readStremfunc(i*SIZE,size,total)
          }else{
            console.log(i*SIZE,(i+1)*SIZE,total,456)
            readStremfunc(i*SIZE,(i+1)*SIZE-1,total)
          }
        }
      //分片后【0,128M】,【128M, 256M】...

    3.实现读取函数

    const readStremfunc = () => {
        const readStream =  fs.createReadStream(filepath,{start:start,end:end})
        readStream.setEncoding('binary')
        let data = ''
        readStream.on('data', chunk => {
            data = data + chunk
        })
        readStream.end('data', () => {
          ...
        })
    }

    值得注意的是fs.createReadStream(filepath,{start,end}),start和end是前闭后闭的,比如fs.createReadSteam(filepath,{start:0,end:1023})读取的是[0,1023]一共1024个bit。

    三、其他

    3.1 扩展浏览器端的大文件读写

    前面将了大文件在nodejs中的读取,那么在浏览器端会读取大文件会有什么问题吗?

    浏览器在本地读取大文件时,之前有类似FileSaver、StreamSaver等方案,不过在浏览器本身添加了File的规范,使得浏览器本身就默认和优化了Stream的读取。我们不需要做额外的工作,相关的工作:github.com/whatwg/fs。不过不同的版本会有兼容性的问题,我们还是可以通过FileSaver等进行兼容。

    3.2 请求静态资源大文件

    如果是在浏览器中获取静态资源大文件,一般情况下只需要通过range分配请求即可,一般的CDN加速域名,不管是阿里云还是腾讯云,对于分片请求都支持的很好,我们可以将资源通过cdn加速,然后在浏览器端直接请求cdn加速有的资源。

    分片获取cdn静态资源大文件的步骤为,首先通过head请求获取文件大小:

    const getHeaderInfo = async (url: string) => {
      const res: any = await axios.head(url + `?${Math.random()}`);
      return res?.headers;
    };
    const header = getHeaderInfo(source_url)
    const size = header['content-length']

    我们可以从header中的content-length属性中,获取文件的大小。然后进行分片和分段,最后发起range请求:

    const getRangeInfo = async (url: string, start: number, end: number) => {
        const data = await axios({
          method: 'get',
          url,
          headers: {
            range: `bytes=${start}-${end}`,
          },
          responseType: 'blob',
        });
        return data?.data;
      };

    在headers中指定 range: bytes=${start}-${end},就可以发起分片请求去获取分段资源,这里的start和end也是前闭后闭的。

    更多node相关知识,请访问:nodejs 教程

    以上就是浅析Nodejs怎么进行大文件读写的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    快捷开发Web应用及小程序:点击使用

    支持亿级表,高并发,自动生成可视化后台。

    专题推荐:node nodejs
    上一篇:一文通过实践解析nodejs中间件 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• 浅析Node处理CPU密集型任务的方法• node服务CPU过高怎么办?聊聊排查思路• Node.js的内置模块 event,利用它怎么实现发布订阅模式• 聊聊怎么使用Node的内置模块zlib进行gzip压缩• 一文通过实践解析nodejs中间件
    1/1

    PHP中文网