# @tars/stream

## 00 - 安装

> $ npm install @tars/stream

## 01 - stream 模块基本介绍和使用方法

stream 模块用作 Tars(tars/TUP)基础协议编解码库，使用该模块可以基于 tars 协议描述格式对数据流进行编解码，并能够与目前使用 tars 协议的 TARS 服务端以及终端进行无障碍通信。

tars 编解码模块工作流方式一般有如下三种：

#### 第一种，以 tars 文件作为调用方和服务方的通信桥梁（双方约定最终协议以 tars 文件为准）。

该 tars 文件也就是我们常说的以".tars"结尾的协议描述文件。

该 tars 文件一般由后台开发制定，前台开发需向后台开发索求经评审确认的 tars 文件，然后经工具转换成适用于 NodeJS 的编解码源代码文件。

```cpp
module TRom
{
    struct User_t
    {
        0 optional int id = 0;
        1 optional float score = 0;
        2 optional string name = "";
    };

    struct Result_t
    {
        0 optional int id = 0;
    };

    interface NodeJsComm
    {
        int test();

        int getall(User_t stUser, out Result_t stResult);

        int getUsrName(string sUsrName, out string sValue1, out string sValue2);

        int secRequest(vector<byte> binRequest, out vector<byte> binResponse);
    };
};
```

比如，我们将如上内容保存为“Protocol.tars”后，可以使用如下的命令生成不同的文件：

> $ tars2node Protocol.tars

上述命令将忽略 interface 描述段，只转换文件中定义的“常量”、“枚举值”、“结构体”等数据类型，供开发者当不使用 Tars 框架作为调用工具时的编解码库文件。生成的文件名称为“Protocol.js”。

> $ tars2node Protocol.tars --client

上述命令不仅转换文件中定义的“常量”、“枚举值”、“结构体”等数据类型，同时将 interface 的描述段翻译成 RPC 调用框架。生成的文件名称为“ProtocolProxy.js”，该文件供调用方使用。开发者引入该文件之后，可以直接调用服务端的服务。具体的使用方法请参考“npm install rpc”模块的说明文档。

> $ tars2node Protocol.tars --server

上述命令不仅转换文件中定义的“常量”、“枚举值”、“结构体”等数据类型，同时将 interface 的描述段翻译成服务端的接口文件。生成的文件名称为“Protocol.js”以及“ProtocolImp.js”，开发者不要改动“Protocol.js”，只需要继续完善“ProtocolImp.js”，实现文件中具体的函数，即可作为 Tars 服务端提供服务。具体的使用方法请参考“npm install rpc”模块的说明文档。

#### 第二种，没有协议描述文件，需要我们自己手工书写编解码代码时。

比如服务后台提供购买某件商品的功能，它需要“用户号码”、“用户昵称”、“商品编号”、“商品数量”等四个参数。 后台对这四个参数的编号（也就是 tars 中所指的 tag）分别为 0、1、2、3。

```javascript
//第一步，引入tars/TUP编解码库
var Tars = require("@tars/stream");

//第二步，客户端按照服务端要求，对输入参数进行编码
var ost = new Tars.OutputStream();
ost.writeUInt32(0, 155069599);		//写入“用户号码”；在服务端“0”代表“用户号码”。
ost.writeString(1, "KevinTian");	//写入“用户昵称”；在服务端“1”代表“用户昵称”。
ost.writeUInt32(2, 1002121);		//写入“商品编号”；在服务端“2”代表“商品编号”。
ost.writeUInt32(3, 10);				//写入“商品数量”；在服务端“3”代表“商品数量”。

//第三步，客户端将打包后的二进制Buffer发送给服务端
send ( ost.getBinBuffer().toNodeBuffer() ) to server

//第四步，服务端从客户端接收完整的请求二进制Buffer
recv ( var requestBuffer = new Buffer() ) from client

//第五步，将该请求进行解码反序列化
var ist = new Tars.InputStream(new Tars.BinBuffer(requestBuffer));

var uin  = ist.readUInt32(0, true);	//根据编号“0”读取“用户号码”。
var name = ist.readString(1, true);	//根据编号“1”读取“用户昵称”。
var gid  = ist.readUInt32(2, true);	//根据编号“2”读取“商品编号”。
var num  = ist.readUInt32(3, true);	//根据编号“3”读取“商品数量”。

//第六步，根据相关传入参数进行相应的逻辑操作
console.log("name:", name);
console.log("num :", num);
......
```

#### 第三种，服务端接受 TUP 协议格式的数据。

```javascript
//第一步，引入tars/TUP编解码库
var Tars = require("@tars/stream");

//第二步，客户端按照服务端要求，对输入参数进行编码
var tup_encode = new Tars.Tup();
tup_encode.writeUInt32("uin",  155069599);		//服务端接口函数“用户号码”的变量名称为“uin”。
tup_encode.writeString("name", "KevinTian");	//服务端接口函数“用户昵称”的变量名称为“name”。
tup_encode.writeUInt32("gid",  1002121);		//服务端接口函数“商品编号”的变量名称为“gid”。
tup_encode.writeUInt32("num",  10);				//服务端接口函数“商品数量”的变量名称为“uum”。

var BinBuffer = tup_encode.encode(true);

//第三步，客户端将打包后的二进制Buffer发送给服务端
send ( BinBuffer.toNodeBuffer() ) to server

//第四步，服务端从客户端接收完整的请求二进制Buffer
recv ( var requestBuffer = new Buffer() ) from client

//第五步，将该请求进行解码反序列化
var tup_decode = new Tars.Tup();
tup_decode.decode(new Tars.BinBuffer(requestBuffer));

var uin  = tup_decode.readUInt32("uin");		//服务端根据变量名“uin”读取“用户号码”。
var name = tup_decode.readString("name");		//服务端根据变量名“name”读取“用户昵称”。
var num  = tup_decode.readUInt32("num");		//服务端根据变量名“gid”读取“商品编号”。
var gid  = tup_decode.readUInt32("gid");		//服务端根据变量名“num”读取“商品数量”。

//第六步，根据相关传入参数进行相应的逻辑操作
console.log("name:", name);
console.log("num :", num);
......
```

## 02 - stream 支持的数据类型以及使用方法

**基本数据类型**

| 数据类型 | 对应 C++语言的数据类型                                                    |
| ---- | ---------------------------------------------------------------- |
| 布尔值  | bool                                                             |
| 整型   | char(int8)、short(int16)、int(int32)、long long(int64)              |
| 整型   | unsigned char(uint8)、unsigned short(uint16)、unsigned int(uint32) |
| 数值   | float(32 位)、double(64 位)                                         |
| 字符串  | std::string                                                      |

**复杂数据类型**

| 数据类型       | 对应 C++语言的数据类型                                                           |
| ---------- | ----------------------------------------------------------------------- |
| 结构体        | struct（在 Tars 框架中需要使用 tars2node 根据 tars 文件来生成 Javascript 中的类）           |
| 二进制 Buffer | vector\<char>（在 NodeJs 中使用\[stream].BinBuffer 类型来模拟）                    |
| 数组         | vector\<DataType>（在 NodeJs 中使用\[stream].List(vproto)类型来模拟）              |
| 词典         | map\<KeyType, DataType>（在 NodeJs 中使用\[stream].Map(kproto, vproto)类型来模拟） |

**关于 NodeJs 中数据类型的特别说明**

