通过Node.js Stream API 实现逐行读取的实例

Node 给 streaming 带来了简洁和美。Streams 目前是一种很棒的用于建立模块和应用的方式。原来的 streams API 存在一些问题,在 v0.10 版本中对这些问题进行了修复,并且扩展了一些 API 使得应用更简单并且可以概括更多的应用场景。

本篇文章将介绍并通过实例应用 v0.10 版本提供的新的 API。

逐行问题

具有良好组织的日志数据对一个公司的开发团队是非常宝贵的资源。为了更好的使用并分析它们,而不只是通过 shell 命令行去操作,我们需要逐行扫描日志数据。

Stream 的优点就是我们不需要把整个日志文件都一次性读入内存,因为它们可能很庞大,而是在它们可以准备好去读取的时候再处理它们。Stream 可以工作于任何 I/O 场景下,包括文件系统,网络等等。

通过使用心得 stream API,我们可以创建可复用的实现上述逐行操作的 I/O 模块。

Transform Stream

Node 0.10 提供了非常优雅的 stream.Transform 类,用以处理输入输出是因果相关的。对于我们的问题来说,输入和输出的数据是完全一样的,只是把输入的数据逐行分离为了更好的处理。

位于管道中间层的 Transform 是即可读也可写的:
Transform pipeline
以下是使用 Transform的初始化代码:

var stream = require('stream')
var liner = new stream.Transform( { objectMode: true } )

打开 objectMode

吼吼,这个 {objectMode: true} 是个啥?如果没有这个 objectMode,stream 默认把纯数据块送过来,否则会把数据快放到一个 object 中,当然这个 object 中还会包含其他信息。

_transform 方法

这只是一个开始,我们继续。Transform 类在应用时需要我们必须提供一个叫做 _transform 的方法,还有一个 _flush 方法可以选择提供。我们先来看一下这个 _transform 方法到底是什么。

_transform 方法在每次 stream 中有数据来了之后都会被执行,先看代码:

liner._transform = function (chunk, encoding, done) {
  var data = chunk.toString()
  if (this._lastLineData) data = this._lastLineData + data 

  var lines = data.split('\n') 
  this._lastLineData = lines.splice(lines.length-1,1)[0] 

  lines.forEach(this.push.bind(this)) 
  done()
}

数据一来,_transform 方法就会被执行。联同数据一起过来的还有数据的编码和一个表示此数据已经接受完毕的信号函数。

在这个问题中,我们并不关心编码问题。通过 toString() 把数据转为需要的字符串,然后再通过 split('\n') 数据块字符串按换行符打散为一个数组。然后在把每一行 push 到对应的处理模块中。

注:push 方法是 Readable stream 类的内置方法,同时在 Node 0.8 版本中和产生 data 时间的的方法是同类的:

stream.emit(‘data’, data) ➤ stream.push(data)

最后通过调用 done() 方法来发出接受完成的信号。由于 done 方法是一个回调函数,我们也可以把它在 _transform 中进行异步调用。

代码中的 _lastLineData 又是神马?在 stream 中我们并不想一块数据的结尾是从一行的中间断开的,为了解决这个问题,我们实际上并不会吧打散的数组中的最后一行送出去,而是留到下一次的数据块来的时候放到下一次数据块的开头。

_flush 方法

然后我们再来看看这个 _flulsh 方法,还记得在 _transform 方法中每次 _lastLineData 的值都不会被送出去吗,是不是最后一次数据块的 _lastLindeData 就没法收到了?没错, _flush方法就是用来处理这种情况的。在所有的数据块都被 _transform 方法处理过后,才会调用 _flush 方法。所以它的作用就是处理残留数据的:

liner._flush = function (done) {
  if (this._lastLineData) this.push(this._lastLineData)
  this._lastLineData = null
  done()
}

如果有 _lastLineData 则把它 push 出去然后清空它,最后调用 `done()` 方法标志着完成处理残留数据的工作,同时这也意味着 stream 的结束。需要注意的是, _flush 方法并不是必须的有些场景下就不需要。

简单代码实现

以下代码是一个简单的逐行读取的模块的实现:

var stream = require('stream')
var liner = new stream.Transform( { objectMode: true } )

liner._transform = function (chunk, encoding, done) {
  var data = chunk.toString()
  if (this._lastLineData) data = this._lastLineData + data

  var lines = data.split('\n')
  this._lastLineData = lines.splice(lines.length-1,1)[0]

  lines.forEach(this.push.bind(this))
  done()
}

liner._flush = function (done) {
     if (this._lastLineData) this.push(this._lastLineData)
     this._lastLineData = null
     done()
}

module.exports = liner

测试实例

看起来已经差不多了,是不是可以用起来了?

首先我们需要一个数据源。任何由行组成的文件都可以,以日志文件为例:

var fs = require('fs')
var liner = require('./liner')
var source = fs.createReadStream('./access_log')
source.pipe(liner)
liner.on('readable', function () {
  var line
  while (line = liner.read()) {
    // do something with line
  }
})

原文链接


生吞橘子

335 声望
8 粉丝
0 条评论
推荐阅读
利用原生 Javascript 实现 Delegated Event
举个例子说明一下,有一组按钮,每当点击其中一个按钮,就把这个按钮的状态变为 "active",再点一下就取消 "active" 状态,代码如下:

2hu10n92hen91阅读 5.5k评论 2

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木60阅读 5.9k评论 16

从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木39阅读 7k评论 6

从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...

乌柏木32阅读 6k评论 9

从零搭建 Node.js 企业级 Web 服务器(五):数据库访问
回顾 从零搭建 Node.js 企业级 Web 服务器(一):接口与分层,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,控制层与服务层实现了业务处理过程,模型层定义了业务实体并以 对象-关系...

乌柏木32阅读 4.5k评论 9

从零搭建 Node.js 企业级 Web 服务器(十三):断点调试与性能分析
Node.js 官方提供了断点调试机制,出于安全性考虑默认为关闭状态,可以通过 node 参数 --inspect 或 --inspect-brk 开启,配合 IDE 能够非常方便地调试代码,本章就上一章已完成的项目 licg9999/nodejs-server-ex...

乌柏木29阅读 3.8k评论 9

从零搭建 Node.js 企业级 Web 服务器(八):网络安全
计算机网络依据 TCP/IP 协议栈分为了物理层、网络层、传输层、应用层,通常基础设施供应商会解决好前三层的网络安全问题,需要开发者自行解决应用层的网络安全问题,本章将着重表述应用层常见的网络安全问题及处...

乌柏木31阅读 5.7k评论 1

生吞橘子

335 声望
8 粉丝
宣传栏