在本章的开始,我本来只想写一些关于fs模块的内容,虽然这个模块包含的方法非常的多,但没有想到的是它和我们上一篇文章Node.js Buffer还存在着关联,所以我又把关于Buffer的内容简单的学习下,以至于对它不那么陌生。我以为这样就可以来看看fs模块了,没想到又杀出个程咬金,这就是本文要学习的Stream。

这个Stream目前还是个不稳定的模块,从API的介绍来说,所有stream是对象EventEmitter的实例,测试下:

//abs file_path:F:\wamp\www\Node\mystream.js
console.log(new(require(‘stream’)) instanceof require(‘events’).EventEmitter);//输出:true

什么是Stream(流)
在Node中,Stream是一个抽象的接口,由不同的实例对象来实现,比如HTTP服务器中的requestI流,再比如stdout标准输出流。流可以是可读的,可写的,或者可读可写的。

为什么要提供Stream
在Node中,大部分的模块能持续的读或者写,为了确保这些模块拥有统一的方法来完成持续的读或者写,Stream于是为它们提供了统一的接口。Stream接口提供了通用的方法和属性,因为stream是EventEmitter的实例,所以允许这些模块emit自己的事件。

Readable Stream(可读流)
一个常见的readable stream就是从一个文件中读取数据。可读流支持的事件有四种:data,end,error,close。它们分别对应如下场景:
data:当有数据可读时触发
end:当没有数据可读时或者达到文件尾
error:读取数据时遇到异常
close:底层的文件描述(fd)关闭时

为了简单起见,我们使用fs模块中的方法来读取windows下的boot.ini文件:

1
2
3
4
5
6
var fs = require('fs');
fs.readFile('c:/boot.ini',function(err,data){
  if(err) throw err;
        //console.log(data);//传递给回调函数的data参数是一个Buffer对象,大小默认为64K?
  console.log(data.toString());
});

fs.readFile方法能触发data事件,在这里我们把读取到的数据打印到控制台上。为了让本文稍微有点深度,这里贴上fs.readFile方法的源码:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
fs.readFile = function(path, encoding_) {
  var encoding = typeof(encoding_) === 'string' ? encoding_ : null;
  var callback = arguments[arguments.length - 1];
  if (typeof(callback) !== 'function') callback = function() {};
 
  // first, stat the file, so we know the size.
  var size;
  var buffer; // single buffer with file data
  var buffers; // list for when size is unknown
  var pos = 0;
  var fd;
 
  fs.open(path, constants.O_RDONLY, 438 /*=0666*/, function(er, fd_) {
    if (er) return callback(er);
    fd = fd_;
 
    fs.fstat(fd, function(er, st) {
      if (er) return callback(er);
      size = st.size;
      if (size === 0) {
        // the kernel lies about many files.
        // Go ahead and try to read some bytes.
        buffers = [];
        return read();
      }
 
      buffer = new Buffer(size);
      read();
    });
  });
 
  function read() {
    if (size === 0) {
      buffer = new Buffer(8192);
      fs.read(fd, buffer, 0, 8192, -1, afterRead);
    } else {
      fs.read(fd, buffer, pos, size - pos, -1, afterRead);
    }
  }
 
  function afterRead(er, bytesRead) {
    if (er) {
      return fs.close(fd, function(er2) {
        return callback(er);
      });
    }
 
    if (bytesRead === 0) {
      return close();
    }
 
    pos += bytesRead;
    if (size !== 0) {
      if (pos === size) close();
      else read();
    } else {
      // unknown size, just read until we don't get bytes.
      buffers.push(buffer.slice(0, bytesRead));
      read();
    }
  }
 
  function close() {
    fs.close(fd, function(er) {
      if (size === 0) {
        // collected the data into the buffers list.
        buffer = Buffer.concat(buffers, pos);
      } else if (pos < size) {
        buffer = buffer.slice(0, pos);
      }
 
      if (encoding) buffer = buffer.toString(encoding);
      return callback(er, buffer);
    });
  }
};

