nodeJS——文件读取之如何理解“流”?

1,542 阅读3分钟

对于前端童靴来说,很少接触文件读读取和存储。对于后端童鞋来说,文件读写就像吃饭喝水一样了。通过对nodeJS的学习,前端也能了解文件读写的大致流程。

文件读取的大致流程

计算机储存文件是放在储存(硬盘)中的,如果我们要对文件进行操作(查看、修改等),计算机会将文件内容读取到内存中(内存就是你手机上6G、12G那个玩意儿),操作完成后再写入储存(大致是这样,细节不用纠结)。

image.png

nodeJS读取文件

nodeJS中读取文件,可使用fs内置模块中的方法
nodejs.org/dist/latest…

const fs = require('fs');
async function test() {
    // 读取文件
    const content = await fs.promises.readFile(fromSrc, 'utf-8');
    // 写入文件
    await fs.promises.writeFile(toSrc, content);
    // 如此就完成了一次文件的读写(复制)
}
test();

IO流

  读取小文件使用fs模块非常方便,但是如果文件很大,就会占用更多的内存,内存对计算机来说是非常宝贵的,内存不足会导致卡顿,甚至无法运行其他程序。
  要解决这个问题很简单,只需要一点一点读取文件即可,每次读取文件的一部分,对内存的占用就小了 image.png   如果将读取的所有片段在内存中拼接成完整内容,然后再进行写入操作,那和全读全写一样了,没有意义。
  所以,我们可以读一点,写一点。 image.png 文件片段读取输入的过程就像水流一样,这既是文件流(stream)。IO流“I”指input,“O”指output,既“输入输出流”。

nodeJS中创建文件流

创建可读流:fs.createReadStream(path[, options])
创建可写流:fs.createWriteStream(path[, options])

可读流

const path = require('path');
const fs = require('fs');

const filename = path.resolve(__dirname, './myFiles/test.txt')
const rs = fs.createReadStream(filename, {
    encoding: 'utf-8',
    highWaterMark: 1,
    autoClose: true,
});

// 注册'data'事件后流才会开始读取数据,chunk为每次读取到的一点数据
rs.on('data', (chunk) => {
    console.log('读到了一点数据', chunk);
    rs.pause(); // 暂停
});

rs.on('pause', () => {
    setTimeout(() => {
        rs.resume();// 再开始
    }, 100);
});

rs.on('open', () => {
    console.log('文件打开了');
});

rs.on('end', () => {
    console.log('文件读取完毕');
});

rs.on('close', () => {
    console.log('文件关闭');
});

rs.on("error", () => {
    console.log("出错了!!");
});

// 详细用法请查阅api,不多做介绍

可写流

const path = require('path');
const fs = require('fs');

const filename = path.resolve(__dirname, './myFiles/test2.txt');
const wr = fs.createWriteStream(filename, {
    flags: 'w',
    encoding: 'utf-8',
    highWaterMark: 16 * 1024,
    autoClose: true,
});

let i = 0;

function write() {
    let flag = true;
    while (flag && i < 1024 * 1024 * 3) {
        // 解决背压问题
        // 如果管道满了,不能再加入数据了wr.write('b')返回false,反之
        flag = wr.write('b');
        i++;
    }
}

write();

// 解决背压问题, 当管道清空时会触发此事件
wr.on('drain', () => {
    write();
});

// 详细用法请查阅api,不多做介绍

背压问题

磁盘写入数据的速度是远低于内存的,我们想象内存和磁盘之间有一个“管道”,“管道”中是“流”,内存的数据流入管道是非常快的,当管道塞满时,内存中就会产生数据背压,数据积压在内存中,占用资源。 image.png 解决方法:上文“可写流”注释部分已标明

读写合并

const fs = require('fs');
const path = require('path');

const from = path.resolve(__dirname, './myFiles/test2.txt');
const to = path.resolve(__dirname, './myFiles/test3.txt');

function case1() {
    const rs = fs.createReadStream(from);
    const wr = fs.createWriteStream(to);

    rs.on('data', (chunk) => { // 一边读
        let flag = wr.write(chunk); // 一边写
        if (!flag) {
            rs.pause(); // 暂停读取数据
        }
    });

    wr.on('drain', () => { // 通道清空
        rs.resume(); // 重新开始读取数据
    });
}

function case2() {
    const rs = fs.createReadStream(from);
    const wr = fs.createWriteStream(to);

    rs.pipe(wr);
}
// case1和case2效果一样

// case1();
case2();