Node.js Buffer(一、初学者的角度了解Buffer)

Buffer

稳定性:2-Stable

0x01 为什么要用Buffer

众所周知,JavaScript与C/C++不同,没有读写二进制流的机制也无法操作内存。在ECMAScript 2015(ES6)中,TypedArray可以有效的解决这个问题,它允许开发者以数组下标的形式操作内存,大大增强了JavaScript处理二进制数据的能力。其实Buffer的功能与TypedArray相同,它为node.js提供了处理内存的功能,如操作TCP流,文件系统等。相比之下,Buffer的一些功能在V8引擎的助力下性能可以发挥得更好。因此推荐在node.js的开发中使用Buffer(当然,使用ES6的TypedArray也可以)

  • Buffer的实例与整型数组类似,最底层的内存分配是在V8 heap的外部,分配内存的大小在Buffer实例创建后不可更改。
  • Buffer类是使用八位字节流,所以一个字节的大小是0~255,与C语言中的unsigned char和ES6的Uint8Array的范围是相同的。
  • Buffer类是全局的,直接使用即可,不需要使用require('buffer').Buffer

以下是官网的一些简单事例:

// Creates a zero-filled Buffer of length 10.
const buf1 = Buffer.alloc(10);

// Creates a Buffer of length 10, filled with 0x1.
const buf2 = Buffer.alloc(10, 1);

// Creates an uninitialized buffer of length 10.
// This is faster than calling Buffer.alloc() but the returned
// Buffer instance might contain old data that needs to be
// overwritten using either fill() or write().
const buf3 = Buffer.allocUnsafe(10);

// Creates a Buffer containing [0x1, 0x2, 0x3].
const buf4 = Buffer.from([1, 2, 3]);

// Creates a Buffer containing ASCII bytes [0x74, 0x65, 0x73, 0x74].
const buf5 = Buffer.from('test');

// Creates a Buffer containing UTF-8 bytes [0x74, 0xc3, 0xa9, 0x73, 0x74].
const buf6 = Buffer.from('tést', 'utf8');

不吹不黑,作者亲身经历,掌握好以下两种技能可以帮助你更快的理解这篇文章和官方文档:

  • C语言基础
  • ECMAScript 2015 ArrayBuffer TypedArray

0x02 Buffer.from(), Buffer.alloc(), and Buffer.allocUnsafe()

Node.js V6版本之前,Buffer实例是通过Buffer构造函数创建的,构造函数的传参不同,创建的实例也是不同的

  • 如果第一个参数是一个number(e.g. new Buffer(10)),会创建一个大小是number值的Buffer,它分配的内存段是没有进行初始化的,可能会包含敏感的数据,这种实例必须通过buf.fill(0)方法或者其他写操作使其内存进行初始化。然而,这种情况就是为了提升性能和速度, 因为创建一个fast-but-uninitialized Buffer(内存快速分配但是没有进行初始化)与slower-but-safer Buffer(分配的内存进行初始化,速度慢但更安全)差别还是很大的
  • 如果第一个参数是一个String或者Buffer,则Buffer实例会拷贝参数的内存
  • 如果第一个参数是ArrayBuffer,则Buffer实例共享ArrayBuffer的内存

以上可知,new Buffer()根据第一个参数的类型不同,分配的内存有很大的差别,且程序也不会对new Buffer()的参数进行正确性校验,或是当初始化Buffer内存数据失败时,这些都会不经意地给您的代码带来安全性和可靠性问题。为了使创建Buffer实例的过程更加安全和可靠,各种形式的new Buffer()都是反对使用的,开发人员可能要改造有关new Buffer()的代码,用Buffer.from()Buffer.alloc()Buffer.allocUnsafe()等方法来代替。

  • Buffer.from(array)
  • Buffer.from(arrayBuffer[, buteOffset [, length]])
  • Buffer.from(buffer)
  • Buffer.from(string)
  • Buffer.alloc(size[, fill[, encoding]])
  • Buffer.allocUnsafe(size)Buffer.allocUnsafeSlow(size)

通过Buffer.allocUnsafe()创建的Buffer实例可能会分配共享内存池的内存,如果它的大小小于等于Buffer.poolSize的一半。通过Buffer.allocUnsafeSlow()创建的内存则永远不会使用共享内存池。

0x03 命令行参数:--zero-fill-buffers

--zero-fill-buffer参数的作用是:new Buffer(size)Buffer.allocUnsafe()Buffer.allocUnsafeSlow()或者new SlowBuffer(size)创建的实例的内存都会被0填满。使用这个参数将改变这些方法的执行结果和性能,只有在需要强制创建没有敏感数据的Buffer实例时推荐使用

$ node --zeor-file-buffers
> Buffer.allocUnsafe(5); 
// <Buffer 00 00 00 00 00>
// 如果不加这个参数则Buffer的数据可能是别的

0x04 为什么 Buffer.allocUnsafe() 和 Buffer.allocUnsafeSlow() 不安全

当调用Buffer.allocUnsafe()Buffer.allocUnsafeSlow()时,分配的内存段没有初始化(内存没有清零),这个设计使得分配内存的过程非常快,但分配的内存可能潜在地包含敏感数据。所以调用Buffer.allocUnsafe()创建的Buffer实例如果没有对分配的内存段的数据进行重写,当对这个Buffer进行读操作时,敏感的数据就会泄露。当考虑性能方面的优势使用Buffer.allocUnsafe()时,一定要避免这个安全漏洞。

0x05 Buffers and ES6 iteration