想认真学习的人应该仔细读读fs.readFile是如何实现的,想看fs模块的完整源码位于lib目录下的fs.js文件。下载Node.js源码(版本v0.8.18)。

为了更加直观的描述readable stream中的这些事件,请参见下面的例子

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var fs = require('fs');
var opt = {
  flags:'r',
  encoding:'utf8',
  fd:null,
  mode:0666,
  bufferSize:64 * 1024,
  start:0,
  end:99
};
st = fs.createReadStream('c:/boot.ini',opt);
//st.pause();//暂停data事件的触发
//st.resume();//恢复data事件
var text = "";
st.on('data',function(data){
  text += data;
});
st.on('end',function(close){
  console.log("reading 99 bytes:\n" + text);
});
st.on('error',function(error){
  console.log('An error occurred:' + error);
});
st.on('close',function(){
  console.log('The file descriptor has been closed.');
});
st.on('open',function(fd){
  console.log('Current fd number: ' + fd);
});

输出:

F:\wamp\www\Node>node mystream.js
Current fd number: 3
reading 99 bytes:
[boot loader]
timeout=3
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]

The file descriptor has been closed.

F:\wamp\www\Node>

不想太多解析这个示例,为什么文件描述是3?因为0,1,2已经被占用啦,原因?你懂的!

Writable Stream(可写流)
可写流支持的事件有:drain,error,close,pipe。我只介绍drain和pipe,它们分别对应如下场景:
drain:当Node的写队列为空并且Buffer中没有数据时,发生这个事件
pipe:当把Writable Stream对象传递一个Readable Stream对象的pipe方法时发生

分别来看看这两个事件的例子:
1.drain

01
02
03
04
05
06
07
08
09
10
11
var fs = require('fs');
var stream = fs.createWriteStream(__dirname + '/out.txt');
var str = 'i=[';
  
for (var i=0;i<10;i++){
  stream.write(str + i + ']\r\n');
}
  
stream.on('drain',function(){
  console.log('start drain');
});

当for循环完成时,我们的控制台才打印出’start drain’,这意味着写队列已经为空,即当前没有事件存在写入的情况。由于drain事件能发生在任何时候,所以为了避免触发不必要的drain事件,官方的建议是,使用events.EventEmitter once()方法,这样drain事件只可能触发一次,尔后它上面的监听器就被删除了。

2.pipe

1
2
3
4
5
6
7
8
var fs = require('fs');
var writeStream = fs.createWriteStream('copyout.txt',{ flags:'w'});
var readStream = fs.createReadStream('./out.txt');
 
readStream.pipe(writeStream);
writeStream.on('close',function(){
    console.log('All done!');
});

这个示例把out.txt文件中的内容复制到了copyout.txt中。

这篇文章写了我很长时间,虽然还有很多的知识点没有介绍上来,也到此为止吧。我希望在日后有机会补充一些。

Date.prototype.format = function(format){
var o = {
"M+" : this.getMonth()+1, //month
"d+" : this.getDate(), //day
"h+" : this.getHours(), //hour
"m+" : this.getMinutes(), //minute
"s+" : this.getSeconds(), //second
"q+" : Math.floor((this.getMonth()+3)/3), //quarter
"S" : this.getMilliseconds() //millisecond
}
if(/(y+)/.test(format)) format=format.replace(RegExp.$1,
(this.getFullYear()+"").substr(4 - RegExp.$1.length));
for(var k in o)if(new RegExp("("+ k +")").test(format))
format = format.replace(RegExp.$1,
RegExp.$1.length==1 ? o[k] :
("00"+ o[k]).substr((""+ o[k]).length));
return format;
}

var fs=require('fs');
fs.open(__dirname+'/error.log','a',function(err,fd){

if(err){ throw err; }
cTime = new Date();
cTime = cTime.format('yyyy-MM-dd h:m:s');
var wBuff = new Buffer('Start log: ' + cTime + "\r\n");
buffPos = 0;
buffLen = wBuff.length;
filePos = 0;

fs.write(fd,wBuff,buffPos,buffLen,filePos,function(err,wbytes,data){
if(err){ throw err; }
console.log('wrote ' + wbytes + ' bytes');
fs.close(fd);
});

});