**\[1]：** “复杂数据类型”与“基本数据类型”，或者“复杂数据类型”与“复杂数据类型”组合使用可以组成其他高级数据类型。

**\[2]：** 虽然 NodeJS 中支持 Float 和 Double 数据类型，但我们不推荐使用，因为在序列化和反序列化之后，数值存在精度损失，某些情况下会对业务逻辑造成伤害。

**\[3]：** 我们这里实现的 64 位整形实际上是伪 64 位，在 NodeJs 中它的原形仍然是 Number。

我们都知道 Js 中的 Number 类型采用 IEEE754 双精度浮点数标准来表示。IEEE754 规定有效数字第一位默认为 1，再加上后面的 52 位来表示数值。

也就是说 IEEE754 提供的有效数字的精度为 53 个二进制位，这就意味着 NodeJs 的 Number 数值或者说我们实现的 Int64 数据类型只能精确表示绝对值小于 2 的 53 次方的整数。

**\[4]：** 在 Javascript 中 String 类型是 Unicode 编码，在 tars 编解码时我们将其转换成了 UTF8 编码格式；

后台服务程序接受到的字符串是 UTF8 编码，如果需要按照 GBK 编码的方式处理字符串，需要后台程序先做下转码（UTF8->GBK）；

后台服务程序如果使用的是 GBK，发送字符串之前，需要将其转成 UTF8 编码。

## 03 - 基本类型使用方法

```javascript
//必须引入stream模块
var Tars = require("@tars/stream");

//使用Tars.OutputStream对数据进行序列化
var os = new Tars.OutputStream();

os.writeBoolean(0, false);
os.writeInt8(1, 10);
os.writeInt16(2, 32767);
os.writeInt32(3, 0x7ffffffe);
os.writeInt64(4, 8589934591);
os.writeUInt8(5, 200);
os.writeUInt16(6, 65535);
os.writeUInt32(7, 0xffffffee);
os.writeString(8, "我的测试程序");

//使用Tars.InputStream对数据进行反序列化
var is = new Tars.InputStream(os.getBinBuffer());

var tp0 = is.readBoolean(0, true, false);
console.log("BOOLEAN:", tp0);

var tp1 = is.readInt8(1, true, 0);
console.log("INT8:", tp1);

var tp2 = is.readInt16(2, true, 0);
console.log("INT16:", tp2);

var tp3 = is.readInt32(3, true, 0);
console.log("INT32:", tp3);

var tp4 = is.readInt64(4, true, 0);
console.log("INT64:", tp4);

var tp5 = is.readUInt8(5, true, 0);
console.log("UINT8:", tp5);

var tp6 = is.readUInt16(6, true, 0);
console.log("UINT16:", tp6);

var tp7 = is.readUInt32(7, true, 0);
console.log("UINT32:", tp7);

var tp8 = is.readString(8, true, "");
console.log("STRING:", tp8);
```

## 04 - 复杂类型前传 - 用于表示复杂类型的类型原

首先，我们理解下什么是 **类型原型**！

在 C++中，我们可以按如下方法声明一个字符串的容器向量：

```javascript
#include <string>
#include <vector>

std::vector<std::string> vec;
vec.push_back("qzone");
vec.push_back("wechat");
```

其中 std::vectorstd::string，std::vector 表示容器类型，而 std::string 则表示该容器所容纳的 **类型原型** 。

那我们如何在 NodeJs 中表示该类型？并能使之与 tars 的编解码库无缝的融合？

为了解决这个问题，我们使用如下的方法对 std::vector 进行模拟，以达到上述 C++代码所能完成的功能：

```javascript
var Tars = require("@tars/stream");

var abc = new Tars.List(Tars.String);
abc.push("qzone");
abc.push("wechat");
```

其中 Tars.List(Tars.String)，Tars.List 表示数组类型，而 Tars.String 则用来表示该容器所容纳的 **类型原型**。

**至此，我们明白类型原型主要是用来与复杂数据类型组合，表示更加复杂的数据类型。**

目前的版本中，我们支持如下的类型原型定义：

| 数据类型       | 描述                                                                                                               |
| ---------- | ---------------------------------------------------------------------------------------------------------------- |
| 布尔值        | \[stream].Boolean                                                                                                |
| 整型         | \[stream].Int8, \[stream].Int16, \[stream].32, \[stream].64, \[stream].UInt8, \[stream].UInt16, \[stream].UInt32 |
| 数值         | \[stream].Float, \[stream].Double                                                                                |
| 字符串        | \[stream].String                                                                                                 |
| 枚举值        | \[stream].Enum                                                                                                   |
| 数组         | \[stream].List                                                                                                   |
| 字典         | \[stream].Map                                                                                                    |
| 二进制 Buffer | \[stream].BinBuffer                                                                                              |

为了大家更加清晰的理解该概念，我们提前描述一部分复杂类型的在 NodeJs 中的表示方法。

数据类型的详细使用方法，请参考后续的详细说明。

```javascript
var Tars = require("@tars/stream");

//c++语法：std::vector<int>
var abc = new Tars.List(Tars.Int32);
abc.push(10000);
abc.push(10001);

//c++语法：std::vector<std::vector<std::string> >
var abc = new Tars.List(Tars.List(Tars.String));
var ta = new Tars.List(Tars.String);
ta.push("ta1");
ta.push("ta2");
var tb = new Tars.List(Tars.String);
tb.push("tb1");
tb.push("tb2");
abc.push(ta);
abc.push(tb);

//c++语法：std::map<std::string, std::string>
var abc = new Tars.Map(Tars.String, Tars.String);
abc.insert("key1", "value1");
abc.insert("key2", "value2");

//c++语法：std::map<std::string, std::vector<string> >
var abc = new Tars.Map(Tars.String, Tars.List(Tars.String));
var ta = new Tars.List(Tars.String);
ta.push("ta1");
ta.push("ta2");
var tb = new Tars.List(Tars.String);
tb.push("tb1");
tb.push("tb2");
abc.insert("key_a", ta);
abc.insert("key_b", tb);

//c++语法：std::vector<char>
var abc = new Tars.BinBuffer();
abc.writeInt32(10000);
abc.writeInt32(10001);
```

## 05 - 复杂类型 - struct（结构体）的使用方法说明

```javascript
module Ext
{
    struct ExtInfo  {
        0 optional string sUserName;
        1 optional map<string, vector<byte> > data;
        2 optional map<string, map<string, vector<byte> > > cons;
    };
};
```

将上述内容保存为文件“Demo.tars”，然后使用命令“tars2node Demo.tars”生成编解码文件“Demo.js”。

“Demo.js”内容如下所示：

