在Node.js中处理数据I/O

    大多数活跃的Web应用程序和服务都有许多流经它们的数据,这些数据的形式包括文本,JSON字符串,二进制缓冲区和数据流。因此,Node.js有很多内置的机制来处理从一个系统到另一个系统的数据I/O。理解Node.js提供的实现有效和高效的Web应用程序和服务的机制非常重要。

    本篇文章的重点是操纵JSON数据,管理二进制数据缓冲区,并实现可读取和可写入数据流及数据压缩/解压缩。你将学习如何利用Node.js功能去处理具有不同I/O要求的工作。

1,处理JSON

    实现Node.js Web应用程序和服务时你将最常使用的一种数据类型是JSON

    和JSON有关的介绍可以看: https://blog.csdn.net/qq_39263663/article/details/80286437

2,使用Buffer模块缓冲数据

    虽然JavaScript可能是对Unicode非常友好的,但是它不很擅长管理二进制数据。然而,在实施一些Web应用程序和服务时,二进制数据是非常有用的,例如:

  • 传输压缩文件。
  • 生成动态图像
  • 发送序列化的二进制数据
(1)了解缓冲数据

    缓冲数据是由一系列的大端或小端格式字节组成的。这意味着它们比文本数据占用较少的空间。因此,Node.js提供Buffer(缓冲区)模块,它允许你在缓冲区结构中构建,读取,写入和操作二进制数据。Buffer模块是全局性的,所以并不需要使用require()函数来访问它。

    缓冲数据被存储在类似数组的结构中,但它被存储在正常的V8堆之外的原始内存分配区中。因此,缓冲区不能调整大小。

    当对缓冲区与字符串进行互相转换时,需要指明要使用的明确的编码方法。下表列出了受支持的各种编码方法。

方法 说明
utf8 多字节编码的Unicode字符,是大多数文档和网页的标准
utf16le 2个或4个字节小端编码的Unicode字符
ucs2 2个或4个字节小端编码的Unicode字符
base64 base64字符串编码
Hex 每个字节编码为两个十六进制字符
(2)创建缓冲区

    Buffer对象实际上是最原始的内存分配区。因此,你必须在创建时确定其大小。使用new关键字创建Buffer对象有3种方法:

new Buffer(sizeInBytes)
new Buffer(octetArray)
new Buffer(string,[encoding])

    例如,下面的代码行分别使用字节大小,一个八位字节的缓冲区,以及一个UTF8字符串来定义缓冲区:

var buf256 = new Buffer(256);
var bufOctets = new Buffer([0x6f,0x63,0x74,0x64,0x73,0x73]);
var bufUTF8 = new Buffer("some UTF8 Text \u00b6",'utf8');
(3)写入缓冲区

    Buffer对象已经创建后,你不能扩展其大小,但可以吧数据写入缓冲区中的任何位置。如下表所示,有多种方法可以写入缓冲区。

方法 说明
buffer.write(string,[offset],[length],[encoding]) 使用encoding的编码从缓冲区内的offset(偏移量)索引开始,写入string中length数量的字节
buffer[offset] = value 将索引offset中的字符串替换为value
buffer.fill(value,[offset],[end]) 在缓冲区中索引为offset,结束为end中的位置写入 value

writeInt8(value,offset,[noAssert])

writeInt16LE(value,offset,[noAssert])

       Buffer对象有一大批方法来写入整数,无符号整数,等等,value指定写入的值,offset指定要写的索引,而noAssert指定是否要跳过value和offset的验证

    下面是一些代码的例子:

buf256 = new Buffer(256);
buf256.fill(0);
buf256.write("xiaobaicai hello!");
console.log(buf256.toString());
buf256.write("more text",9,9);
console.log(buf256.toString());

(4)从缓冲区读取

方法 说明
buffer.toString([encoding],[start],[end]) 返回一个字符串,它包含了从缓冲区的start索引到end索引到end索引的字符,由encoding指定的编码解码
stringDecoder.write(buffer) 返回缓冲区的解码字符串版本
buffer(offset) 返回缓冲区在指定的offset(偏移量)字节的八进制值
readInt8(offset,[noAssert])  
(5)确定缓冲区长度

    使用Buffer.byteLength(string,[encoding])确定缓冲区字节长度,在后面直接加.length得到的是字符串长度。

