《深入浅出Node.js》第6章 理解 Buffer
@by Ruth92(转载请注明出处)
第6章 理解 Buffer
✁ 为什么需要 Buffer
?
在 Node 中,应用需要处理网络协议、操作数据库、处理图片、接收上传文件等,在网络流和文件的操作中,还要处理大量二进制数据,JavaScript 自由的字符串远远不能满足这些需求,于是 Buffer
对象应运而生。
✁ 字符串与 Buffer
的区别
Buffer
是二进制数据,字符串与 Buffer
之间存在编码关系。
一、Buffer 结构
Buffer
是一个像 Array
的对象,但它主要用于操作字节。
模块结构
Buffer
所占用的内存不是通过 V8 分配的,属于堆外内存。由于
Buffer
太过常见,Node 在进程启动时就已经加载了它,并将其放在全局对象(global
)上。所以在使用Buffer
时,无需通过require()
即可直接使用。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
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。
字符串转 Buffer
字符串转
Buffer
对象主要是通过 构造函数 完成的。new Buffer(str, [encoding]);
通过构造函数转换的
Buffer
对象,存储的只能是一种编码类型。encoding
参数不传递时,默认按 UTF-8 编码进行转码和存储。一个
Buffer
对象可以存储不同编码类型的字符串转码的值,调用write()
方法可以实现。buf.write(string, [offset], [length], [encoding]);
由于可以不断写入内容到
Buffer
对象中,并且每次写入可以指定编码,所以Buffer
对象中可以存在多种编码转化后的内容。需要小心的是,每种编码所用的字节长度不同,将Buffer
反转回字符串时需要谨慎处理。Buffer 转字符串
Buffer
对象的toString()
可以将Buffer
对象转换为字符串。buf.toString([encoding], [start], [end]);
Buffer 不支持的编码类型
isEncoding()
方法:判断编码是否支持转换Buffer.isEncoding(encoding); Buffer.isEncoding('utf-8'); // true
Buffer.isEncoding('GBK'); // false
对于不支持的编码类型,可以借助 Node 生态圈中的模块完成转换。iconv 和 iconv-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);
});
乱码是如何产生的
toString()
默认以 UTF-8 编码,中文在该编码方式下占3个字节,因此,对于宽字节的中文,会形成问题。// 将文件可读流的每次读取的 Buffer 长度限制为 11
var rs = fs.createReadStream('test.md', {highWaterMark: 11});
// => 窗前明◆◆◆光,疑◆◆◆地上霜
✎ 第一个 Buffer 对象在输出时,只能显示3个字符,Buffer 中剩下的2个字节将会以乱码的形式显示。第二个Buffer对象的第一个字节也不能形成文字,只能显示乱码。
☁ 对于任意长度的
Buffer
而言,宽字节字符串都有可能存在被截断的情况,只不过Buffer
的长度越大出现的概率越低而已。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()
的方式不可否认能解决大部分的乱码问题,但并不能从根本上解决该问题。正确拼接 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 的值越大,读取速度越快。
- highWaterMark 设置对
《深入浅出Node.js》第6章 理解 Buffer的更多相关文章
- 《深入浅出Node.js》第7章 网络编程
@by Ruth92(转载请注明出处) 第7章 网络编程 Node 只需要几行代码即可构建服务器,无需额外的容器. Node 提供了以下4个模块(适用于服务器端和客户端): net -> TCP ...
- 一个月时间整理《深入浅出Node.js》
今天终于把朴灵老师写的<深入浅出Node.js>给学习完了, 这本书不是一本简单的Node入门书籍,它没有停留在Node介绍或者框架.库的使用层面上,而是从不同的视角来揭示Node自己内在 ...
- 《深入浅出node.js(朴灵)》【PDF】下载
<深入浅出node.js(朴灵)>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062563 内容简介 <深入浅出Node. ...
- 深入浅出Node.js(一):什么是Node.js
Node.js从2009年诞生至今,已经发展了两年有余,其成长的速度有目共睹.从在github的访问量超过Rails,到去年底Node.jsS创始人Ryan Dalh加盟Joyent获得企业资助,再到 ...
- 深入浅出Node.js(一):什么是Node.js(转贴)
以下内容转自:http://www.infoq.com/cn/articles/what-is-nodejs/ 作者:崔康 [编者按]:Node.js从2009年诞生至今,已经发展了两年有余,其成长的 ...
- 深入浅出Node.js(上)
(一):什么是Node.js Node.js从2009年诞生至今,已经发展了两年有余,其成长的速度有目共睹.从在github的访问量超过Rails,到去年底Node.jsS创始人Ryan Dalh加盟 ...
- 读书笔记: 深入浅出node.js
>> 深入浅出node.js node.js是c++编写的js运行环境 浏览器: 渲染引擎 + js引擎 后端的js运行环境 node.js用google v8引擎,同时提供很多系统级的A ...
- 深入浅出node.js游戏服务器开发1——基础架构与框架介绍
2013年04月19日 14:09:37 MJiao 阅读数:4614 深入浅出node.js游戏服务器开发1——基础架构与框架介绍 游戏服务器概述 没开发过游戏的人会觉得游戏服务器是很神秘的 ...
- Node.js Event Loop 的理解 Timers,process.nextTick()
写这篇文章的目的是将自己对该文章的理解做一个记录,官方文档链接The Node.js Event Loop, Timers, and process.nextTick() 文章内容可能有错误理解的地方 ...
随机推荐
- C语言程序设计入门学习五步曲(转发)
笔者在从事教学的过程中,听到同学抱怨最多的一句话是:老师,上课我也能听懂,书上的例题也能看明白,可是到自己动手做编程时,却不知道如何下手.发生这种现象的原因有三个: 一.所谓的看懂听明白,只是很肤浅的 ...
- 【MCU】【STM32】1.cube MX库使用笔记
STM32Cube 是一个全面的软件平台,包括了ST产品的每个系列.(如,STM32CubeF4 是针对STM32F4系列). 平台包括了STM32Cube 硬件抽象层和一套的中间件组件(RTOS, ...
- iOS开发_内存泄漏、内存溢出和野指针之间的区别
今天,在工作群中,被问到了内存泄漏和野指针指向的区别,自己答的不是很好,特意回来查了资料,在博文中总结一下经验,欢迎指正. 内存泄漏:是指在堆区,alloc 或new 创建了一个对象,但是并没有放到自 ...
- iOS系统网络抓包方法
转到自己的博客收藏. 1. 网络共享 + 可视化抓包工具 基本原理 原理比较简单,ios设备通过代理方式共享连接mac电脑的无线网卡,使用抓包工具抓包,然后进行分析(我们推荐使用Wireshark,在 ...
- SEO学习笔记-误区和经验总结
原文链接:http://www.cnblogs.com/monxue/p/seo_note.html 常见误区和错误: 1.忽视404错误页面的优化,没有及时处理死链导致权重降低 2.做外链优化只链到 ...
- Android studio打开之后 cannot load project: java.lang.NUllpointerException
参考来源:http://bbs.csdn.net/topics/391014393 关闭网络,重新打开Android studio就好了.(但是原因不清楚是为什么?) Internal error. ...
- 【JavaScript】固定布局轮播图特效
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- one recursive approach for 3, hdu 1016 (with an improved version) , permutations, N-Queens puzzle 分类: hdoj 2015-07-19 16:49 86人阅读 评论(0) 收藏
one recursive approach to solve hdu 1016, list all permutations, solve N-Queens puzzle. reference: t ...
- clistctrl失去焦点高亮显示选中行
clistctrl失去焦点高亮显示选中行 响应两个消息 NM_SETFOCUS,NM_KILLFOCUS void CDatabaseParseDlg::OnNMKillfocusListGroup( ...
- linux输出 /dev/null
在学习Linux的过程中,常会看到一些终端命令或者程序中有">/dev/null 2>&1 "出现,由于已经遇到了好几次了,为了理解清楚,不妨花点时间百度或者g ...