@by Ruth92(转载请注明出处)

第6章 理解 Buffer

✁ 为什么需要 Buffer

在 Node 中,应用需要处理网络协议、操作数据库、处理图片、接收上传文件等,在网络流和文件的操作中,还要处理大量二进制数据,JavaScript 自由的字符串远远不能满足这些需求,于是 Buffer 对象应运而生。

✁ 字符串与 Buffer 的区别

Buffer 是二进制数据,字符串与 Buffer 之间存在编码关系。

一、Buffer 结构

Buffer 是一个像 Array 的对象,但它主要用于操作字节。

  1. 模块结构

    Buffer 所占用的内存不是通过 V8 分配的,属于堆外内存。

    由于 Buffer 太过常见,Node 在进程启动时就已经加载了它,并将其放在全局对象(global)上。所以在使用 Buffer 时,无需通过 require() 即可直接使用。

  2. Buffer 对象

    Buffer 对象类似于数组,它的元素为16进制的两位数,即 0-255 的数值。

     var str = '深入浅出Node.js';
    var buf = new Buffer(str, 'utf-8'); // 不同的编码的字符串占用的元素个数各不相同
    // 中文字在 UTF-8 编码下占用3个元素,字母和半标点符号占用1个元素
    buf;
    // => <Buffer e6 b7 b1 e5 85 a5 e6 b5 85 e5 87 ba 6e 6f 64 65 2e 6a 73> // Buffer 受 Array 类型的影响很大,
    // 可以访问 length 属性得到长度,也可以通过下标访问元素
    buf.length; // => 19
    buf[10]; // => 135 var newBuf = new Buffer(100);
    newBuf.length; // => 100
    newBuf[10]; // => 88 (元素值是一个 0-255 的随机值) // 如果给元素赋值不是0-255的数时:
    // 给元素的赋值如果小于0,就讲该值逐次加256,直到得到0-255区间内的整数
    newBuf[20] = -100;
    newBuf[20]; // => 156 // 给元素的赋值如果大于0,就逐次减256,直到得到0-255区间内的整数
    newBuf[21] = 300;
    newBuf[21]; // => 44 // 如果是小数,舍弃小数部分,只保留整数部分
    newBuf[22] = 3.1415;
    newBuf[22]; // 3
  3. Buffer 内存分配

    Buffer 对象的内存分配不是在 V8 的堆内存中,而是在 Node 的 C++ 层面实现内存的申请的。

    Node 在内存的使用上应用的是在 C++ 层面申请内存、在 JavaScript 中分配内存的策略。

    【小结】:

    • 真正的内存是在 Node 的 C++ 层面提供的,JavaScript 层面只是使用它。
    • 当进行小而频繁的 Buffer 操作时,采用 slab 的机制进行预先申请和事后分配,使得 JavaScript 到操作系统之间不必有过多的内存申请方面的系统调用。
    • 对于大块的 Buffer 而言,则直接使用 C++ 层面提供的内存,而无需细腻的分配操作。

二、Buffer 的转换

Buffer 对象可以与字符串之间相互转换。目前支持的字符串编码类型有:ASCII、UTF-8、UTF-16LE/UCS-2、Base64、Binary、Hex。

  1. 字符串转 Buffer

    字符串转 Buffer 对象主要是通过 构造函数 完成的。

     new Buffer(str, [encoding]);

    通过构造函数转换的 Buffer 对象,存储的只能是一种编码类型。encoding 参数不传递时,默认按 UTF-8 编码进行转码和存储。

    一个 Buffer 对象可以存储不同编码类型的字符串转码的值,调用 write() 方法可以实现。

     buf.write(string, [offset], [length], [encoding]);

    由于可以不断写入内容到 Buffer 对象中,并且每次写入可以指定编码,所以 Buffer 对象中可以存在多种编码转化后的内容。需要小心的是,每种编码所用的字节长度不同,将 Buffer 反转回字符串时需要谨慎处理。

  2. Buffer 转字符串

    Buffer 对象的 toString() 可以将 Buffer 对象转换为字符串。

     buf.toString([encoding], [start], [end]);
  3. Buffer 不支持的编码类型

    isEncoding() 方法:判断编码是否支持转换

     Buffer.isEncoding(encoding);
    
     Buffer.isEncoding('utf-8');	// true
    Buffer.isEncoding('GBK'); // false

    对于不支持的编码类型,可以借助 Node 生态圈中的模块完成转换。iconviconv-lite 两个模块可以支持更多的编码类型转换。

三、Buffer 的拼接

Buffer 在使用场景中,通常是以一段一段的方式传输。