Buffer实例可以使用ECMAScript 2015 (ES6) for...of语法

const buf = Buffer.from([1, 2, 3]);

// Prints:
//   1
//   2
//   3
for (const b of buf) {
  console.log(b);
}

另外说明的是,buf.values()buf.keys()buf.entries() 也可以创建iterators (遍历器)

0x06 Buffers and Character Encodings

Buffer实例通常用来表述字符编码的序列,例如UTF-8,UCS2,Base64,甚至十六进制的数据。通过指定的字符编码,Buffer和JavaScript字符串可以相互转换。

const buf = Buffer.from('hello world', 'ascii');

// Prints: 68656c6c6f20776f726c64
console.log(buf.toString('hex'));

// Prints: aGVsbG8gd29ybGQ=
console.log(buf.toString('base64'));

目前Node.js支持的字符编码有:

  • 'ascii' 仅用于7-bit ASCII(ASCII码是0~127),编码速度很快且如果有超出范围的数据将会被截取掉
  • 'utf8' 多字节的Unicode编码字符,多数网页和文档的格式都是UTF-8
  • 'utf16le' 2或者4字节,小端的Unicode编码字符,支持的代理项对(U+10000 to U+10FFFF)
  • 'ucs2' 'utf16le'的别名
  • 'base64' Base64编码,当通过字符串创建Buffer时,将会使用URL and FIlename Safe Alphabet
  • 'latin1' 一字节字符串的编码方式(详见RFC1345第63页)
  • 'binary' 'latin1'的别名
  • 'hex' 一个字节两个十六进制字符的编码方式,如0x2f

注意:当今浏览器遵循的是WHATWG spec标准,'latin1''ISO-8859-1'都属于'win-1252'编码的一种('win-1252'还包含其他的编码)。这意味着如果有些操作如http.get(),它返回的字符是WHATWG spec的'win-1252'编码的数据,使用'latin1'进行解码得到的数据可能是不正确的。

0x07 Buffers and TypedArray

Buffer实例也是Unit8Array视图。但是它与ECMAScript 2015的TypedArray还有些细微的不同。例如,ArrayBuffer#slice()创建实例的内存是slice方法拷贝的内存,而Buffer#slice()是在Buffer的基础上创建了视图来操作内存,所以Buffer#slice()的效率更高一些
通过Buffer创建二进制数组时要注意下面几点:

  • TypedArray是拷贝Buffer对象的内存,但不共享内存
  • Buffer对象的数组形式与TypedArry的不同。例如,new Uint32Array(Buffer.from([1,2,3,4]))所创建的Uint32Array是多元素数组[1,2,3,4],而new Uint32Array(TypedArray)创建的是只有一个元素的数组[0x01020304][0x04030201]

通过TypedArray对象的.buffer属性创建的Buffer实例与TypedArray实例共享同一个内存

const arr = new Uint16Array(2);// arr是TypedArray

arr[0] = 5000;
arr[1] = 4000;

// 拷贝`arr`的内存
const buf1 = Buffer.from(arr);

// 共享`arr`的内存
const buf2 = Buffer.from(arr.buffer);

// Prints: <Buffer 88 a0>
console.log(buf1);

// Prints: <Buffer 88 13 a0 0f>
console.log(buf2);

arr[1] = 6000;

// Prints: <Buffer 88 a0>
console.log(buf1);

// Prints: <Buffer 88 13 70 17>
console.log(buf2);

通过TypedArray.buffer创建Buffer时,可以通过byteOffsetlength参数使用ArrayTyped的部分内存

const arr = new Uint16Array(20);
const buf = Buffer.from(arr.buffer, 0, 16);

// Prints: 16
console.log(buf.length);

Buffer.from()TypedArray.from()方法是不同的,TypedArray.from()的第二个参数是一个mapping遍历的函数:

  • TypedArray.from(source[, mapFn[, thisArg]])

Buffer.from()并不支持这个mapping函数:

  • Buffer.from(array)
  • Buffer.from(buffer)
  • Buffer.from(arrayBuffer[, byteOffset [, length]])
  • Buffer.from(string[, encoding])

总结:这篇文章首先回答了为什么要使用Buffer,然后详述了Buffer的功能、效率、安全性及一些使用上的问题,使读者对其概念有一个初步的了解,下一章将会带大家深入了解Class Buffer的具体使用方法,敬请期待。

本文档是根据Node.js目前稳定版本的文档Node.js v6.10.2 Documentation进行总结的,如您在阅读的过程中发现问题,请联系作者,最后感谢您的支持!

简书作者 小菜荔枝 转载请联系作者获得授权

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 二进制数组(ArrayBuffer对象、TypedArray视图和DataView视图)是JavaScript操作...
    呼呼哥阅读 21,261评论 2 12
  • Node.js Buffer(缓冲区) JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。但在...
    FTOLsXD阅读 492评论 0 2
  • 一 背景 JavaScript经过二十来年年的发展,由最初简单的交互脚本语言,发展到今天的富客户端交互,后端服务器...
    Michael_bdb5阅读 1,233评论 0 2
  • Buffer是node的核心模块,开发者可以利用它来处理二进制数据,比如文件流的读写、网络请求数据的处理等。 Bu...
    自度君阅读 497评论 0 1
  • 夏天和孩子出去玩的时候,捡起几块石头,小编手把手教你用才华征服这些石头。 把捡回来的石头让孩子们用不同颜色的马克笔...
    跟着爸比玩手工阅读 469评论 0 0