"UTF8 text \u00b6".length;
//计算结果是11
Buffer.byteLength("UTF8 text \u00b6");
//计算结果是12
(6)复制缓冲区

    处理缓冲区的一个重要组成部分,是将一个缓冲区中的数据复制到另一个缓冲区的能力。Node.js为Buffer对象提供copy(targetBuffer,[targetStart],[sourceStart],[sourceIndex])函数。targerBuffer参数是另一个Buffer对象,targetStart,sourceStart和sourceEnd是源和目标缓冲区的索引。

#若要从一个缓冲区复制字符串到另一个缓冲区,应确保两个缓冲区使用相同的编码;否则,对结果缓冲区解码时,你可能会得到意想不到的结果。

    也可以通过直接索引将一个缓冲区中的数据复制到另一个缓冲区,比如在下面这个例子中:

    sourceBuffer[index] = destinationBuffer[index]

    下面的程序清单是将一个buffer对象中的数据复制到另一个buffer对象的各种方法

var alphabet = new Buffer('abcdefghijklmnopqrstuvwxyz');
console.log(alphabet.toString());
//copy full buffer
var blank = new Buffer(26);
blank.fill();
console.log("Blank: " + blank.toString());
alphabet.copy(blank);
console.log("Blank: " + blank.toString());
//copy part of buffer
var dashes = new Buffer(26);
dashes.fill('-');
console.log("dashes: " + dashes.toString());
alphabet.copy(dashes,10,10,15);
console.log("dashes: " + dashes.toString());
//copy to and from direct indexes of buffers
var dots = new Buffer(26)
dots.fill('-');
console.log("dots: " + dots.toString());
for(var i=0;i<dots.length;i++){
	if(i%2){dots[i] = alphabet[i];}
}
console.log("dots: " + dots.toString());

下面是输出的结果


(7)对缓冲区切片

    使用slice([start],[end])进行切片

(8)拼接缓冲区

    你可以把多个缓冲区使用concat(list,[totalLength])拼接

3,使用Stream模块来传送数据

    Stream模块是Node.js的一个重要模块。数据流是可读,可写,或即可读又可写的内存结构。流在Node.js中被广泛使用,它用在访问文件时,从HTTP请求中读取数据时,以及其他一些领域。本节将介绍使用Stream模块来创建流,以及从它们读出数据和向它们写入数据。

    流的目的是提供一种从一个地方向另一个地方传达数据的通用机制。它们还公开各种事件,如数据可被读取时的data,当错误发生时的error,等等,这样你就可以注册监听器来在流变为可用或已准备好被写入时处理数据。

    流一般用于HTTP数据和文件。你可用作为读取流打开一个文件或作为读取流从HTTP请求访问数据,并读出所需的字节。此外,你可以创建自己的自定义流。以下各节描述创建和使用Readable(可读),Writeable(可写),Duplex(双工)和Transform(变换)流的过程。

(1)Readable流

    Readable流旨在提供一种机制,以方便地读取从其他来源进入应用程序的数据。Readable流的一些常见实例是:

  • 在客户端的HTTP响应。
  • 在服务器的HTTP请求。
  • fs读取流
  • zlib流
  • crypto(加密)流
  • TCP套接字
  • 子进程的stdout和stderr
  • process.stdin

    Readable流提供read([size])方法来读取数据,其中size指定从流中读取的字节数。read()可以返回一个String对象,Buffer对象或null。Readable流也公开了以下事件。

  • readable:在数据块可以从流中读取的时候发出。
  • data:类似于readable:不同之处在于,当数据的事件处理程序被连接时,流被转变成流动的模式,并且数据处理程序被连续的调用,直到所有数据都被用尽。
  • end:当数据将不再被提供时由流发出。
  • close:当底层的资源,如文件,已关闭时发出。
  • error:当在接收数据中出现错误时发出。

    readable流对象也提供了很多函数,使你可以读取和操作它们。表5.4列出了可用的Readable流对象的方法。

适用于Readable流对象的方法
read([size]) 从流中读取数据。这些数据可以是String,Buffer或者null(null表示没有剩下任何更多的数据)。如果指定size参数,那么被读取的数据将仅限于那个字节数
setEncoding(encoding) 设置从read()请求读取返回String时使用的编码
pause() 暂停从该对象发出的data事件
resume() 恢复从该对象发出的data事件
pipe(destination,[options]) 把这个流的输出传输到一个由destition指定的Writable流对象。options是一个JavaScript对象。例如,0{end:true}当Readable结束时就结束Writable目的地
unpipe([destination]) 从Writble目的地断开这一对象
   

    为了实现自己定义的Readable流对象,你需要首先继承Readable流的功能。实现这一点最简单的方法是使用下面的代码,它使用util模块的inherits()方法:

var util = require('util');
util.inherits(Answers,stream.Readable);

    然后你创建对象调用的实例:

stream.Readable.call(this,opt);

    你还需要实现一个调用push()来输出Readable对象中的数据的_read()方法。push()调用应推入的是一个String,Buffer或者null.

    下面清单中的代码说明了实现一个Readable流,并从中读取的基本知识。请注意,Answer()类继承自Readable,然后实现了Answers.prototype._read()函数来处理数据的推出。还要注意,在第18行,直接read()调用从流中读取第一个条目,然后在第19~21行定义的数据事件处理程序读取其余条目。

var stream = require('stream');
var util = require('util');
util.inherits(Answers,stream.Readable);
function Answers(opt){
	stream.Readable.call(this,opt);
	this.quotes = ["yes","no","maybe"];
	this._index = 0;
}
Answers.prototype._read = function(){
	if(this._index > this.quotes.length){
		this.push(null);
	}else{
		this.push(this.quotes[this._index]);
		this._index += 1;
	}
};
var r = new Answers();
console.log("Direct read:" + r.read().toString());
r.on('data',function(data){
	console.log("callback read: "+ data.toString());
});
r.on('end',function(data){
	console.log("No more answers.");
});

(2)Writeable流

    Writeable流旨在提供把数据写入一种可以轻松地在代码的另一个区域被使用的形式的机制。Writable流的一些常见实例是:

  • 客户端上的HTTP请求
  • 服务器上的HTTP请求
  • fs写入流
  • zlib流
  • crypto流
  • TCP套接字
  • 子进程的stdin
  • process.stdout和process.stderr。

    Writable流提供write(chunk,[encoding],[callback])方法来将数据写入该流。其中,chunk(数据块)中包含要写入的数据;encoding指定字符串的编码,如果需要的话;而callback指定当数据已经完全刷新时执行的一个回调函数。如果数据被成功写入,则write()函数返回true,Writable流也公开了以下事件。

  • drain:在write()调用返回false后,当准备好开始写更多的数据时,发出此事件通知监听器。
  • finish:当end()在Writable对象上被调用,所有的数据都被刷新,并且不会有更多的数据将被接受时发出此事件。
  • pipe:当pipe()方法在readable流上被调用,以添加此Writeable为目的地时,发出此事件。
  • unpipe:当unpipe()方法在Readable流上被调用,以删除此Writeable为目的地时,发出此事件。

    Writable流对象也提供了一些你可以用来写和操纵它的方法。下表列出了可用的Writable流对象的方法。

在Writable流对象上可用的方法
方法 对象
write(chunk,[encoding],[callback])    将数据块写入流对象的数据位置。该数据可以是字符串或缓冲区。如果指定encoding,那么将其用于对字符串数据的编码。如果指定callback,那么它在数据已被刷新后被调用
end([chunk],[encoding],[callback])    与write相同,除了它吧writable对象置于不在接受数据的状态,并发送finish事件外

    为了实现自己的自定义Writable流对象,你需要首先继承Writable流的功能。实现这一点最简单的方法是使用下面的代码,它使用util模块的inherits()方法:

var util = require('util');
util.inherits(Writer,stream.Writable);

    然后你创建对象调用的实例

stream.Writable.call(this,opt);

    你还需要一实现一个_write(data,encoding,callback)方法存储Writable对象的数据。如下所示实现了基本的Writable流

var stream = require('stream');
var util = require('util');
util.inherits(Writer,stream.Writable);
function Writer(opt){
	stream.Writable.call(this,opt);
	this.data = new Array();
}
Writer.prototype._write = function(data,encoding,callback){
	this.data.push(data.toString('utf8'));
	console.log("Adding: " + data);
	callback();
};
var w = new Writer();
for(var i=1;i<=5;i++){
	w.write("Item" + i,'utf8');
}
w.end("ItemLast");
console.log(w.data);