```javascript
var TarsStream = require("@tars/stream");

var Ext = Ext || {};
module.exports.Ext = Ext;

Ext.ExtInfo = function () {
  this.sUserName = "";
  this.data = new TarsStream.Map(TarsStream.String, TarsStream.BinBuffer);
  this.cons = new TarsStream.Map(
    TarsStream.String,
    TarsStream.Map(TarsStream.String, TarsStream.BinBuffer)
  );
};
Ext.ExtInfo._write = function (os, tag, value) {
  os.writeStruct(tag, value);
};
Ext.ExtInfo._read = function (is, tag, def) {
  return is.readStruct(tag, true, def);
};
Ext.ExtInfo._readFrom = function (is) {
  var tmp = new Ext.ExtInfo();
  tmp.sUserName = is.readString(0, false, "");
  tmp.data = is.readMap(
    1,
    false,
    TarsStream.Map(TarsStream.String, TarsStream.BinBuffer)
  );
  tmp.cons = is.readMap(
    2,
    false,
    TarsStream.Map(
      TarsStream.String,
      TarsStream.Map(TarsStream.String, TarsStream.BinBuffer)
    )
  );
  return tmp;
};
Ext.ExtInfo.prototype._writeTo = function (os) {
  os.writeString(0, this.sUserName);
  os.writeMap(1, this.data);
  os.writeMap(2, this.cons);
};
Ext.ExtInfo.prototype._equal = function (anItem) {
  return (
    anItem.sUserName === this.sUserName &&
    anItem.data === this.data &&
    anItem.cons === this.cons
  );
};
Ext.ExtInfo.prototype._genKey = function () {
  if (!this._proto_struct_name_) {
    this._proto_struct_name_ = "STRUCT" + Math.random();
  }
  return this._proto_struct_name_;
};
Ext.ExtInfo.prototype.toBinBuffer = function () {
  var os = new TarsStream.OutputStream();
  this._writeTo(os);
  return os.getBinBuffer();
};
Ext.ExtInfo.create = function (is) {
  return Ext.ExtInfo._readFrom(is);
};
```

**对“module Ext”的说明**

Ext 在 C++中就是命名空间，在 Javascript 中我们将它翻译成一个 Object，该命名空间下所有的“常量”、“枚举值”、“结构体”、“函数”都挂接在该 Object 之下。

**tars 文件中描述的结构体的表示方法**

首先，结构体翻译成一个 Object。翻译程序根据数据类型以及 tars 文件中定义的默认值，生成数据成员。除 tars 中定义的数据成员之外，根据编解码的需要，翻译程序为结构体添加了若干辅助函数。这些函数如\_writeTo，在需要将结构体序列化成数据流的地方，被编解码库调用，该函数逐个将数据成员写入数据流中。

**翻译程序默认添加的辅助函数**

| 方法          | 限制     | 描述                                                                  |
| ----------- | ------ | ------------------------------------------------------------------- |
| \_write     | 开发者不可用 | 静态函数。当结构体用作类型原型时使用。                                                 |
| \_read      | 开发者不可用 | 静态函数。当结构体用作类型原型时使用。                                                 |
| \_readFrom  | 开发者不可用 | 静态函数。从数据流中读取结构体的数据成员值，并生成一个权限的结构体示例返回。                              |
| \_writeTo   | 开发者不可用 | 成员函数。将当前结构体的数据成员写入指定的数据流中。                                          |
| \_equal     | 开发者不可用 | 成员函数。将当前结构体用作字典类型 Key 值时的比较函数。                                      |
| \_genKey    | 开发者不可用 | 成员函数。将当前结构体用作字典类型 Key 值时，内部使用该函数获得当前结构体的别名。                         |
| toBinBuffer | 开发者可用  | 成员函数。将当前结构体序列化成二进制 Buffer，返回值类型为 require("@tars/stream").BinBuffer。 |
| create      | 开发者可用  | 成员函数。从数据流中返回一个全新的结构体。                                               |

**结构体的使用示例**

我们演示结构体在三个典型场景的使用方法：

**第一种场景：** 当结构体用作 RPC 函数的参数时。

由于 rpc 框架会自动对参数进行序列化，所以我们无需关心编解码，只需要按照普通的类一样，先 new 后赋值，然后传入参数直接调用 RPC 函数即可。

假如服务端有个 RPC 如下定义：

```javascript
module TRom
{
    struct Param  {
        0 optional string sUserName;
        1 optional int iId;
    };

	interface process {
		int getUserLevel(Param userInfo, out int iLevel);
	};
};}
```

安装上述方法生成 tars 编解码文件（生成文件名称为：Protocol.js）之后，按如下方法调用对端服务：

```javascript
var Tars = require("@tars/rpc").client;
var TRom = require("./Protocol.js").TRom;

var prx = Tars.stringToProxy(
  TRom.NodeJsCommProxy,
  "TRom.NodeJsTestServer.NodeJsCommObj@tcp -h 10.12.22.13 -p 8080  -t 60000"
);

var usr = new TRom.Param();
usr.sUserName = "KevinTian";
usr.iId = 10000;

prx
  .getUserLevel(usr)
  .then(
    function (result) {
      console.log("success:", result);
    },
    function (result) {
      console.log("error:", result);
    }
  )
  .done();
```

**第二种场景：** 对端非标准 rpc 框架，接受序列化的数据流作为参数。

在这种场景下需要我们自己对结构体进行序列化。还是以上面的 tars 文件作为例子，一般的方法如下：

```javascript
//客户端安装如下方法进行打包，然后将打包后的二进制数据流发送到服务端
var Tars = require("@tars/stream");
var TRom = require("./Protocol.js").TRom;

var usr = new TRom.Param();
usr.sUserName = "KevinTian";
usr.iId = 10000;

var os = new Tars.OutputStream();
os.writeStruct(1, usr);

//打包并得到发送的二进制数据流
var toSendBuffer = os.getBinBuffer().toNodeBuffer();
```

客户端将 toSendBuffer 发送给服务端，并且服务端接受完毕之后按如下方法进行解码：

```javascript
var Tars = require("@tars/stream");
var TRom = require("./Protocol.js").TRom;

var is = new Tars.InputStream(new Tars.BinBuffer(toSendBuffer));
var usr = is.readStruct(1, true, TRom.Param);

console.log("TRom.Param.sUserName:", usr.sUserName);
console.log("TRom.Param.iId:", usr.iId);
```

**第三种场景：** 对方服务要求数据流使用 Tup 协议，并且已经约定好了各个变量的名字。我们可以按如下的方法进行编解码：

```javascript
//客户端根据约定的名字，将结构体放入Tup中
var Tars = require("@tars/stream");
var TRom = require("./Protocol.js").TRom;

var usr = new TRom.Param();
usr.sUserName = "KevinTian";
usr.iId = 10000;

var tup_encode = new Tars.Tup();
tup_encode.writeStruct("userInfo", usr);

//打包并得到发送的二进制数据流
var toSendBuffer = tup_encode.encode(true).toNodeBuffer();
```

客户端将 toSendBuffer 发送给服务端，并且服务端接受完毕之后按如下方法进行解码：

```javascript
var Tars = require("@tars/stream");
var TRom = require("./Protocol.js").TRom;

var tup_decode = new Tars.Tup();
tup_decode.decode(new Tars.BinBuffer(toSendBuffer));

var usr = tup_decode.readStruct("userInfo", TRom.Param);

console.log("TRom.Param.sUserName:", usr.sUserName);
console.log("TRom.Param.iId:", usr.iId);
```

## 06 - 复杂类型 - vector（数组）的使用方法说明

由于 Javascript 原生的 Array 不支持 tars 中的一些特殊化操作，所以我们对它进行了一次封装。开发者可按下述的代码理解：

```javascript
[stream].List = function(proto)
{
    this.proto = proto;
    this.value = new Array();
    this.push  = function (value) { this.value.push(value); }
    ......
}
```

**\[stream].List 对象属性**

| 属性     | 描述                                               |
| ------ | ------------------------------------------------ |
| value  | Js 中的 Array 数据类型。Tars.List 实际是基于该 Array 进行的上层封装。 |
| length | 返回数组中元素的数目。                                      |

