作用
nodeJS中的流最大的作用是:读取大文件的过程中,不会一次性的读入到内存中。每次只会读取数据源的一个数据块。然后后续过程中可以立即处理该数据块(数据处理完成后会进入垃圾回收机制)。而不用等待所有的数据。
这么做的原因:因为浏览器的运行内存一般情况下只有1~3g,倘若我们将一个4g的视频放到浏览器播放,此时浏览器的运行内存便无法去解析了,所以就需要文件流技术了。每次只读取一部分视频,然后运行完成后再销毁,再读取另一段视频,这样便可以完美播放视频了
创建文件流
读取
创建文件流需要利用到require
中的fs
模块
const fs = require("fs");
const path = require("path");
let filePath=path.resolve(__dirname,"./experiment.txt");//合并路径
fs.createReadStream(filePath,{
})
这样就创建了一个文件流了,通过fs
模块的createReadStream
函数来操作文件。
fs.createReadStream(path,[options])
参数一path :获取文件的路径
参数二options:可选配置项(用于定义如何操作文件)
配置项常用属性
encoding:编码方式
start:起始字节
end:结束字节
highWaterMark:每次读取的数量(也就是每次读几个字节)
autoClose:自动关闭
这样创建之后只是配置了文件流的方式,如果想操作的文件的输出方式的话,需要给它搭载事件
搭载文件流事件
const fs = require("fs");
const path = require("path");
let filePath=path.resolve(__dirname,"./experiment.txt");
let rs = fs.createReadStream(filePath,{
encoding:"utf-8",
highWaterMark:3,
autoClose:true
})
先建立一个变量来接收返回的文件对象。通过rs.on()
来给它搭载事件
rs.on()
常用搭载事件:
open: 打开的时候运行的事件
close :关闭的时候运行的事件
error: 发生错误的时候出发的事件
data: 获取到一部分数据后触发
end:所有数据读取完后触发
pause(): 暂停
resume(): 继续
举几个例子:
open()事件
const fs = require("fs");
const path = require("path");
let filePath=path.resolve(__dirname,"./experiment.txt");
let rs = fs.createReadStream(filePath,{
})
rs.on("open",()=>{
console.log("打开文件流");
})
控制台打印
error()事件
const fs = require("fs");
const path = require("path");
let filePath=path.resolve(__dirname,"./asd/experiment.txt");//路径设置错误
let rs = fs.createReadStream(filePath,{
})
rs.on("open",()=>{
console.log("打开文件流");
})
rs.on("error", () => {
console.log("文件出错");
})
控制台打印
代码中的路径设置错误时,此时不会触发open事件,而是触发error事件
重点:data事件(输出文件内容)
创建文件experiment.txt。它的内容:我是测试文本
注意data事件需要搭配chunk参数,以下data方法的回调函数是箭头函数的缩写
const fs = require("fs");
const path = require("path");
let filePath=path.resolve(__dirname,"./experiment.txt");
let rs = fs.createReadStream(filePath,{
encoding:"utf-8",
highWaterMark:3,
autoClose:true
})
rs.on("data",chunk=>{
console.log(chunk);
})
控制台打印
注意:现在一个汉字占三个字节
倘若修改代码:将highWaterMark
每次输出的字符修改4个。
let rs = fs.createReadStream(filePath,{
encoding:"utf-8",
highWaterMark:4,
autoClose:true
})
可以看到第三行是两个字,造成这样的原因是因为,highWaterMark
设置的是每次输出4个字节,一个汉字占3个字节,第一行只能输出一个汉字,并空出一个字节,第二行补上第一行空出的一个字节,那么此时第二行便空了两个字节,第三行便是2个字节+4个字节,正好输出两个汉字
多加些汉字:
resume() 和 pause()
pause():
const fs = require("fs");
const path = require("path");
let filePath=path.resolve(__dirname,"./experiment.txt");
let rs = fs.createReadStream(filePath,{
encoding:"utf-8",
highWaterMark:3,
autoClose:true
})
let amount=0;
rs.on("data",chunk=>{
if(amount==3){
rs.pause();
}
console.log(chunk);
amount++;
})
可以看到在amount=3时,停止输出
resume():
当文件被pause()
方法暂停时,可以通过resume()
恢复
const fs = require("fs");
const path = require("path");
let filePath=path.resolve(__dirname,"./experiment.txt");
let rs = fs.createReadStream(filePath,{
encoding:"utf-8",
highWaterMark:3,
autoClose:true
})
let amount=0;
rs.on("data",chunk=>{
console.log(chunk);
if(amount==3){
rs.pause();
}
amount++;
})
rs.on('pause',()=>{
rs.resume();
console.log("恢复了")
})
写入
关键字:fs.createWriteStream(path,[options])
参数一:文件路径
参数二:可选配置项
flags <string> 参见文件系统 flag 的支持。 默认值: 'w'。(a为在文档的原基础上添加)
encoding <string> 默认值: 'utf8'。
fd <integer> 默认值: null。
mode <integer> 默认值: 0o666。
autoClose <boolean> 默认值: true。
emitClose <boolean> 默认值: false。
start <integer>
fs <Object> | <null> 默认值: null。
highWaterMark 每次写入多少字节
创建一个可写流
const fs=require("fs");
const path=require("path");
let filePath=path.resolve(__dirname,"experiment.txt");
let ws=fs.createWriteStream(filePath,{
});
ws.write("12312");
成功写入
以上方法是直接将数据写入,只适合小文件写入方式,遇见大文件时我们以流的方式写入更好
以流的方式写入
以流的方式写入时,需要理解write()
和drain()
还有就是配置项中的highWaterMark
是怎么和他们配合的。
write()与highWaterMark的配合使用
const fs=require("fs");
const path=require("path");
let filePath=path.resolve(__dirname,"experiment.txt");
let ws=fs.createWriteStream(filePath,{
highWaterMark:3
});
let i=0;
function flowWrite(){
let flag=true;
while(i<9 && flag){
flag=ws.write("a");
console.log(flag);
i++
}
}
flowWrite();
控制台打印
文本内容:
解析:write()
在写入数据后,会给一个返回值:根据highWaterMark
每次能通过的字节数,如果在写入一个数据后,管道内还有位子,那么返回一个true,否则返回false。
所以上述代码中:
写入第一个a时,highWaterMark
规定的通道还有两个字节位子,后面的数据可以直接传输不需要排队,返回true。
写入第二个a时,通道内还有一个字节位子,后面的数据可以直接传输不需要排队,返回true。
写入第三个a时,写入的通道被填满,后面的数据将进入写入队列,返回false。
write()、highWaterMark、drain联合使用,便时流写入了
const fs=require("fs");
const path=require("path");
let filePath=path.resolve(__dirname,"experiment.txt");
let ws=fs.createWriteStream(filePath,{
highWaterMark:3
});
let i=0;
function flowWrite(){
let flag=true;
while(i<10 && flag){
flag=ws.write("a");
console.log(flag);
i++
}
}
ws.on("drain",()=>{
console.log("drain执行一次");
flowWrite();
})
flowWrite();
控制台打印
文本内容
可以看到每次false后drain()
便执行了一次。由此可以知道drain()
可以监听highWaterMark
限定的管道通入的宽度。管道被填满后会触发它。
这样便实现了文档流的写入。
另外还有个end()方法
代码执行过程中遇见此方法时,会直接结束写入
const fs=require("fs");
const path=require("path");
let filePath=path.resolve(__dirname,"experiment.txt");
let ws=fs.createWriteStream(filePath,{
highWaterMark:3
});
let i=0;
function flowWrite(){
let flag=true;
while(i<10 && flag){
flag=ws.write("a");
console.log(flag);
i++
}
}
ws.on("drain",()=>{
console.log("drain执行一次");
flowWrite();
})
flowWrite();
ws.end(()=>{
console.log("杀死进程");
});
控制台
文本内容