文件IO
I/O:input output
- 对外部设备的输入输出
- 外部设备:磁盘、网卡、显卡、打印机、其他…
- I/O的速度往往远低于内存和CPU的交互速度!
<<现代操作系统>>,英语水平允许的话,建议看原版。中文翻译的。。
fs模块—内置模块
具体用法去官网查api,fs中常用api(下述都是方法名)如下:
- fs.promises.xxx:node12(ES6之后)之后出现,它会返回一个Promise
- 后续我们都用这个promises下面的api读写文件等操作 ```javascript const fs = require(“fs”); const path = require(“path”);
const filename = path.resolve(__dirname, “./myfiles/1.txt”); // fs.readFile( // filename, // { // encoding: “utf-8”, //只有encoding配置时,可以直接把这个配置作为readFile的第二个参数 // }, // (err, content) => { // console.log(content); // } // );
// Sync函数是同步的,会导致JS运行阻塞,极其影响性能 // 通常,在程序启动时运行有限的次数即可
// const content = fs.readFileSync(filename, “utf-8”); // console.log(content);
async function test() { const content = await fs.promises.readFile(filename, “utf-8”); console.log(content); }
test();
- fs.readFile:读取一个文件,编码格式默认是**buffer**- fs.writeFile:向文件写入数据,编码格式默认是**utf-8**。**若目录不存在则会报错,文件不存在会新建**- **fs.unlink: 删除文件,传一个路径就行**- fs.copyFile:复制文件内容到另外一个文件- fs.stat:获取**文件或者目录**的状态信息- size:占用的字节数。目录的size为0,在OS中,目录其实也是一种特殊的空文件,记录一个指针- birthTime:创建文件的时间戳- atime: 上次访问文件的时间- mtime:上次文件内容被修改时间- ctime:上次文件状态被修改时间- isDirectory():是否是目录- isFile():是文件- 其他很多属性,查api文档- fs.readdir:获取目录中所有的 **直接** 文件和子目录的名字,由所有字符串组成一个数组- fs.mkdir:创建目录- fs.exists:判断文件或目录是否存在,官方把它废弃了。说使用,fs.stat中的那俩属性<a name="J5DNO"></a>## 练习:读取一个目录中的所有子目录和文件每个目录或文件都是一个对象,具有一些属性、方法。还有个filename:文件/目录的绝对路径<br />```javascriptconst fs = require("fs");const path = require("path");class File {constructor(filename, name, ext, isFile, size, createTime, updateTime) {this.filename = filename;this.name = name;this.ext = ext;this.isFile = isFile;this.size = size;this.createTime = createTime;this.updateTime = updateTime;}async getContent(isBuffer = false) {if (this.isFile) {if (isBuffer) {return await fs.promises.readFile(this.filename);} else {return await fs.promises.readFile(this.filename, "utf-8");}}return null;}async getChildren() {if (this.isFile) {//文件不可能有子文件return [];}let children = await fs.promises.readdir(this.filename);children = children.map(name => {const result = path.resolve(this.filename, name);return File.getFile(result);});return Promise.all(children);}static async getFile(filename) {const stat = await fs.promises.stat(filename);const name = path.basename(filename);const ext = path.extname(filename);const isFile = stat.isFile();const size = stat.size;const createTime = new Date(stat.birthtime);const updateTime = new Date(stat.mtime);return new File(filename, name, ext, isFile, size, createTime, updateTime);}}async function readDir(dirname) {const file = await File.getFile(dirname);return await file.getChildren();}async function test() {const dirname = path.resolve(__dirname, "./myfiles");const result = await readDir(dirname);const datas = await result[0].getChildren();console.log(datas);}test();
文件流
什么是流
- 流是指数据的流动,数据从一个地方缓缓的、一点点的流动到另外一个地方
流是有方向的
其他介质和内存的数据规模不一致
- 其他介质和内存的数据处理能力不一致
在node中有个流模块—stream
const { Readable, Writable } = require("stream");//其他类型的流,都应该继承自这俩个
文件流
什么是文件流?—— 内存数据和磁盘文件数据之间的流动
文件流的创建
文件可读流
fs.createReadStream(path[, options])
- 含义:创建一个文件可读流,用于读取文件内容
- path:要读取的文件路径
- options:可选配置
- autoClose: 默认为true,文件被读完后就会自动关闭
- encoding: 编码方式
- start:起始字节
- end:结束字节
- highWaterMark: 每次读取的数量,默认是64KB,但它的单位不一定是kb,还要看encoding的值
- encoding有值,为utf-8(它认为汉字是三个字节)时,highWaterMark为1就代表一个汉字。
- encoding为null,highWaterMark数量就代表字节数
返回值:Readable的子类ReadStream,简称rs
- 事件:rs.on(‘事件名’, 回调/处理函数)
事件名有:
- open:文件被正常打开后触发回调
- error:打开文件失败
- close:文件被关闭后就会触发回调
- 可通过rs.close();手动关闭,然后就会触发回调
- 文件读完了也会自动关闭,然后触发回调
data:读取到一部分数据后就会触发回调,所以会反复触发
- 只有注册了这个data事件,才会真正读取数据!否则不读!
- 每次读取highWaterMark指定的数量
- 回调函数中会附带每次读到的数据
rs.on("data", chunk => {console.log("读到了一部分数据:", chunk);rs.pause(); //暂停rs.resume();//恢复});
end:全部数据读取完毕后触发回调,先触发end,再触发close
- pause:读取数据被暂停时触发回调
- re.pause():手动暂停读取,会触发pause事件
- resume:重新恢复读取数据时触发回调
- rs.resume():手动恢复读取,会触发resume事件
文件可写流
fs.createWriteStream(path[, options])
- 含义:创建一个文件可写流,用于向文件写入数据
- path:要写入数据的文件路径
- options:可选配置
- flags:操作文件的方式
- w:直接覆盖掉原有内容,a:追加内容
- autoClose: 默认为true,数据被写完后就会自动关闭
- encoding: 编码方式,默认为utf-8
- start:起始字节
- highWaterMark: 默认16KB。这里代表的就是每次最多写入的字节数!这点与可读流不一样
- 它的大小就是下述写入通道的长度。单位是字节
- flags:操作文件的方式
- 返回值:Writable的子类WriteStream,简称ws
- ws.on(“事件名”, 处理函数)。用法,基本同可读流
- drain事件:写入通道清空时触发回调
- ws.write(data); 写入一组数据
- data可以是字符串或Buffer
- 返回一个boolean值。还是因为内存比硬盘的处理速度快多了
- true:写入通道没有被填满,接下来的数据可以直接写入,无须排队
- false:写入通道目前已被填满,接下来的数据将进入写入队列

3. **背压问题**:写入队列中积压了过多的数据,导致占用内存很高1. 当返回false时我们就不写write数据,这样就不会产生背压问题,但是时间依旧节约不了1. 利用rs.pipe(ws) 或者 我们自己实现的类似pipe4. 写入通道排满了,然后等它清空时(也就是都通道里都写到磁盘里后)!就会触发drain事件!
- ws.end([data]); 手动结束写入,然后会自动关闭文件(还取决于autoClose)
- data是可选的,表示关闭前最后一次写入的数据 ```javascript const fs = require(“fs”); const path = require(“path”);
const filename = path.resolve(__dirname, “./temp/abc.txt”);
const ws = fs.createWriteStream(filename, { encoding: “utf-8”, highWaterMark: 16 * 1024 });
let i = 0; //一直写,直到到达上限,或无法再直接写入 function write() { let flag = true; while (i < 1024 1024 10 && flag) { flag = ws.write(“a”); //写入a,得到下一次还能不能直接写 i++; } }
write();
ws.on(“drain”, () => { write(); });
<a name="wxT81"></a>## 实现:将文件A内容复制到另外一个文件```javascriptconst fs = require("fs");const path = require("path");//方式1, 利用现有api,一次性读写, 占用内存很大async function method1() {const from = path.resolve(__dirname, "./temp/abc.txt");const to = path.resolve(__dirname, "./temp/abc2.txt");console.time("方式1");const content = await fs.promises.readFile(from);await fs.promises.writeFile(to, content);console.timeEnd("方式1");console.log("复制完成");}//方式2, 读一点写一点,停止、恢复, 其实就是rs.pipe(ws)的原理async function method2() {const from = path.resolve(__dirname, "./temp/abc.txt");const to = path.resolve(__dirname, "./temp/abc2.txt");console.time("方式2");const rs = fs.createReadStream(from);const ws = fs.createWriteStream(to);rs.on("data", chunk => {//读到一部分数据const flag = ws.write(chunk);if (!flag) {//表示下一次写入,会造成背压rs.pause(); //暂停读取}});ws.on("drain", () => {//可以继续写了rs.resume();});//上述两个on极其内容可以替换为一句: rs.pipe(ws);rs.on("close", () => {//写完了ws.end(); //完毕写入流console.timeEnd("方式2");console.log("复制完成");});}method2();
rs.pipe(ws)
- 将可读流连接到可写流
- 返回值就是ws
- 该方法可解决背压问题