**\[stream].List 对象方法**

| 方法             | 描述                                        |
| -------------- | ----------------------------------------- |
| at             | 返回数组中指定位置的元素。                             |
| push           | 向数组的末尾添加一个元素。                             |
| forEach        | 当前数组的遍历方法，具体使用方法请参考后面的示例。                 |
| toObject       | 将 List 实例转化成基本的数据对象，具体使用方法请参考后面的示例。       |
| readFromObject | 将传入的数组处理后 push 到 List 实例中，具体使用方法请参考后面的示例。 |

proto 是 Vector 的类型原型（类型原型决定了在对 Vector 编解码时采用的方法，所以声明 Vector 的时候必须传入正确的类型原型）。

**\[stream].List 的声明示例**

```javascript
var Tars = require("@tars/stream");

//例子1：声明vector<int32>
var va = new Tars.List(Tars.Int32);

//例子2：声明vector<string>
var vb = new Tars.List(Tars.String);

//例子3：声明vector<map<uint32, string> >
var vc = new Tars.List(Tars.Map(Tars.UInt32, Tars.String));

//例子4：声明vector<struct>，假设结构体名称为TRom.Param
var vd = new Tars.Vector(TRom.Param);
```

**\[stream].List 的操作示例**

```javascript
var Tars = require("@tars/stream");

var ve = new Tars.List(Tars.String);

//向数组中添加元素
ve.push("TENCENT-MIG");
ve.push("TENCENT-SNG");
ve.push("TENCENT-IEG");
ve.push("TENCENT-TEG");

//获取数组的长度
console.log("Length:", ve.length);

//获取指定位置的元素
console.log("Array[1]:", ve.at(1));

//遍历方法1：
ve.forEach(function (value, index, oArray) {
  console.log("Array[" + index + "]:", value);
});

// 遍历方法2：
for (var index = 0, len = ve.length; index < len; index++) {
  console.log("Array[" + index + "]:", ve.at(index));
}

// toObject方法和readFromObject方法的详细例子可以参照sample/list路径下的test-list-c3.js文件
var user1 = new TRom.User_t();
user1.id = 1;
user1.name = "x1";
user1.score = 1;

var user2 = new TRom.User_t();
user2.id = 2;
user2.name = "x2";
user2.score = 2;

var user3 = new TRom.User_t();
user3.id = 3;
user3.name = "x3";
user3.score = 3;

var userList1 = new Tars.List(TRom.User_t);

console.log("user1: ", user1);
console.log("user2: ", user2);

userList1.push(user1);
userList1.push(user2);

//toObject方法
console.log("userList1: ", userList1.toObject());

var userList2 = new Tars.List(TRom.User_t);
//readFromObject方法
userList2.readFromObject([user1, user2, user3]);
console.log("userList2: ", userList2.toObject());
```

## 07 - 复杂类型 - map（字典）的使用方法说明

由于 Javascript 原生的 Object 不支持 tars 中的一些特殊化操作，所以我们对它进行了一次封装。开发者可按下述的代码理解：

```javascript
[stream].Map = function(kproto, vproto) {
    var Map = function() {
        this._kproto = kproto;
        this._vproto = vproto;
        this.value   = new Object();
        this.put     = function(key, value) { this.insert(key, value); }
        ......
	}

	return new Map();
}
```

**\[stream].Map 对象属性**

| 属性    | 描述                                                     |
| ----- | ------------------------------------------------------ |
| value | Js 中的 Object 数据类型。\[stream].Map 实际是基于该 Object 进行的上层封装。 |

**\[stream].Map 方法属性**

| 方法             | 描述                                         |
| -------------- | ------------------------------------------ |
| insert         | 向字典中添加一个元素。                                |
| set            | 同 insert。                                  |
| put            | 同 insert。                                  |
| remove         | 根据指定的 key，从字典中删除对应的数值。                     |
| clear          | 清空当前字典。                                    |
| has            | 根据指定的 key，判断字典中是否包含对应的数值。                  |
| size           | 返回当前字典中元素的数目。                              |
| forEach        | 当前数组的遍历方法，具体使用方法请参考后面的示例。                  |
| toObject       | 将 Map 实例转化成基本的数据对象，具体使用方法请参考后面的示例。         |
| readFromObject | 将传入的对象处理后 insert 到 Map 实例中，具体使用方法请参考后面的示例。 |

**\[stream].Map 的声明示例**

```javascript
var Tars = require("@tars/stream");

//例子1：声明map<int32, int32>
var ma = new Tars.Map(Tars.Int32, Tars.Int32);

//例子2：声明map<uint32, string>
var mb = new Tars.Map(Tars.Int32, Tars.String);

//例子3：声明map<string, string>的方法
var mc = new Tars.Map(Tars.String, Tars.String);

//例子4：声明map<string, vector<int32> >
var md = new Tars.Map(Tars.String, Tars.List(Tars.Int32));

//例子5：声明map<string, map<int32, vector<string> > >
var me = new Tars.Map(
  Tars.String,
  Tars.Map(Tars.Int32, Tars.List(Tars.String))
);

//例子6：声明map<string, struct>的方法，假设结构体名称为TRom.Param
var mf = new Tars.map(Tars.String, TRom.Param);
```

**\[stream].Map 的操作示例**

```javascript
var Tars = require("@tars/stream");

var mc = new Tars.Map(Tars.String, Tars.String);

//向字典中添加元素
mc.insert("KEY-00", "TENCENT-MIG");
mc.insert("KEY-01", "TENCENT-IEG");
mc.insert("KEY-02", "TENCENT-TEG");
mc.insert("KEY-03", "TENCENT-SNG");

//获取字典元素大小
console.log("SIZE:", mc.size());

//判断字典中是否有指定的值
console.log("Has:", mc.has("KEY-04"));

//字典遍历
mc.forEach(function (key, value) {
  console.log("KEY:", key);
  console.log("VALUE:", value);
});

// toObject方法和readFromObject方法的详细例子可以参照sample/map路径下的test-map-c5.js文件
var user1 = new TRom.User_t();
user1.id = 1;
user1.name = "x1";
user1.score = 1;

var user2 = new TRom.User_t();
user2.id = 2;
user2.name = "x2";
user2.score = 2;

var user3 = new TRom.User_t();
user3.id = 3;
user3.name = "x3";
user3.score = 3;

var userMap1 = new Tars.Map(Tars.String, TRom.User_t);

userMap1.insert("user1", user1);
userMap1.insert("user2", user2);

//toObject方法
console.log("userMap1: ", userMap1.toObject());

var userMap2 = new Tars.Map(Tars.String, TRom.User_t);
//readFromObject方法
userMap2.readFromObject({
  user1: user1,
  user2: user2,
  user3: user3,
});
console.log("userMap2: ", userMap2.toObject());
```

**支持 MultiMap 类型**

支持 MultiMap 类型，此类型允许以一个结构体作为 Map 的 key。javascript 原生对象没有办法表示此数据类型，因此此类型没有实现普通 Map 支持的 toObject 和 readFromObject 方法。

其操作实例如下：

