日常的学习笔记,包括 ES6、Promise、Node.js、Webpack、http 原理、Vue全家桶,后续可能还会继续更新 Typescript、Vue3 和 常见的面试题 等等。
在之前的文章中,我们曾讲过 fs模块 ,其中包含 readFile 和 writeFile 两个方法。其中 readFile 是读取文件, writeFile 是写入文件。
下面我们来用这两个方法来实现一个简单的 拷贝操作 。
const fs = require('fs');
const path = require('path');
fs.readFile(path.resolve(__dirname, './package.json'), (err, data) => {
if (err) return console.log('error', err);
fs.writeFile(path.resolve(__dirname, './test.json'), data, () => {
console.log('success');
})
})
这样我们的文件就被拷贝下来了,但是这种操作有一个问题。
readFile 只适合操作小文件 (如 JS、Css 和 json 文件等)。但是对于一些大文件 (如 音频、视频 等) ,虽然也可以操作,但是可能会 淹没可用内存。 原因是因为 readFile 是将文件全部读取下来之后,再进行操作的。
这样我们就需要一种新的概念 分片读写,用来操作大型文件。 而分片读写 也就是后端领域中,经常提及的 数据流 (Stream) 操作。
大文件的分片读写
如果要使用 node 实现一套文件读写,我们需要用到 fs.open、fs.read、fs.write、fs.close。
其根本的实现思路就是,将需要进行读写的文件进行 边读边写 的操作,这样我们就可以控制读写的 速率。
现在我们先创建一个目标文件 test.js(需要进行拷贝操作的文件),里面随便写一些内容,比如 1234567890。
现在我们就需要进行 读一写一(读一个数字写一个数字) 的操作,然后再产生一个名为 newTest.js 的文件。
我们先来实现一套 单个文字 的操作流程,来了解一下读写操作的原理。
let buf = Buffer.alloc(1); // 创建一个用来进行存储数据内存的 Buffer 类型
// 读取 源文件 中的数据
fs.open(path.resolve(__dirname, "test.js"), "r", function (err, rfd) { // fs 是 数字类型
// 将读取到的数据写入到 buf 中。从第0个位置开始写入buf,写入长度为1个,然后从文件的第0个位置开始进行读取。
fs.read(rfd, buf, 0, 1, 0, function (err, bytesRead) {
console.log(bytesRead); // 读取到的字节长度
// 读取到的第一文字以16进制的形式存入了 buf实例 中。
console.log(buf); // <Buffer 31>
// 打开 目标文件。
fs.open(path.resolve(__dirname, "newTest.js"), "w", function (err, wfd) {
// 数据已经被写入了
console.log(rfd, wfd); // 3 4
// 向文件中写入buf中的数据。从第0个位置开始进行读取,读取长度为1个,然后再写入到文件中。
fs.write(wfd, buf, 0, 1, 0, function (err, bytesWritten) {
console.log("success");
});
});
});
});
现在我们可以根据这种方式,然后使用 发布订阅模式,封装一套 分片读写操作。
/**
*
* @param {String} source 源文件
* @param {String} target 目标文件
* @param {Function} cb 回调函数
*/
function copy(source, target, cb) {
const BUFFER_SIZE = 3; // buffer的固定长度
const PATH_SOURCE = path.resolve(__dirname, source);
const PATH_TARGET = path.resolve(__dirname, target);
let buf = Buffer.alloc(BUFFER_SIZE); // buffer实例
let rOffset = 0; // 读取偏移量
let wOffset = 0; // 写入偏移量
fs.open(PATH_SOURCE, "r", function (err, rfd) {
if (err) return cb(err);
fs.open(PATH_TARGET, "w", function (err, wfd) {
if (err) return cb(err);
function next() {
fs.read(rfd, buf, 0, BUFFER_SIZE, rOffset, function (err, bytesRead) {
if (err) return cb(err);
// 如果全部读取完毕,则关闭当前 读写操作
if (bytesRead == 0) {
let index = 0;
let done = () => {
if (++index == 2) { cb(); }
};
fs.close(wfd, done);
fs.close(rfd, done);
return;
}
fs.write(wfd, buf, 0, bytesRead, wOffset, function (err, bytesWritten) {
if (err) return cb(err);
// 读取成功,并更新偏移量
rOffset += bytesRead;
wOffset += bytesWritten;
next();
}
);
});
}
next();
});
});
};
// 执行封装好的方法
copy("./test.js", "./newTest.js", function (err) {
if (err) return console.log(err);
console.log("copy success");
});
这样我们就完成了一套 简单的 分片读写操作。
但是这种方式会出现 回调地狱 的问题,代码看起来非常难以阅读和维护。
这时我们就需要通过 数据流 (Stream) 来进行读写操作。
本篇文章由 莫小尚 创作,文章中如有任何问题和纰漏,欢迎您的指正与交流。
您也可以关注我的 个人站点、博客园 和 掘金,我会在文章产出后同步上传到这些平台上。
最后感谢您的支持!
浙公网安备 33010602011771号