var fs = require('fs');
var rs = fs.createReadStream('test.md');
var data = ''; rs.on('data', function(chunk) {
// data 事件中获取的 chunk 对象即是 Buffer 对象
// 隐藏了 toString() 操作
// 等价于:data = data.toString() + chunk.toString();
data += chunk;
}); rs.on('end', function() {
console.log(data);
});
  1. 乱码是如何产生的

    toString() 默认以 UTF-8 编码,中文在该编码方式下占3个字节,因此,对于宽字节的中文,会形成问题。

     // 将文件可读流的每次读取的 Buffer 长度限制为 11
    var rs = fs.createReadStream('test.md', {highWaterMark: 11});
    // => 窗前明◆◆◆光,疑◆◆◆地上霜

    ✎ 第一个 Buffer 对象在输出时,只能显示3个字符,Buffer 中剩下的2个字节将会以乱码的形式显示。第二个Buffer对象的第一个字节也不能形成文字,只能显示乱码。

    ☁ 对于任意长度的 Buffer 而言,宽字节字符串都有可能存在被截断的情况,只不过 Buffer 的长度越大出现的概率越低而已。

  2. setEncoding() 与 string_decoder() ☛ 不能从根本上解决乱码问题

    setEncoding 方法:设置编码

    【作用】:让 data 事件中传递的不再是一个 Buffer 对象,而是编码后的字符串。

     readable.setEncoding(encoding);
    
     var rs = fs.createReadStream('test.md', {highWaterMark: 11});
    rs.setEncoding('utf8');
    // => 窗前明月光,疑是地上霜

    如论如何设置编码,触发 data 事件的次数依旧相同,即意味着设置编码并未改变按段读取的基本方式。

    在调用 setEncoding() 时,可读流对象在内部设置了一个 decoder 对象。

    decoder 对象:来自于 string_decoder 模块 StringDecoder 的实例对象,最终解决乱码问题。

     // decoder 的神奇原理:
    var StringDecoder = require('string_decoder').StringDecoder;
    var decoder = new StringDecoder('utf8'); var buf1 = new Buffer([0xE5, 0xBA, 0x8A, 0xE5, 0x89, 0x8D, 0xE6, 0x98, 0x8E, 0xE6, 0x9C]);
    console.log(decoder.write(buf1)); // => 床前明 var buf2 = new Buffer([0x88, 0xE5, 0x85, 0x89, 0xEF, 0xBC, 0x8C, 0xE7, 0x96, 0x91, 0xE6]);
    console.log(decoder.write(buf2)); // => 月光,疑

    虽然 string_decoder 模块很奇妙,但是它也并非万能药,它目前只能处理 UTF8、Base64 和 UCS-2/UTF-16LE 这3种编码。所以,通过 setEncoding() 的方式不可否认能解决大部分的乱码问题,但并不能从根本上解决该问题。

  3. 正确拼接 Buffer

    ① 用一个数组来存储接收到的所有 Buffer 片段并记录下所有片段的总长度;

    ② 调用 Buffer.concat() 方法生成一个合并的 Buffer 对象。

     var chunks = [];
    var size = 0; rs.on('data', function(chunk) {
    chunks.push(chunk);
    size += chunk.length;
    }); rs.on('end', function() {
    var buf = Buffer.concat(chunks, size);
    var str = iconv.decode(buf, 'utf8');
    console.log(str);
    });

    Buffer.concat() 方法封装了从小 Buffer 对象向大 Buffer 对象的复制过程:

     Buffer.concat = function(list, length) {
    if (!Array.isArray(list)) {
    throw new Error('Usage: Buffer.concat(list, [length]');
    } if (list.length === 0) {
    return new Buffer(0);
    } else if (list.length === 1) {
    return list[0];
    } if (typeof length !== 'number') {
    length = 0;
    for (var i = 0; i < list.length; i++) {
    var buf = list[i];
    length += buf.length;
    }
    } var buffer = new Buffer(length);
    var pos = 0;
    for (var i = 0; i < list.length; i++) {
    var buf = list[i];
    buf.copy(buffer, pos);
    pos += buf.length;
    }
    return buffer;
    }

四、Buffer 与性能

  • Buffer 在文件 I/O 和网络 I/O 中运用广泛。

    • 在应用中:操作字符串;

    • 在网络中传输:需要转换为 Buffer,以进行二进制数据传输。

  • 在 Web 应用中,字符串转换到 Buffer 是时时刻刻发生的,提高字符串到 Buffer 的转换效率,可以很大程度地提高网络吞吐率。

  • Buffer 的使用除了与字符串的转换有性能损耗外,在文件的读取时,有一个 highWaterMark 设置对性能的影响至关重要。

    • highWaterMark 设置对 Buffer 内存的分配和使用有一定影响;
    • highWaterMark 设置过小,可能导致系统调用次数过多;
    • highWaterMark 的值越大,读取速度越快。