```javascript
//构造Map类型
var msg = new Tars.Map(Test.StatMicMsgHead, Test.StatMicMsgBody);
msg.put(StatMicMsgHead1, StatMicMsgBody1);
msg.put(StatMicMsgHead2, StatMicMsgBody2);

//tars编码
var os = new Tars.OutputStream();
os.writeMap(1, msg);

//tars解码
var data = os.getBinBuffer().toNodeBuffer();

var is = new Tars.InputStream(new Tars.BinBuffer(data));
var ta = is.readMap(
  1,
  true,
  Tars.Map(Test.StatMicMsgHead, Test.StatMicMsgBody)
);

//遍历Map结果集
ta.forEach(function (key, value) {
  console.log("KEY:", key.masterName, "VALUE.totalRspTime", value.totalRspTime);
});

//根据值去获取
var tb = ta.get(StatMicMsgHead2);
if (tb == undefined) {
  console.log("not found by name : StatMicMsgHead2");
} else {
  console.log(tb.totalRspTime);
}
```

## 08 - 复杂类型 - 二进制 Buffer 的使用方法说明

在浏览器中我们可以使用“DataView”和“ArrayBuffer”来存储和操作二进制数据。NodeJS 为了提升性能，自身提供了一个 Buffer 类。为了方便 Tars 的编解码，我们对 Buffer 类进行了一层封装。开发者可按下述的代码理解：

```javascript
[stream].BinBuffer = function (buffer) {
  this._buffer =
    buffer != undefined && buffer instanceof Buffer ? buffer : null;
  this._length =
    buffer != undefined && buffer instanceof Buffer ? buffer.length : 0;
  this._capacity = this._length;
  this._position = 0;
};
```

**\[stream].BinBuffer 对象属性**

| 属性       | 描述                                    |
| -------- | ------------------------------------- |
| length   | 获取该二进制 Buffer 的数据长度                   |
| capacity | 获取该二进制 Buffer 在不重新分配内存的情况下，可容纳数据的最大长度 |
| position | 获取或者设置当前二进制 Buffer 的访问指针              |

> length 和 capacity 的区别：

> 假如我们向 BinBuffer 中写入一个 Int32 类型的数据。写成功之后，length 和 capacity 的区别：

> 由于 BinBuffer 类在第一次分配时使用默认的 512 长度来申请内存，此时 capacity 的值为 512

> length 表示当前 Buffer 中存在真实数据的大小，此时 length 的值为 4

**\[stream].BinBuffer 方法属性**

**toNodeBuffer**

> 函数定义；\[stream].BinBuffer.toNodeBuffer()

> 函数作用：返回当前二进制 Buffer 的数据，该值为深拷贝的类型为 NodeJS.Buffer 的数据

> 输入参数：无

> 返回数据：NodeJS.Buffer 类型

**print**

> 函数定义：\[stream].BinBuffer.print()

> 函数作用：以每行 16 个字节，并 16 进制的方式打印当前的 Buffer

**writeNodeBuffer**

> 函数定义：\[stream].BinBuffer.writeNodeBuffer(srcBuffer, offset, byteLength)

> 函数作用：向二进制 Buffer 中写入 NodeJS.Buffer 类数据

> 输入参数：

| 参数         | 数据类型          | 描述                                |
| ---------- | ------------- | --------------------------------- |
| srcBuffer  | NodeJS.Buffer | 原始的 Buffer 数据                     |
| offset     | UInt32        | 表示拷贝 srcBuffer 的起始位置              |
| byteLength | UInt32        | 表示从 offset 开始，从 srcBuffer 中拷贝的数据量 |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + byteLength`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + byteLength`

**writeBinBuffer**

> 函数定义：\[stream].BinBuffer.writeBinBuffer(value)

> 函数作用：向二进制 Buffer 中写入\[stream].BinBuffer 类数据

> 输入参数：

| 参数    | 数据类型                | 描述           |
| ----- | ------------------- | ------------ |
| value | \[stream].BinBuffer | 表示二进制 Buffer |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + value.length`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + value.length`

**writeInt8**

> 函数定义：\[stream].BinBuffer.writeInt8(value)

> 函数作用：向二进制 Buffer 中写入 Int8 类数据

> 输入参数：

| 参数    | 数据类型 | 描述       |
| ----- | ---- | -------- |
| value | Int8 | 8 位的整型数据 |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + 1`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 1`

**writeInt16**

> 函数定义：\[stream].BinBuffer.writeInt16(value)

> 函数作用：向二进制 Buffer 中写入 Int16 类数据

> 输入参数：

| 参数    | 数据类型  | 描述        |
| ----- | ----- | --------- |
| value | Int16 | 16 位的整型数据 |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + 2`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 2`

> \[3]数据存储采用网络字节序

**writeInt32**

> 函数定义：\[stream].BinBuffer.writeInt32(value)

> 函数作用：向二进制 Buffer 中写入 Int32 类数据

> 输入参数：

| 参数    | 数据类型  | 描述        |
| ----- | ----- | --------- |
| value | Int32 | 32 位的整型数据 |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + 4`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 4`

> \[3]数据存储采用网络字节序

**writeInt64**

> 函数定义：\[stream].BinBuffer.writeInt64(value)

> 函数作用：向二进制 Buffer 中写入 Int64 类数据

> 输入参数：

| 参数    | 数据类型  | 描述        |
| ----- | ----- | --------- |
| value | Int64 | 64 位的整型数据 |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + 8`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 8`

> \[3]数据存储采用网络字节序

**writeUInt8**

> 函数定义：\[stream].BinBuffer.writeUInt8(value)

> 函数作用：向二进制 Buffer 中写入 UInt8 类数据

> 输入参数：

| 参数    | 数据类型  | 描述       |
| ----- | ----- | -------- |
| value | UInt8 | 8 位的整型数据 |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + 1`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 1`

**writeUInt16**

> 函数定义：\[stream].BinBuffer.writeUInt16(value)

> 函数作用：向二进制 Buffer 中写入 UInt16 类数据

> 输入参数：

| 参数    | 数据类型   | 描述        |
| ----- | ------ | --------- |
| value | UInt16 | 16 位的整型数据 |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + 2`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 2`

> \[3]数据存储采用网络字节序

**writeUInt32**

> 函数定义：\[stream].BinBuffer.writeUInt32(value)

> 函数作用：向二进制 Buffer 中写入 UInt32 类数据

> 输入参数：

| 参数    | 数据类型   | 描述        |
| ----- | ------ | --------- |
| value | UInt32 | 32 位的整型数据 |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + 4`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 4`

> \[3]数据存储采用网络字节序

**writeFloat**

> 函数定义：\[stream].BinBuffer.writeFloat(value)

> 函数作用：向二进制 Buffer 中写入 Float(32 位，单精度浮点数)类数据

> 输入参数：

| 参数    | 数据类型  | 描述          |
| ----- | ----- | ----------- |
| value | Float | 32 位的单精度浮点数 |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + 4`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 4`

> \[3]数据存储采用网络字节序

**writeDouble**

> 函数定义：\[stream].BinBuffer.writeDouble(value)

> 函数作用：向二进制 Buffer 中写入 Double(64 位，双精度浮点数)类数据

> 输入参数：

| 参数    | 数据类型   | 描述          |
| ----- | ------ | ----------- |
| value | Double | 64 位的双精度浮点数 |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + 8`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 8`

> \[3]数据存储采用网络字节序

**writeString**

> 函数定义：\[stream].BinBuffer.writeString(value)

> 函数作用：向二进制 Buffer 中写入 String(UTF8 编码)类数据

> 输入参数：

