I/O是使node与其他框架与众不同的很重要的一个部分,本章将解释它是如何实现node非堵塞I/O的。
Streams
stream api是一个抽象接口,他帮助node中很多部分实现不间断输入和输出的操作。stream api为它的实现类提供了一些通用的方法和属性,streams可以是可读的、可写的或者两者都是,并且他们都实现了EventEmitter,可以emit事件。
可读streams
可读streams提供了一系列的方法和事件,使我们可以对数据源的数据进行访问。基本上,可读stream就是为了触发data事件,同时他提供了一些属性允许你对获取的数据大小和获取速度进程配置。来看下面的例子,我们从一个文件中读取文件,每次当有数据可以读取的时候,都会触发data事件。
var fs = require('fs'); var filehandle = fs.readFile('data.txt', function(err, data) { console.log(data) });
通常我们需要把数据存在一个变量中,当stream结束的时候,一起输出结果。
var spool = ""; stream.on('data', function(data) { spool += data; }); stream.on('end', function() { console.log(spool); });
(文件系统)Filesystem
当我们需要访问硬盘上文件的时候,我们需要使用filesystem模块。他非常接近于POSIX风格的文件I/O,对所有方法提供异步和同步两种版本。我们推荐使用异步的方法,虽然这样会使代码变的稍微复杂,但是可以让你并行访问多个文件,并减少脚本运行的时间。
当我们使用异步方法的时候,时常遇到的问题就是代码执行顺序的问题。因为异步方式是不能保证执行顺序的,当一个操作依赖于另外一个操作的时候就会出现问题,如下例:
var fs = require('fs'); fs.readFile('warandpeace.txt', function(e, data) { console.log('War and Peace: ' + data); }); fs.unlink('warandpeace.txt');
这里存在的问题是,当我们还没有读完文件内容的时候,就有可能执行了文件删除的操作。为了解决这个问题,我们需要稍微修改代码,使用内部回调,如下:
var fs = require('fs'); fs.readFile('warandpeace.txt', function(e, data) { console.log('War and Peace: ' + data); fs.unlink('warandpeace.txt'); });
Buffers
虽然node使用javascript作为开发语言,但是他与javascript传统的执行环境是有区别的。例如,浏览器中的javascript更多的是执行函数,而对2进制数据的处理则比较少。虽然javascript支持位运算,但是他没有原生2进制数据的表现形式。node提供了buffer类方便你对2进制数据的操作。
非常重要说明的一点是,当你拷贝一个字符串到Buffer,他们将会以2进制的形式存储,当然你也可以把buffer转换成字符串。可以使用三种参数创建buffer对象,第一种参数是buffer的长度,第二种是bytes数组,第三种是字符串。例如,我们在node REPL输入下面的语句,设置buffer长度为10:
new Buffer(10);
node会给buffer提供一个长度为10的内存区域,但是这段区域是系统刚释放出来的,里面的内容设置还没来得及清除,所以每次运行上面的语句得到的运行结果是不一样的。
在node中默认使用utf-8的编码格式,当然你也可以改变编码格式。
new Buffer('foobarbaz'); new Buffer('foobarbaz', 'ascii'); new Buffer('foobarbaz', 'utf8'); new Buffer('é'); new Buffer('é', 'utf8'); new Buffer('é', 'ascii');
node提供了其他方法去操作buffer,例如Buffer.write()。传入字符串和索引(默认为0)参数,如果buffer有足够的空间,我们将会从指定的索引开始,把buffer内容完全替换成字符串内容;否则我们将会截取字符串,以适应从索引开始位置到结尾的buffer的长度。最后返回修改的buffer内容的长度。
var b = new Buffer(1); b b.write('a'); b b.write('é'); b
定义一个长度为1的buffer,从索引0开始覆写buffer内容为‘a’,这时buffer内容被修改,索引也增加到1。我们再次覆写buffer内容,这时buffer从索引1开始已经没有空间了,所以覆写失败,buffer内容没有发生变化。执行结果如下,可以说明一切。
再看一个例子:
var b = new Buffer(5); b.write('fffff'); b b.write('ab', 1); b
创建一个长度为5的buffer,覆写内容为‘fffff’,从索引1开始覆写内容为‘ab’,我们来看运行结果。
本节课的内容就讲完了,有什么疑问可以给我留言。