《深入浅出Node.js》第6章 理解 Buffer的更多相关文章

  1. 《深入浅出Node.js》第7章 网络编程

    @by Ruth92(转载请注明出处) 第7章 网络编程 Node 只需要几行代码即可构建服务器,无需额外的容器. Node 提供了以下4个模块(适用于服务器端和客户端): net -> TCP ...

  2. 一个月时间整理《深入浅出Node.js》

    今天终于把朴灵老师写的<深入浅出Node.js>给学习完了, 这本书不是一本简单的Node入门书籍,它没有停留在Node介绍或者框架.库的使用层面上,而是从不同的视角来揭示Node自己内在 ...

  3. 《深入浅出node.js(朴灵)》【PDF】下载

    <深入浅出node.js(朴灵)>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062563 内容简介 <深入浅出Node. ...

  4. 深入浅出Node.js(一):什么是Node.js

    Node.js从2009年诞生至今,已经发展了两年有余,其成长的速度有目共睹.从在github的访问量超过Rails,到去年底Node.jsS创始人Ryan Dalh加盟Joyent获得企业资助,再到 ...

  5. 深入浅出Node.js(一):什么是Node.js(转贴)

    以下内容转自:http://www.infoq.com/cn/articles/what-is-nodejs/ 作者:崔康 [编者按]:Node.js从2009年诞生至今,已经发展了两年有余,其成长的 ...

  6. 深入浅出Node.js(上)

    (一):什么是Node.js Node.js从2009年诞生至今,已经发展了两年有余,其成长的速度有目共睹.从在github的访问量超过Rails,到去年底Node.jsS创始人Ryan Dalh加盟 ...

  7. 读书笔记: 深入浅出node.js

    >> 深入浅出node.js node.js是c++编写的js运行环境 浏览器: 渲染引擎 + js引擎 后端的js运行环境 node.js用google v8引擎,同时提供很多系统级的A ...

  8. 深入浅出node.js游戏服务器开发1——基础架构与框架介绍

    2013年04月19日 14:09:37 MJiao 阅读数:4614   深入浅出node.js游戏服务器开发1——基础架构与框架介绍   游戏服务器概述 没开发过游戏的人会觉得游戏服务器是很神秘的 ...

  9. Node.js Event Loop 的理解 Timers,process.nextTick()

    写这篇文章的目的是将自己对该文章的理解做一个记录,官方文档链接The Node.js Event Loop, Timers, and process.nextTick() 文章内容可能有错误理解的地方 ...

随机推荐

  1. solr全文检索原理及solr5.5.0 Windows部署

    文章原理链接:http://blog.csdn.net/xiaoyu411502/article/details/44803859 自己稍微总结:全文检索主要有两个过程:创建索引,搜索索引 创建索引: ...

  2. 临界区 TRTLCriticalSection 和 TCriticalSection

    临界区对象TCriticalSection(Delphi) 与 TRtlCriticalSection 的区别 TRtlCriticalSection 是一个结构体,在windows单元中定义: 是I ...

  3. 学习笔记 android fragment

    最近研究了一下fragment的使用方法,总结概要如下: 1.fragment实际上就是把activity拆开后的封装块,一个fragment就是一个封装好的一部分.这样以来可以方便复用. 2.fra ...

  4. .NET程序的简单编译原理

    1.不管是什么程序,最终的执行官是CPU,而CPU只认识1和0的机器码. 2.我们现在写的一般是高级语言写的程序.CPU是不认识我们用高级语言写的源代码的,那应该怎么办才能让CPU执行我们写好的程序尼 ...

  5. 不经过 App store 的安装方式(转)

    所有安装到真机(非越狱)的应用(可以是 .app ,也可以是 .ipa ,只要编译时选的是编译成 Arm 的就好..app 转 .ipa 只需要一条命令) 都必须经过证书签名.证书主要有三大种: 企业 ...

  6. Github——入门笔记

    一般操作步骤 developer:(你在别人基础上开发) init->clone->add->commit->remote add(origin的地址)->push or ...

  7. Bootstrap 模态框在用户点击背景空白处时会自动关闭

    问题: Bootstrap 模态框在用户点击背景空白处时,会自动关闭. 解决方法: 在HTML页面中编写模态框时,在div初始化时添加属性 aria-hidden=”true” data-backdr ...

  8. 帝国cms怎么调用栏目的别名呢?

    在世界买家网新模板制作过程中,由于栏目名称比较长,用在标题上没有问题,对seo有利,但是在页面上不希望这么长,简单即可,提过提供了栏目别名,如果能调用就方便了, 请留意下面的修改方法 修改后栏目别名使 ...

  9. 如何用vs2010打开vs2013的项目?

    众所周知,用vs2013打开vs2010十分简单,无须做什么. 从VS2010开始,不再制作专有的文件格式,这只是一个xml格式的文本文件,其中决定了解决方案的平台工具集和VS版本. 既然.sln只是 ...

  10. Outlook不能预览和打开Excel文件:

    无法打开Outlook邮箱中的Excel附件,确实让人恼火 先不要着急: 1.在开始->运行,输入"regedit" 2.找到路径:HKEY_CURRENT_USER\Sof ...