| 参数    | 数据类型   | 描述          |
| ----- | ------ | ----------- |
| value | String | UTF8 编码的字符串 |

> 函数说明：

> \[1]当前 BinBuffer 的 `length = length(原Buffer数据长度) + 字符串的字节长度`

> \[2]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 字符串的字节长度`

**readInt8**

> 函数定义：\[stream].BinBuffer.readInt8()

> 函数作用：从二进制 Buffer 中，根据当前数据指针读取一个 Int8 类型的变量

> 输入参数：无

> 函数说明：

> \[1]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 1`

**readInt16**

> 函数定义：\[stream].BinBuffer.readInt16()

> 函数作用：从二进制 Buffer 中，根据当前数据指针读取一个 Int16 类型的变量

> 输入参数：无

> 函数说明：

> \[1]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 2`

**readInt32**

> 函数定义：\[stream].BinBuffer.readInt32()

> 函数作用：从二进制 Buffer 中，根据当前数据指针读取一个 Int32 类型的变量

> 输入参数：无

> 函数说明：

> \[1]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 4`

**readInt64**

> 函数定义：\[stream].BinBuffer.readInt64()

> 函数作用：从二进制 Buffer 中，根据当前数据指针读取一个 Int64 类型的变量

> 输入参数：无

> 函数说明：

> \[1]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 8`

**readUInt8**

> 函数定义：\[stream].BinBuffer.readUInt8()

> 函数作用：从二进制 Buffer 中，根据当前数据指针读取一个 UInt8 类型的变量

> 输入参数：无

> 函数说明：

> \[1]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 1`

**readUInt16**

> 函数定义：\[stream].BinBuffer.readUInt16()

> 函数作用：从二进制 Buffer 中，根据当前数据指针读取一个 UInt16 类型的变量

> 输入参数：无

> 函数说明：

> \[1]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 2`

**readUInt32**

> 函数定义：\[stream].BinBuffer.readUInt32()

> 函数作用：从二进制 Buffer 中，根据当前数据指针读取一个 UInt32 类型的变量

> 输入参数：无

> 函数说明：

> \[1]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 4`

**readFloat**

> 函数定义：\[stream].BinBuffer.readFloat()

> 函数作用：从二进制 Buffer 中，根据当前数据指针读取一个 Float(32 位的单精度浮点数)类型的变量

> 输入参数：无

> 函数说明：

> \[1]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 4`

**readDouble**

> 函数定义：\[stream].BinBuffer.readDouble()

> 函数作用：从二进制 Buffer 中，根据当前数据指针读取一个 Double(64 位的双精度浮点数)类型的变量

> 输入参数：无

> 函数说明：

> \[1]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 8`

**readString**

> 函数定义：\[stream].BinBuffer.readString(byteLength)

> 函数作用：从二进制 Buffer 中，根据当前数据指针读取一个 String(UTF8 编码)类型的变量

> 输入参数：

| 参数         | 数据类型   | 描述       |
| ---------- | ------ | -------- |
| byteLength | UInt32 | 字符串的字节长度 |

> 函数说明：