nodejs fs学习的更多相关文章

  1. # nodejs模块学习: express 解析

    # nodejs模块学习: express 解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固,需要开发者创造大量的轮子 ...

  2. nodejs fs path

    内容详见我的gitHub: https://github.com/shangyueyue/ssy-utils/tree/master/src/nodejs/fs

  3. nodejs模块学习: webpack

    nodejs模块学习: webpack nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固,需要开发者创造大量的轮子来解决现实 ...

  4. nodejs基础学习1

    ES6常用新语法 ES6新语法 什么是ES6? 由于JavaScript是上个世纪90年代,由Brendan Eich在用了10天左右的时间发明的:虽然语言的设计者很牛逼,但是也扛不住"时间 ...

  5. NodeJS入门学习教程

    一.概念 1.什么是nodejs Node.js是JavaScript 运行时环境,通俗易懂的讲,Node.js是JavaScript的运行平台 Node.js既不是语言,也不是框架,它是一个平台 2 ...

  6. NodeJs入门学习(一)

    NodeJs是针对前端工程师向web后端深入理解的一门很好的语言. 首先,记录NodeJS几大特性,后续补充: 一.Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. ...

  7. NodeJs开发学习目录

    1.Nodejs基本概念及Nodejs.npm安装测试[2014-06-06] 2.开发工具简介(主要介绍Sublime Text使用) [2014-06-06] 3.Sublime text插件安装 ...

  8. nodejs模块学习: connect解析

    nodejs模块学习: connect解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固,需要开发者创造大量的轮子来解决 ...

  9. nodejs模块学习: connect2解析

    nodejs模块学习: connect2 解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固,需要开发者创造大量的轮子来 ...

随机推荐

  1. 使用Selectivizr让你的 CSS3选择器 通吃IE6/7/8

    说到HTML5,总是会让人不自觉的想到CSS3,貌似他们就应该是成双成对.OK!前几天和大家分享了<使用html5shiv让HTML5通吃IE6/7/8>,那今天,便再和大家分享一个能让H ...

  2. python笔记2-数据类型:字符串常用操作

    这次主要介绍字符串常用操作方法及例子 1.python字符串 在python中声明一个字符串,通常有三种方法:在它的两边加上单引号.双引号或者三引号,如下: name = 'hello' name1 ...

  3. centos6.5安装apache2

    参考 http://my.oschina.net/u/593517/blog/340289 http://blog.csdn.net/hkmaike/article/details/9732177

  4. 【Raspberry Pi】定时运行python程序读温湿度传感器数据&发邮件

    1.定时执行脚本 http://tech.it168.com/a2011/0707/1214/000001214830_all.shtml /sbin/service crond start //启动 ...

  5. Page_ClientValidate 用法

    JS script function ConfirmMe(){   return confirm("Do you want to proceed?");} ASPX <asp ...

  6. 基础-Eclipse 教程

    1.Eclipse 是一个开放源代码的.基于 Java 的可扩展开发平台.2.下载地址为: https://www.eclipse.org/downloads/.3.Eclipse 修改字符集 : W ...

  7. Django model :add a non-nullable field 'SKU' to product without a default; we can't do that

    You are trying to add a non-nullable field 'SKU' to product without a default; we can't do that (the ...

  8. Oracle起步---创建临时表空间/表空间/创建用户/授权

    1. 安装: 百度一下你就知道 2. sqlplus登录/sqlplus命令登录 在安装Oracle时,你需要记住设置的“全局数据库名”(默认为orcl) 和 口令,在以两种方式登录时: 用户名: s ...

  9. python数据表的合并(python pandas join() 、merge()和concat()的用法)

    merage# pandas提供了一个类似于关系数据库的连接(join)操作的方法<Strong>merage</Strong>,可以根据一个或多个键将不同DataFrame中 ...

  10. std::stringstream(2)

    stringstream本身的复制构造函数是私有的,无法直接用,于是带来了一些复杂的问题 网上,流传着几种办法,如streamA.str(streamB.str()),但这种办法,复制的仅仅是初始化时 ...