目录
一、Node的数据流(Stream)
数据读写可以看作是事件模式(Event)的特例,不断发送的数据块好比一个个的事件。读数据是read事件,写数据是write事件,而数据块是事件附带的信息。Node 为这类情况提供了一个特殊接口Stream。
1、处理缓存的方式
“数据流”(stream)是处理系统缓存的一种方式。操作系统采用数据块(chunk)的方式读取数据,每收到一次数据,就存入缓存。Node应用程序有两种缓存的处理方式:
(1)一次性全部读取,然后再进行处理。缺点是大文件处理非常耗时,优点是过程直观
(2)读取一块处理一块。优点是提高程序的性能
2、四种类型的流
流是可以从一个源读取或写入数据到连续的目标对象。在Node.js,有四种类型的数据流。
- Readable - 其是用于读操作。
- Writable - 用在写操作。
- Duplex - 其可以用于读取和写入操作。
- Transform - 输出基于输入的地方进行计算的一种双相流。
每种类型的流是一个EventEmitter实例,并抛出的不同的事件。
例如,一些常用的事件是:
- data - 当有数据可读取此事件。
- end - 当没有更多的数据读取此事件被触发。
- error - 当有任何错误或接收数据写入此事件。
- finish - 当所有数据已刷新到底层系统触发此事件
1、从流中读取:fs模块的createReadStream方法,就可以创建一个读取数据的数据流。
2、写入流:fs模块的createWriteStream方法,就可以创建一个写数据的数据流。
const fs = require('fs')
let str_data = '';
//创建读数据的流
let readerStream = fs.createReadStream('./input.txt')
//设置流的编码格式
readerStream.setEncoding('utf8')
//给流绑定事件
readerStream.on('data',function (chunk){
str_data += chunk
})
readerStream.on('end',function (){
console.log('读取的数据是:',str_data)
})
readerStream.on('error',function (err){
console.log(err.stack)
})
console.log('---------end----------')
3、管道流:管道是供一个流的输出作为输入到另一个流的机制。它通常被用于从一个流中获取数据,并通过该流输出到另一个流。
例如:一个管道从一个文件中读取和写入到另一个文件
const fs = require('fs')
//创建一个读数据的流
let readerStream = fs.createReadStream('./test.txt')
//创建一个写数据的流
let writeStream = fs.createWriteStream('./input.txt')
//创建管道流
readerStream.pipe(writeStream)
console.log('-----end----')
4、链式流:链式是一个机制,一个流的输出连接到另一个流,并创建一个链多流操作。它通常用于管道的操作。
例如:使用管道和链接先压缩文件,然后解压缩
//压缩文件
var fs = require("fs");
var zlib = require('zlib');
// 将input.txt文件压缩为input.txt.zip
fs.createReadStream('./input.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('./input.zip'));
console.log("File Compressed.");
//解压文件
var fs = require("fs");
var zlib = require('zlib');
// 解压文件input.txt.zip到demo.txt
fs.createReadStream('./input.txt.zip')
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream('./demo.txt'));
console.log("File Decompressed.");
二、Node的事件处理
在访问任何网页的时候,会伴随着许多的事件,例如点击菜单,移动鼠标等等。那么node.js是如何处理的?
(1)Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。
(2)由于nodejs是单线程运行的,所以nodejs需要借助事件轮询,不断去查询事件队列中的事件消息,然后执行该事件对应的回调函数,有点类似windows的消息映射机制。
这个模型非常高效、可扩展性非常强,因为 webserver 一直接受请求而不等待任何读写操作。(这也称之为非阻塞式IO或者事件驱动IO)
在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。
整个事件驱动的流程就是这么实现的,非常简洁。有点类似于观察者模式,事件相当于一个主题(Subject),而所有注册到这个事件上的处理函数相当于观察者(Observer)。
在Node.js的事件机制中主要有三类角色:事件(Event)、事件发射器(EventEmitter)、事件监听器(EventListener)
Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件。例如:
// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件。例如:
var eventEmitter = new events.EventEmitter();
// 绑定事件及事件的处理程序
eventEmitter.on('eventName', eventHandler);
// 触发事件
eventEmitter.emit('eventName');
1、EventEmitter类--事件发射器
在Node.js的用于实现各种事件处理的events模块中,定义了一个EventEmitter类。所有可能触发事件的对象都是一个集成了EventEmitter类的子类的实例对象,在Node.js中,为EventEmitter类定义了许多方法,所有与对象的事件处理函数的绑定及解除相关的处理均依靠这些方法的调用来执行。
2、EventEmitter类的各种方法(event:代表事件名,listener:代表事件处理函数)
方法名与参数 | 描述 |
addListener(event,listener) | 事件监听函数 |
on(event, listener) | 对指定对象绑定事件处理函数(addListener方法的别名) |
once(event, listener) | 对指定对象指定只执行一次的事件处理函数 |
removeListener(event, listener) | 删除事件 |
setMaxListeners(n) | 指定对象处理函数的最大数量,n为正数值,代表最大的可指定事件处理函数的数量 |
listeners(event) | 获取指定对象的所有事件处理函数 |
emit(event, [arg1], [arg2], [...]) | 手动触发指定事件 |
(1)EventEmitter类的on方法:
例1:当服务器接收到客户端请求时,在控制台窗口中输出客户端请求的目标地址(URL地址),并使用响应对象的end方法立即结束响应
const http = require('http')
//创建服务器对象
let sever = http.createServer();
//给服务器绑定事件
sever.on('request',function (req,res){
//输出请求对象的地址
console.log('请求地址:',req.url)
//服务器关闭和客户端的连接,并发送响应信息
res.end('Hello,i am sever')
})
//服务器启动监听
sever.listen(8089,'127.0.0.1')
例2:也可以通过多个on方法的执行来对同一个事件绑定多个事件处理函数
var http = require("http");
var server = http.createServer();
server.on('request', function(req, res){
console.log('接收到客户端请求')
})
server.on("request", function(req, res){
console.log('处理客户端请求')
console.log(req.url);
res.end();
})
server.on('request', function(req, res){
console.log('发送响应完毕')
})
server.listen(8089, "127.0.0.1");
(2)EventEmitter类的once方法
once方法与on方法类似,作用均为对指定事件绑定事件处理函数,区别在于,当事件处理函数执行一次后立即被结束,即该事件处理函数只会被执行一次。once方法所用参数与on方法所用参数相同。
var http = require("http");
var server = http.createServer();
server.once('request', function(req, res){
console.log('接收到客户端请求')
})
server.on("request", function(req, res){
console.log('处理客户端请求')
console.log(req.url);
res.end();
})
server.once('request', function(req, res){
console.log('发送响应完毕')
})
server.listen(8089, "127.0.0.1");
(3)使用removeListener方法取消事件处理函数
var http = require("http");
var server = http.createServer();
var testFunction = function (req,res) {
console.log('发送响应完毕')
}
server.on('request', function(req, res){
console.log('接收到客户端请求')
})
server.on("request", function(req, res){
console.log('处理客户端请求')
console.log(req.url);
res.end();
})
server.on('request', testFunction)
//删除
server.removeListener('request', testFunction)
server.listen(8089, "127.0.0.1");
(4)自定义事件并将其触发
var http = require("http");
var server = http.createServer();
server.on("request", function(req, res){
console.log(req.url);
});
//自定义事件
server.on("customEvent", function(arg1, arg2, arg3){
console.log("自定义事件被触发");
console.log(arg1);
console.log(arg2);
console.log(arg3);
});
//触发自定义事件
server.emit('customEvent', '自定义参数1', '自定义参数2', '自定义参数3')
server.listen(8089, "127.0.0.1");
3、错误捕获:事件处理过程中抛出的错误,可以使用try...catch捕获。
var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter();
emitter.on('beep', function () {
console.log('beep');
});
emitter.on('beep', function () {
throw Error('oops!');
});
emitter.on('beep', function () {
console.log('beep again');
});
console.log('before emit');
try {
emitter.emit('beep');
} catch(err) {
console.error('caught while emitting:', err.message);
}
console.log('after emit');
beep一共绑定了三个监听函数。其中,第二个监听函数会抛出错误。执行上面的代码,会得到下面的结果。
三、通过Node的readline模块实现终端的输入
readline是Node.js里实现标准输入输出的封装好的模块,通过这个模块我们可以以逐行的方式读取数据流。使用require("readline")可以引用模块。
1、如何使用readline:
(1)引入:require('readline')
(2)创建readline对象(接口)
(3)调用接口的相关方法
(4)监听与处理readline事件
// 引入readline模块
const readline = require("readline");
// 创建readline接口实例
let r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
//调用接口方法
r1.question("你叫什么名字\t", function (answer) {
console.log("我的名字是:", answer);
// 不加close,则不会结束
r1.close();
})
//close事件监听
r1.on("close", function () {
// 结束程序
process.exit(0);
})
2、readline的使用
第一步:createInterface创建了一个接口实例
第二步:调用相关方法,如question方法输入
第三步:监听readline的close事件
- 在createInterface里,需要传入标准输入输出作为数据的输入输出流
- 在question方法的回调函数里,可以获取到用户的输入并进行处理,同时进行了close操作来结束程序,否则程序不会结束
- 在close事件的监听里,执行了process.exit(0)来使程序退出的操作,因为readline模块只要一开始获取用户输入就不会结束,必须使用这种直接的方式来结束程序
(1)输入输出示例
// 引入readline模块
let readline = require("readline");
// 创建接口实例
let r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
// 调用接口方法
r1.on("line", function (line) {
switch (line.trim()) {
case "copy":
console.log("复制");
break;
case "hello":
r1.write("Hello ");
console.log("World!");
break;
case "close":
r1.close();
break;
default:
console.log("没有找到命令!");
break;
}
})
// close事件监听
r1.on("close", function () {
console.log("再见");
process.exit(0);
})
(2)模拟命令行的输入输出
// 引入readline模块
let readline = require("readline");
// 创建接口实例
let r1 = readline.createInterface({
input: process.stdin,
output: process.stdout
})
// 方法方法setPromat(promat) ,就是给每一行设置一个提示符,
// 就好比window命令行的> ,这里设置的是Test>
r1.setPrompt("Test> ");
// prompt()是最重要的方法,因为它体现了readline的核心作用,
// 以行为单位读取数据,prompt方法就是在等待用户输入数据
r1.prompt();
// 调用接口方法
// 监听了'line' 事件,因为prompt方法调用一次就只会读取一次数据
// 所以,在这个方法又调用了一次prompt方法,这样就可以继续读取用户输入
// 从而达到一种命令行的效果
r1.on("line", function (line) {
switch (line.trim()) {
case "copy":
console.log("复制");
break;
case "hello":
console.log("World!");
break;
case "close":
r1.close();
break;
default:
console.log("没有找到命令!");
break;
}
r1.prompt();
});
// close事件监听
r1.on("close", function () {
console.log("再见");
process.exit(0);
})