> \[1]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 字符串的字节长度`

> \[2]后台对字符串的编码需要使用 UTF8 字符集

**readBinBuffer**

> 函数定义：\[stream].BinBuffer.readBinBuffer(byteLength)

> 函数作用：从二进制 Buffer 中，根据当前数据指针读取一个\[stream].BinBuffer 类型的变量

> 输入参数：

| 参数         | 数据类型   | 描述               |
| ---------- | ------ | ---------------- |
| byteLength | UInt32 | 二进制 Buffer 的字节长度 |

> 函数说明：

> \[1]当前 BinBuffer 的 `position = position(原Buffer的位置指针) + 二进制Buffer的字节长度`

## 09 - 编码工具 - OutputStream 的使用方法说明

**构造函数**

> 函数定义：\[stream].OutputStram()

> 函数作用：声明一个输出流对象

> 输入参数：无

> 使用示例：var os = new \[stream].OutputStream()

**getBinBuffer**

> 函数定义：var buffer = \[stream].OutputStream.getBinBuffer()

> 函数作用：调用该函数获得打包后的二进制数据流

> 输入参数：无

> 返回数据：返回打包后的二进制数据流，该返回值类型为\[stream].BinBuffer

**writeBoolean**

> 函数定义：\[stream].OutputStream.writeBoolean(tag, value)

> 函数作用：向数据流中写一个 Boolean 类型的变量

> 输入参数：

| 参数    | 数据类型    | 描述                        |
| ----- | ------- | ------------------------- |
| tag   | UInt8   | 表示该变量的数字标识，取值范围\[0, 255]  |
| value | Boolean | 表示该变量的值，取值范围{false, true} |

> 返回数据：void

**writeInt8**

> 函数定义：\[stream].OutputStream.writeInt8(tag, value)

> 函数作用：向数据流中写一个 int8 类型的变量

> 输入参数：

| 参数    | 数据类型         | 描述                       |
| ----- | ------------ | ------------------------ |
| tag   | UInt8        | 表示该变量的数字标识，取值范围\[0, 255] |
| value | int8(Number) | 表示该变量的值，取值范围\[-128, 127] |

> 返回数据：void

**writeInt16**

> 函数定义：\[stream].OutputStream.writeInt16(tag, value)

> 函数作用：向数据流中写一个 Int16 类型的变量

> 输入参数：

| 参数    | 数据类型          | 描述                           |
| ----- | ------------- | ---------------------------- |
| tag   | UInt8         | 表示该变量的数字标识，取值范围\[0, 255]     |
| value | int16(Number) | 表示该变量的值，取值范围\[-32768, 32767] |

> 返回数据：void

**writeInt32**

> 函数定义：\[stream].OutputStream.writeInt32(tag, value)

> 函数作用：向数据流中写一个 Int32 类型的变量

> 输入参数：

| 参数    | 数据类型          | 描述                                     |
| ----- | ------------- | -------------------------------------- |
| tag   | UInt8         | 表示该变量的数字标识，取值范围\[0, 255]               |
| value | int32(Number) | 表示该变量的值，取值范围\[-2147483648, 2147483647] |

> 返回数据：void

**writeInt64**

> 函数定义：\[stream].OutputStream.writeInt64(tag, value)

> 函数作用：向数据流中写一个 Int64 类型的变量

> 输入参数：

| 参数    | 数据类型          | 描述                                                       |
| ----- | ------------- | -------------------------------------------------------- |
| tag   | UInt8         | 表示该变量的数字标识，取值范围\[0, 255]                                 |
| value | int64(Number) | 表示该变量的值，取值范围\[-9223372036854775808, 9223372036854775807] |

> 返回数据：void

**writeUInt8**

> 函数定义：\[stream].OutputStream.writeUInt8(tag, value)

> 函数作用：向数据流中写一个 UInt8 类型的变量

> 输入参数：

| 参数    | 数据类型          | 描述                       |
| ----- | ------------- | ------------------------ |
| tag   | UInt8         | 表示该变量的数字标识，取值范围\[0, 255] |
| value | UInt8(Number) | 表示该变量的值，取值范围\[0, 255]    |

> 返回数据：void

**writeUInt16**

> 函数定义：\[stream].OutputStream.writeUInt16(tag, value)

> 函数作用：向数据流中写一个 UInt16 类型的变量

> 输入参数：

| 参数    | 数据类型           | 描述                       |
| ----- | -------------- | ------------------------ |
| tag   | UInt8          | 表示该变量的数字标识，取值范围\[0, 255] |
| value | UInt16(Number) | 表示该变量的值，取值范围\[0, 65535]  |

> 返回数据：void

**writeUInt32**

> 函数定义：\[stream].OutputStream.writeUInt32(tag, value)

> 函数作用：向数据流中写一个 UInt32 类型的变量

> 输入参数：

| 参数    | 数据类型           | 描述                           |
| ----- | -------------- | ---------------------------- |
| tag   | UInt8          | 表示该变量的数字标识，取值范围\[0, 255]     |
| value | UInt32(Number) | 表示该变量的值，取值范围\[0, 4294967295] |

> 返回数据：void

**writeFloat**

> 函数定义：\[stream].OutputStream.writeFloat(tag, value)

> 函数作用：向数据流中写一个 float(32 位)类型的变量

> 输入参数：

| 参数    | 数据类型          | 描述                        |
| ----- | ------------- | ------------------------- |
| tag   | UInt8         | 表示该变量的数字标识，取值范围\[0, 255]  |
| value | Float(Number) | 单精度浮点数，因为有精度损失问题，不推荐使用该类型 |

> 返回数据：void

**writeDouble**

> 函数定义：\[stream].OutputStream.writeDouble(tag, value)

> 函数作用：向数据流中写一个 double(64 位)类型的变量

> 输入参数：

| 参数    | 数据类型           | 描述                        |
| ----- | -------------- | ------------------------- |
| tag   | UInt8          | 表示该变量的数字标识，取值范围\[0, 255]  |
| value | Double(Number) | 双精度浮点数，因为有精度损失问题，不推荐使用该类型 |

> 返回数据：void

**writeString**

> 函数定义：\[stream].OutputStream.writeString(tag, value)

> 函数作用：向数据流中写一个 String 类型的变量

> 输入参数：

| 参数    | 数据类型   | 描述                       |
| ----- | ------ | ------------------------ |
| tag   | UInt8  | 表示该变量的数字标识，取值范围\[0, 255] |
| value | String | 表示该变量的值，字符串编码字符集为 UTF8   |

> 返回数据：void

**writeStruct**

> 函数定义：writeStruct(tag, value)

> 函数作用：向数据流中写一个自定义结构体的变量

> 输入参数：

| 参数    | 数据类型   | 描述                                            |
| ----- | ------ | --------------------------------------------- |
| tag   | UInt8  | 表示该变量的数字标识，取值范围\[0, 255]                      |
| value | 自定义结构体 | 结构体必须是使用 tars2node 转换而成的，否则可能会因缺少辅助函数而导致编解码失败 |

> 返回数据：void

**writeBytes**

> 函数定义：\[stream].OutputStream.writeBytes(tag, value)

> 函数作用：向数据流中写一个类型为 `char *` 或者 `vector<char>` 的变量

> 输入参数：

| 参数    | 数据类型                | 描述                                                   |
| ----- | ------------------- | ---------------------------------------------------- |
| tag   | UInt8               | 表示该变量的数字标识，取值范围\[0, 255]                             |
| value | \[stream].BinBuffer | BinBuffer 是对 NodeJs 中的 Buffer 类的封装，同时集成了编解码需要用到的辅助函数 |

> 返回数据：void

**writeList**

> 函数定义：\[stream].OutputStream.writeList(tag, value)

> 函数作用：向数据流中写一个类型为 `vector<T>`（T 不可为 byte）的变量

> 函数参数：

| 参数    | 数据类型              | 描述                       |
| ----- | ----------------- | ------------------------ |
| tag   | UInt8             | 表示该变量的数字标识，取值范围\[0, 255] |
| value | \[stream].List(T) | 该变量的类型原型                 |

> 返回数据：void

**writeMap**

> 函数定义：\[stream].OutputStream.writeMap(tag, value)

> 函数作用：向数据流中写一个类型为 `map<T, V>` 类型的字段。

> 函数参数：

| 参数    | 数据类型                | 描述                       |
| ----- | ------------------- | ------------------------ |
| tag   | UInt8               | 表示该变量的数字标识，取值范围\[0, 255] |
| value | \[stream].Map(T, V) | 该变量的类型原型                 |

> 返回数据：void

## 10 - 解码工具 - InputStream 的使用方法说明

**构造函数**

> 函数定义：\[stream].InputStream(binBuffer)

> 函数作用：声明一个输入流对象

> 输入参数：

> binBuffer 欲解码的二进制数据流，该值类型必须为\[stream].BinBuffer，而不能是 NodeJs 中实现的 Buffer 类。

> 使用示例：var is = new \[stream].InputStream(new \[stream].BinBuffer(Node.Buffer))

**readBoolean**

> 函数定义：var value = \[stream].InputStream.readBoolean(tag, require, default)

> 函数作用：从数据流读取一个 Boolean 类型的数值

> 输入参数：

| 参数      | 数据类型    | 描述                               |
| ------- | ------- | -------------------------------- |
| tag     | UInt8   | 表示欲读取变量的数字标识，取值范围\[0, 255]       |
| require | Boolean | 表示当前变量是否为必须值，取值范围{false, true}   |
| default | Boolean | 表示读取变量不成功时的返回值，取值范围{false, true} |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回变量的默认值 default；

> 返回数据：Boolean，取值范围{false, true}

**readInt8**

> 函数定义：\[stream].InputStream.readInt8(tag, require, default)

> 函数作用：从数据流读取一个 Int8 类型的数值

> 输入参数：

| 参数      | 数据类型    | 描述                              |
| ------- | ------- | ------------------------------- |
| tag     | UInt8   | 表示欲读取变量的数字标识，取值范围\[0, 255]      |
| require | Boolean | 表示当前变量是否为必须值，取值范围{false, true}  |
| default | Int8    | 表示读取变量不成功时的返回值，取值范围\[-128, 127] |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回变量的默认值 default；

> 返回数据：Int8，取值范围\[-128, 127]

**readInt16**

> 函数定义：\[stream].InputStream.readInt16(tag, require, default)

> 函数作用：从数据流读取一个 Int16 类型的数值

> 输入参数：

| 参数      | 数据类型    | 描述                                  |
| ------- | ------- | ----------------------------------- |
| tag     | UInt8   | 表示欲读取变量的数字标识，取值范围\[0, 255]          |
| require | Boolean | 表示当前变量是否为必须值，取值范围{false, true}      |
| default | Int16   | 表示读取变量不成功时的返回值，取值范围\[-32768, 32767] |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回变量的默认值 default；

> 返回数据：Int16，取值范围\[-32768, 32767]

**readInt32**

> 函数定义：\[stream].InputStream.readInt32(tag, require, default)

> 函数作用：从数据流读取一个 Int32 类型的数值

> 输入参数：

| 参数      | 数据类型    | 描述                                            |
| ------- | ------- | --------------------------------------------- |
| tag     | UInt8   | 表示欲读取变量的数字标识，取值范围\[0, 255]                    |
| require | Boolean | 表示当前变量是否为必须值，取值范围{false, true}                |
| default | Int32   | 表示读取变量不成功时的返回值，取值范围\[-2147483648, 2147483647] |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回变量的默认值 default；

> 返回数据：Int32，取值范围\[-2147483648, 2147483647]

**readInt64**

> 函数定义：\[stream].InputStream.readInt64(tag, require, default)

> 函数作用：从数据流读取一个 Int64 类型的数值

> 输入参数：

| 参数      | 数据类型    | 描述                                                              |
| ------- | ------- | --------------------------------------------------------------- |
| tag     | UInt8   | 表示欲读取变量的数字标识，取值范围\[0, 255]                                      |
| require | Boolean | 表示当前变量是否为必须值，取值范围{false, true}                                  |
| default | Int64   | 表示读取变量不成功时的返回值，取值范围\[-9223372036854775808, 9223372036854775807] |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回变量的默认值 default；

> 返回数据：Int64(Number)，取值范围\[-9223372036854775808, 9223372036854775807]

**readUInt8**

> 函数定义：\[stream].InputStream.readUInt8(tag, require, default)

> 函数作用：从数据流读取一个 UInt8 类型的数值

> 输入参数：

| 参数      | 数据类型    | 描述                             |
| ------- | ------- | ------------------------------ |
| tag     | UInt8   | 表示欲读取变量的数字标识，取值范围\[0, 255]     |
| require | Boolean | 表示当前变量是否为必须值，取值范围{false, true} |
| default | UInt8   | 表示读取变量不成功时的返回值，取值范围\[0, 255]   |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回变量的默认值 default；

> 返回数据：UInt8(Number)，取值范围\[0, 255]

**readUInt16**

> 函数定义：\[stream].InputStream.readUInt16(tag, require, default)

> 函数作用：从数据流读取一个 UInt16 类型的数值

> 输入参数：

| 参数      | 数据类型    | 描述                             |
| ------- | ------- | ------------------------------ |
| tag     | UInt8   | 表示欲读取变量的数字标识，取值范围\[0, 255]     |
| require | Boolean | 表示当前变量是否为必须值，取值范围{false, true} |
| default | UInt8   | 表示读取变量不成功时的返回值，取值范围\[0, 65535] |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回变量的默认值 default；

> 返回数据：UInt16(Number)，取值范围\[0, 65535]

**readUInt32**

> 函数定义：\[stream].InputStream.readUInt32(tag, require, default)

> 函数作用：从数据流读取一个 UInt32 类型的数值

> 输入参数：

| 参数      | 数据类型    | 描述                                  |
| ------- | ------- | ----------------------------------- |
| tag     | UInt8   | 表示欲读取变量的数字标识，取值范围\[0, 255]          |
| require | Boolean | 表示当前变量是否为必须值，取值范围{false, true}      |
| default | UInt8   | 表示读取变量不成功时的返回值，取值范围\[0, 4294967295] |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回变量的默认值 default；

> 返回数据：UInt32(Number)，取值范围\[0, 4294967295]

**readFloat**

> 函数定义：\[stream].InputStream.readFloat(tag, require, default)

> 函数作用：从数据流读取一个 Float（32 位，单精度浮点数）类型的数值

> 输入参数：

| 参数      | 数据类型    | 描述                             |
| ------- | ------- | ------------------------------ |
| tag     | UInt8   | 表示欲读取变量的数字标识，取值范围\[0, 255]     |
| require | Boolean | 表示当前变量是否为必须值，取值范围{false, true} |
| default | Float   | 表示读取变量不成功时的返回值                 |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回变量的默认值 default；

> 返回数据：Float(Number)

**readDouble**

> 函数定义：\[stream].InputStream.readFloat(tag, require, default)

> 函数作用：从数据流读取一个 Double（64 位，双精度浮点数）类型的数值

> 输入参数：

| 参数      | 数据类型    | 描述                             |
| ------- | ------- | ------------------------------ |
| tag     | UInt8   | 表示欲读取变量的数字标识，取值范围\[0, 255]     |
| require | Boolean | 表示当前变量是否为必须值，取值范围{false, true} |
| default | Double  | 表示读取变量不成功时的返回值                 |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回变量的默认值 default；

> 返回数据：Double(Number)

**readString**

> 函数定义：\[stream].InputStream.readString(tag, require, default)

> 函数作用：从数据流读取一个 String（UTF8 编码）类型的数值

> 输入参数：

| 参数      | 数据类型    | 描述                             |
| ------- | ------- | ------------------------------ |
| tag     | UInt8   | 表示欲读取变量的数字标识，取值范围\[0, 255]     |
| require | Boolean | 表示当前变量是否为必须值，取值范围{false, true} |
| default | String  | 表示读取变量不成功时的返回值                 |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回变量的默认值 default；

> 返回数据：String（UTF8 编码）

**readStruct**

> 函数定义：\[stream].InputStream.readStruct(tag, require, TYPE\_T)

> 函数作用：从数据流读取一个自定义结构体类型的数值

> 输入参数：

| 参数      | 数据类型        | 描述                             |
| ------- | ----------- | ------------------------------ |
| tag     | UInt8       | 表示欲读取变量的数字标识，取值范围\[0, 255]     |
| require | Boolean     | 表示当前变量是否为必须值，取值范围{false, true} |
| TYPE\_T | 自定义结构体的类型原型 | 表示该变量的类型原型                     |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回一个空的结构体的实例；

> 返回数据：自定义结构体的实例

**readBytes**

> 函数定义：\[stream].InputStream.readBytes(tag, require, TYPE\_T)

> 函数作用：从数据流读取一个 `[stream].BinBuffer` 类型的数值

> 输入参数：

| 参数      | 数据类型                | 描述                             |
| ------- | ------------------- | ------------------------------ |
| tag     | UInt8               | 表示欲读取变量的数字标识，取值范围\[0, 255]     |
| require | Boolean             | 表示当前变量是否为必须值，取值范围{false, true} |
| TYPE\_T | \[stream].BinBuffer | 表示该变量的类型原型                     |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回一个空的\[stream].BinBuffer 的实例；

> 返回数据：\[stream].BinBuffer

**readList**

> 函数定义：\[stream].InputStream.readList(tag, require, TYPE\_T)

> 函数作用：从数据流读取一个 `[stream].List<T>` 类型的数值

> 输入参数：

| 参数      | 数据类型           | 描述                             |
| ------- | -------------- | ------------------------------ |
| tag     | UInt8          | 表示欲读取变量的数字标识，取值范围\[0, 255]     |
| require | Boolean        | 表示当前变量是否为必须值，取值范围{false, true} |
| TYPE\_T | \[stream].List | 表示该变量的类型原型                     |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回一个空的\[stream].List(T)的实例；

> 返回数据：\[stream].List(T)

**readMap**

> 函数定义：\[stream].InputStream.readMap(tag, require, TYPE\_T)

> 函数作用：从数据流读取一个 `[stream].Map<T, V>` 类型的数值

> 输入参数：

| 参数      | 数据类型                | 描述                             |
| ------- | ------------------- | ------------------------------ |
| tag     | UInt8               | 表示欲读取变量的数字标识，取值范围\[0, 255]     |
| require | Boolean             | 表示当前变量是否为必须值，取值范围{false, true} |
| TYPE\_T | \[stream].Map(T, V) | 表示该变量的类型原型                     |

> > 对 require 的说明：

> > 当 `require === true` 时， 如果当前变量不在数据流中，系统将抛出一个读取数据不存在的异常；

> > 当 `require === false` 时，如果当前变量不在数据流中，系统将返回一个空的\[stream].Map(T, V)的实例；

> 返回数据：\[stream].Map(T, V)
