# @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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tarscloud.gitbook.io/tarsdocs/tars.js/tars-stream.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