(3)Duplex流

    Duplex(双向)流是结合可读写功能的流。Duplex流的一个很好例子是TCP套接字连接。你可以创建套接字后读取和写入它。

    为了实现自己的自定义Duplex流对象,你需要先继承Duplex流的功能。实现这一点最简单的方法是使用下面的代码,它使用util模块的inherits()方法:

var util = require('util');
util.inherits(Duplexer,stream.Duplex);

    然后创建对象调用方法

stream.Duplex.call(this,opt);

    具体代码如下

var stream = require('stream');
var util = require('util');
util.inherits(Duplexer,stream.Duplex);
function Duplexer(opt){
	stream.Duplex.call(this,opt);
	this.data = [];
}
Duplexer.prototype._read = function readItem(size){
	var chunk = this.data.shift();
	if(chunk == "stop"){
		this.push(null);
	}else{
		if(chunk){
			this.push(chunk);
		}else{
			setTimeout(readItem.bind(this),500,size);
		}
	}
};
Duplexer.prototype._write = function(data,encoding,callback){
	this.data.push(data);
	callback();
};
var d = new Duplexer();
d.on('data',function(chunk){
	console.log('read: ',chunk.toString());
});
d.on('end',function(){
	console.log('Message Complete');
});
d.write("I think, ");
d.write("therefore");
d.write("I am.");
d.write("Rene Descartes");
d.write("stop");

(4)Transform流

    Tramsform(变换)流扩展了Duplex流,但它修改Writable流和Readable流之间的数据。当你需要修改从一个系统到另一个系统的数据时,此流类型会非常有用。Transform流的一些实力如下:

  • zlib流
  • crypto流

    Duplex和Transform流之间的一个主要区别是,在Transform流中不需要实现_read()和_write()原型方法。这些被作为直通函数提供。相反,你要实现_transform()方法应该接受来自write()请求的数据,对其修改,并推出修改后的数据,

    下面清单中的代码说明了实现Transform流的基本知识。这个流接受JSON字符串,将它们转换为对象,然后发出发送对象的名为object的自定义事件给所有监听器。该_transform()函数也修改对象来包括一个handled属性,然后以一个字符串形式发送。请注意,第18~21行实现了对象的事件处理函数,它显示某些属性。

var stream = require('stream');
var util = require('util');
util.inherits(JSONObjectStream,stream.Transform);
function JSONObjectStream(opt){
	stream.Transform.call(this,opt);
};
JSONObjectStream.prototype._transform = function(data,encoding,callback){
	object = data?JSON.parse(data.toString()) : "";
	this.emit("object",object);
	object.handled = true;
	this.push(JSON.stringify(object));
	callback();
};
JSONObjectStream.prototype._flush = function(cb){
	cb();
};
var tc = new JSONObjectStream();
tc.on("object",function(object){
	console.log("Name: %s",object.name);
	console.log("Color: %s",object.color);
});
tc.on("data",function(data){
	console.log("Data: %s",data.toString());
});
tc.write('{"name":"Carolinus","color":"Green"}');
tc.write('{"name":"xiaobaicai","color":"white"}');
tc.write('{"name":"xiaohui","color":"blue"}');

(5)把Readable流用管道输送到Writable流

    你可以用流对象做的最酷的东西之一是通过pipe(writableStream,[options])函数吧Readable流链接到Writable流。它做的工作正如其名: 它把Readable流的输出直接输入到Writable流。options参数接受一个end属性设置为true或false的对象。当end是true时,Writable流随着Readable流的结束而结束。这是默认的行为。例如:

readStream.pipe(writeStream,{end:true});

    你也可以通过编程方式使用unpipe(destinationStream)选项来打破管道。如下是一段代码例子:

var stream= require('stream');
var util = require('util');
util.inherits(Reader,stream.Readable);
util.inherits(Writer,stream.Writable);
function Reader(opt){
	stream.Readable.call(this,opt);
	this._index = 1;
}
Reader.prototype._read = function(size){
	var i = this._index++;
	if(i>10){
		this.push(null);
	}else{
		this.push("Item" + i.toString());
	}
};
function Writer(opt){
	stream.Writable.call(this,opt);
	this._index = 1;
}
Writer.prototype._write = function(data,encoding,callback){
	console.log(data.toString());
	callback();
};
var r = new Reader();
var w = new Writer();
r.pipe(w);

猜你喜欢

转载自blog.csdn.net/qq_39263663/article/details/80342529