文件流
一、文件流
1.什么是文件流?
1.概念
流是指数据的流动,数据从一个地方缓缓的流动到另一个地方。
2.流是有方向的
- 可读流: Readable
数据从源头流向内存 - 可写流: Writable
数据从内存流向源头 - 双工流:Duplex
数据既可从源头流向内存,又可从内存流向源头
2.为什么要流
1.其他介质和内存的数据规模不一致
磁盘空间大,价格相对低;内存容量小,价格相对高。
2.其他介质和内存的数据处理能力不一致
磁盘读存数据相对较慢,内存读取数据相对较快。
3.文件流
1.文件流的创建
1.可读流
fs.createReadStream(path[, options])
创建一个文件可读流,用于读取文件内容
path:读取的文件路径 options:可选配置 encoding:编码方式 start:起始字节 end:结束字节 highWaterMark:每次读取数量 如果encoding有值,该数量表示一个字符数 如果encoding为null,该数量表示字节数
返回:Readable的子类ReadStream 事件:rs.on(事件名, 处理函数) open 文件打开事件 文件被打开后触发 error 发生错误时触发 close 文件被关闭后触发 可通过rs.close手动关闭 或文件读取完成后自动关闭 autoClose配置项默认为true data 读取到一部分数据后触发 注册data事件后,才会真正开始读取 每次读取highWaterMark指定的数量 回调函数中会附带读取到的数据 若指定了编码,则读取到的数据会自动按照编码转换为字符串 若没有指定编码,读取到的数据是Buffer end 所有数据读取完毕后触发 rs.pause() 暂停读取, 会触发pause事件 rs.resume() 恢复读取,会触发resume事件
const fs = require("fs");
const path = require("path");
const filename = path.resolve(__dirname,"./test.txt");
const rs = fs.createReadStream(filename,{
encoding: "utf-8",
highWaterMark: 1
});
rs.on("open",()=>{
console.log("文件被打开了。");
});
rs.on("error",()=>{
console.log("文件读取错误。");//例:读取了一个不存在的文件
});
rs.on("close",()=>{
console.log("文件关闭了。");
});
rs.close();//这里使用手动关闭,或者可以等文件读取完毕后自动关闭。
rs.on("data",chunk => {
console.log("读取到的数据:",chunk);
});
rs.on("end",() => {
console.log("数据读取完毕。");
});
rs.on("data",chunk => {
console.log("读取到的数据:",chunk);
rs.pause();
});
rs.on("pause", () => {
console.log("暂停了");
setTimeout(() => {
rs.resume();
}, 1000);
});
rs.on("resume", () => {
console.log("恢复了");
});
2.可写流
fs.createWriteStream(path[, options])
创建一个写入流
path:写入的文件路径 options flags:操作文件的方式 w:覆盖 a:追加 其他 encoding:编码方式 start:起始字节 highWaterMark:每次最多写入的字节数 返回:Writable的字类WriteStream ws.on(事件名, 处理函数) open error close ws.write(data) 写入一组数据 data可以是字符串或Buffer 返回一个boolean值 true:写入通道没有被填满,接下来的数据可以直接写入,无须排队 false:写入通道目前已被填满,接下来的数据将进入写入队列 要特别注意背压问题,因为写入队列是内存中的数据,是有限的 当写入队列清空时,会触发drain事件 ws.end([data]) 结束写入,将自动关闭文件 是否自动关闭取决于autoClose配置 默认为true data是可选的,表示关闭前的最后一次写入 rs.pipe(ws) 将可读流连接到可写流 返回参数的值 该方法可解决背压问题
这里的一些参数就不多说了,与前面可读相似。
以及一些绑定事件。
const fs = require('fs');
const path = require('path');
const filename = path.resolve(__dirname,"./abc.txt");
const ws = fs.createWriteStream(filename,{
encoding: 'utf-8',
highWaterMark: 3
});
const flag = ws.write('1');
console.log(flag);
ws.write(data),写入一组数据,data可以是字符串或Buffer
返回一个Boolean值:
true:写入通道没有被填满,接下来的数据可以直接写入,无须排队
如上面代码,highWaterMark设置三个字节,传入的1占两个字节,所以表示通道未被占满,传下一个数据时会直接写入,无需排队。
const fs = require('fs');
const path = require('path');
const filename = path.resolve(__dirname,"./abc.txt");
const ws = fs.createWriteStream(filename,{
encoding: 'utf-8',
highWaterMark: 4
});
const flag = ws.write('雷');
console.log(flag);
const fs = require('fs');
const path = require('path');
const filename = path.resolve(__dirname,"./abc.txt");
const ws = fs.createWriteStream(filename,{
encoding: 'utf-8',
highWaterMark: 4
});
const flag = ws.write('𠮷');
console.log(flag);
在这里就可以看出,对于中文文字,有些是3个字节,有些是两个字节。
所以当通道的容量小于传入的数据大小,则会将后面的数据放在写入队列。
由于内存的读写速度高于磁盘的读写速度,一段时间后写入队列会不堪重负。(就是背压问题)
const fs = require('fs');
const path = require('path');
const filename = path.resolve(__dirname,"./abc.txt");
const ws = fs.createWriteStream(filename,{
encoding: 'utf-8',
flags: 'w',
highWaterMark: 4
});
let i = 0;
function write(){
let flag = true;
while(i < 1024*1024*1 && flag){
flag = ws.write("a");
i ++;
}
}
write();
如上方法,一旦通道饱和,即不再写入,那么如何做到将1M的内容完成呢?
这里node提供了一个事件:当写入队列清空时,会触发drain事件
function write(){
let flag = true;
while(i < 1024*1024*1 && flag){
flag = ws.write("a");
i ++;
}
}
write();
ws.on("drain",()=>{
write();
});
最后耗时20s将1M空间写满a。
2.利用文件流复制文件
方法一
const fs = require("fs");
const path = require("path");
async function method1() {
const from = path.resolve(__dirname, "./abc.txt");
const to = path.resolve(__dirname, "./abc2.txt");
console.time("方式1");
const content = await fs.promises.readFile(from);
await fs.promises.writeFile(to, content);
console.timeEnd("方式1");
console.log("复制完成");
}
method1()
方法二
async function method2(){
const from = path.resolve(__dirname, "./abc.txt");
const to = path.resolve(__dirname, "./abc3.txt");
console.time('方式二');
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();
});
rs.on("close",()=>{
ws.end();//完毕写入流
console.timeEnd("方式二");
console.log("复制完成");
})
}
method2();
3.rs.pipe(ws)
将可读流连接到可写流,返回参数的值,该方法可解决背压问题。
const fs = require("fs");
const path = require("path");
async function method2() {
const from = path.resolve(__dirname, "./abc.txt");
const to = path.resolve(__dirname, "./abc4.txt");
console.time("方式2");
const rs = fs.createReadStream(from);
const ws = fs.createWriteStream(to);
rs.pipe(ws);
rs.on("close", () => {
console.timeEnd("方式2");
});
}
method2();