Node核心模塊(1):Buffer

## 模塊概覽

Buffer是node的核心模塊,開發(fā)者可以利用它來(lái)處理二進(jìn)制數(shù)據(jù),比如文件流的讀寫、網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)的處理等。

Buffer的API非常多,本文僅挑選 比較常用/容易理解 的API進(jìn)行講解,包括Buffer實(shí)例的創(chuàng)建、比較、連接、拷貝、查找、遍歷、類型轉(zhuǎn)換、截取、編碼轉(zhuǎn)換等。

WritableReadable 流都會(huì)將數(shù)據(jù)存儲(chǔ)到內(nèi)部的緩存(buffer)中。這些緩存可以 通過(guò)相應(yīng)的 writable._writableState.getBuffer() 或 readable._readableState.buffer來(lái)獲取。
緩存的大小取決于傳遞給流構(gòu)造函數(shù)的 highWaterMark選項(xiàng)。 對(duì)于普通的流, highWaterMark 選項(xiàng)指定了總共的字節(jié)數(shù)。對(duì)于工作在對(duì)象模式的流, highWaterMark 指定了對(duì)象的總數(shù)。當(dāng)可讀流的實(shí)現(xiàn)調(diào)用 stream.push(chunk) 方法時(shí),數(shù)據(jù)被放到緩存中。如果流的消費(fèi)者 沒(méi)有調(diào)用 stream.read() 方法, 這些數(shù)據(jù)會(huì)始終存在于內(nèi)部隊(duì)列中,直到被消費(fèi)。
當(dāng)內(nèi)部可讀緩存的大小達(dá)到 highWaterMark指定的閾值時(shí),流會(huì)暫停從底層資源讀取數(shù)據(jù),直到當(dāng)前 緩存的數(shù)據(jù)被消費(fèi) (也就是說(shuō), 流會(huì)在內(nèi)部停止調(diào)用 readable._read()來(lái)填充可讀緩存)。可寫流通過(guò)反復(fù)調(diào)用 writable.write(chunk) 方法將數(shù)據(jù)放到緩存。 當(dāng)內(nèi)部可寫緩存的總大小小于 highWaterMark 指定的閾值時(shí), 調(diào)用 writable.write() 將返回true。 一旦內(nèi)部緩存的大小達(dá)到或超過(guò) highWaterMark ,調(diào)用 writable.write() 將返回 false。
stream API 的關(guān)鍵目標(biāo), 尤其對(duì)于 stream.pipe() 方法, 就是限制緩存數(shù)據(jù)大小,以達(dá)到可接受的程度。這樣,對(duì)于讀寫速度不匹配的源頭和目標(biāo),就不會(huì)超出可用的內(nèi)存大小。DuplexTransform 都是可讀寫的。 在內(nèi)部,它們都維護(hù)了 兩個(gè) 相互獨(dú)立的緩存用于讀和寫。 在維持了合理高效的數(shù)據(jù)流的同時(shí),也使得對(duì)于讀和寫可以獨(dú)立進(jìn)行而互不影響。 例如, net.Socket 就是 Duplex 的實(shí)例,它的可讀端可以消費(fèi)從套接字(socket)中接收的數(shù)據(jù), 可寫端則可以將數(shù)據(jù)寫入到套接字。 由于數(shù)據(jù)寫入到套接字中的速度可能比從套接字接收數(shù)據(jù)的速度快或者慢, 在讀寫兩端使用獨(dú)立緩存,并進(jìn)行獨(dú)立操作就顯得很重要了。

創(chuàng)建

  • new Buffer(array)
  • Buffer.alloc(length)
  • Buffer.allocUnsafe(length)
  • Buffer.from(array)

通過(guò) new Buffer(array)

// Creates a new Buffer containing the ASCII bytes of the string 'buffer'
const buf = new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);

驗(yàn)證下:

var array = 'buffer'.split('').map(function(v){
    return '0x' + v.charCodeAt(0).toString(16)
});

console.log( array.join() );
// 輸出:0x62,0x75,0x66,0x66,0x65,0x72

通過(guò) Buffer.alloc(length)

var buf1 = Buffer.alloc(10);  // 長(zhǎng)度為10的buffer,初始值為0x0
var buf2 = Buffer.alloc(10, 1);  // 長(zhǎng)度為10的buffer,初始值為0x1
var buf3 = Buffer.allocUnsafe(10);  // 長(zhǎng)度為10的buffer,初始值不確定
var buf4 = Buffer.from([1, 2, 3])  // 長(zhǎng)度為3的buffer,初始值為 0x01, 0x02, 0x03

通過(guò)Buffer.from()

例子一:Buffer.from(array)

// [0x62, 0x75, 0x66, 0x66, 0x65, 0x72] 為字符串 "buffer" 
// 0x62 為16進(jìn)制,轉(zhuǎn)成十進(jìn)制就是 98,代表的就是字母 b
var buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
console.log(buf.toString());

例子二:Buffer.from(string[, encoding])

通過(guò)string創(chuàng)建buffer,跟將buffer轉(zhuǎn)成字符串時(shí),記得編碼保持一致,不然會(huì)出現(xiàn)亂碼,如下所示。

var buf = Buffer.from('this is a tést');  // 默認(rèn)采用utf8

// 輸出:this is a tést
console.log(buf.toString());  // 默認(rèn)編碼是utf8,所以正常打印

// 輸出:this is a tC)st
console.log(buf.toString('ascii'));  // 轉(zhuǎn)成字符串時(shí),編碼不是utf8,所以亂碼

對(duì)亂碼的分析如下:

var letter = 'é';
var buff = Buffer.from(letter);  // 默認(rèn)編碼是utf8,這里占據(jù)兩個(gè)字節(jié) <Buffer c3 a9>
var len = buff.length;  // 2
var code = buff[0]; // 第一個(gè)字節(jié)為0xc3,即195:超出ascii的最大支持范圍
var binary = code.toString(2);  // 195的二進(jìn)制:10101001
var finalBinary = binary.slice(1);  // 將高位的1舍棄,變成:0101001
var finalCode = parseInt(finalBinary, 2);  // 0101001 對(duì)應(yīng)的十進(jìn)制:67
var finalLetter = String.fromCharCode(finalCode);  // 67對(duì)應(yīng)的字符:C

// 同理 0xa9最終轉(zhuǎn)成的ascii字符為)
// 所以,最終輸出為 this is a tC)st

例子三:Buffer.from(buffer)

創(chuàng)建新的Buffer實(shí)例,并將buffer的數(shù)據(jù)拷貝到新的實(shí)例子中去。

var buff = Buffer.from('buffer');
var buff2 = Buffer.from(buff);

console.log(buff.toString());  // 輸出:buffer
console.log(buff2.toString());  // 輸出:buffer

buff2[0] = 0x61;

console.log(buff.toString());  // 輸出:buffer
console.log(buff2.toString());  // 輸出:auffer

buffer比較

buf.equals(otherBuffer)

判斷兩個(gè)buffer實(shí)例存儲(chǔ)的數(shù)據(jù)是否相同,如果是,返回true,否則返回false。

// 例子一:編碼一樣,內(nèi)容相同
var buf1 = Buffer.from('A');
var buf2 = Buffer.from('A');

console.log( buf1.equals(buf2) );  // true

// 例子二:編碼一樣,內(nèi)容不同
var buf3 = Buffer.from('A');
var buf4 = Buffer.from('B');

console.log( buf3.equals(buf4) );  // false

// 例子三:編碼不一樣,內(nèi)容相同
var buf5 = Buffer.from('ABC');  // <Buffer 41 42 43>
var buf6 = Buffer.from('414243', 'hex');

console.log(buf5.equals(buf6));

buf.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]])

同樣是對(duì)兩個(gè)buffer實(shí)例進(jìn)行比較,不同的是:

  1. 可以指定特定比較的范圍(通過(guò)start、end指定)
  2. 返回值為整數(shù),達(dá)標(biāo)buf、target的大小關(guān)系

假設(shè)返回值為

  • 0:buf、target大小相同。
  • 1:buf大于target,也就是說(shuō)buf應(yīng)該排在target之后。
  • -1:buf小于target,也就是說(shuō)buf應(yīng)該排在target之前。

看例子,官方的例子挺好的,直接貼一下:

const buf1 = Buffer.from('ABC');
const buf2 = Buffer.from('BCD');
const buf3 = Buffer.from('ABCD');

// Prints: 0
console.log(buf1.compare(buf1));

// Prints: -1
console.log(buf1.compare(buf2));

// Prints: -1
console.log(buf1.compare(buf3));

// Prints: 1
console.log(buf2.compare(buf1));

// Prints: 1
console.log(buf2.compare(buf3));

// Prints: [ <Buffer 41 42 43>, <Buffer 41 42 43 44>, <Buffer 42 43 44> ]
// (This result is equal to: [buf1, buf3, buf2])
console.log([buf1, buf2, buf3].sort(Buffer.compare));

Buffer.compare(buf1, buf2)

buf.compare(target) 大同小異,一般用于排序。直接貼官方例子:

const buf1 = Buffer.from('1234');
const buf2 = Buffer.from('0123');
const arr = [buf1, buf2];

// Prints: [ <Buffer 30 31 32 33>, <Buffer 31 32 33 34> ]
// (This result is equal to: [buf2, buf1])
console.log(arr.sort(Buffer.compare));

從Buffer.from([62])談起

這里稍微研究下Buffer.from(array)。下面是官方文檔對(duì)API的說(shuō)明,也就是說(shuō),每個(gè)array的元素對(duì)應(yīng)1個(gè)字節(jié)(8位),取值從0到255。

Allocates a new Buffer using an array of octets.

數(shù)組元素為數(shù)字

首先看下,傳入的元素為數(shù)字的場(chǎng)景。下面分別是10進(jìn)制、8進(jìn)制、16進(jìn)制,跟預(yù)期中的結(jié)果一致。

var buff = Buffer.from([62])
// <Buffer 3e>
// buff[0] === parseInt('3e', 16) === 62
var buff = Buffer.from([062])
// <Buffer 32>
// buff[0] === parseInt(62, 8) === parseInt(32, 16) === 50
var buff = Buffer.from([0x62])
// <Buffer 62>
// buff[0] === parseInt(62, 16) === 98

數(shù)組元素為字符串

再看下,傳入的元素為字符串的場(chǎng)景。

  1. 0開頭的字符串,在parseInt('062')時(shí),可以解釋為62,也可以解釋為50(八進(jìn)制),這里看到采用了第一種解釋。
  2. 字符串的場(chǎng)景,跟parseInt()有沒(méi)有關(guān)系,暫未深入探究,只是這樣猜想。TODO(找時(shí)間研究下)
var buff = Buffer.from(['62'])
// <Buffer 3e>
// buff[0] === parseInt('3e', 16) === parseInt('62') === 62
var buff = Buffer.from(['062'])
// <Buffer 3e>
// buff[0] === parseInt('3e', 16) === parseInt('062') === 62
var buff = Buffer.from(['0x62'])
// <Buffer 62>
// buff[0] === parseInt('62', 16) === parseInt('0x62') === 98

數(shù)組元素大小超出1個(gè)字節(jié)

感興趣的同學(xué)自行探究。

var buff = Buffer.from([256])
// <Buffer 00>

Buffer.from('1')

一開始不自覺(jué)的會(huì)將Buffer.from('1')[0]"1"劃等號(hào),其實(shí)"1"對(duì)應(yīng)的編碼是49。

var buff = Buffer.from('1')  // <Buffer 31>
console.log(buff[0] === 1)  // false

這樣對(duì)比就知道了,編碼為1的是個(gè)控制字符,表示 Start of Heading。

console.log( String.fromCharCode(49) )  // '1'
console.log( String.fromCharCode(1) )  // '\u0001'

buffer連接:Buffer.concat(list[, totalLength])

備注:個(gè)人覺(jué)得totalLength這個(gè)參數(shù)挺多余的,從官方文檔來(lái)看,是處于性能提升的角度考慮。不過(guò)內(nèi)部實(shí)現(xiàn)也只是遍歷list,將length累加得到totalLength,從這點(diǎn)來(lái)看,性能優(yōu)化是幾乎可以忽略不計(jì)的。

var buff1 = Buffer.alloc(10);
var buff2 = Buffer.alloc(20);

var totalLength = buff1.length + buff2.length;

console.log(totalLength);  // 30

var buff3 = Buffer.concat([buff1, buff2], totalLength);

console.log(buff3.length);  // 30

除了上面提到的性能優(yōu)化,totalLength還有兩點(diǎn)需要注意。假設(shè)list里面所有buffer的長(zhǎng)度累加和為length

  • totalLength > length:返回長(zhǎng)度為totalLength的Buffer實(shí)例,超出長(zhǎng)度的部分填充0。
  • totalLength < length:返回長(zhǎng)度為totalLength的Buffer實(shí)例,后面部分舍棄。
var buff4 = Buffer.from([1, 2]);
var buff5 = Buffer.from([3, 4]);

var buff6 = Buffer.concat([buff4, buff5], 5);

console.log(buff6.length);  // 
console.log(buff6);  // <Buffer 01 02 03 04 00>

var buff7 = Buffer.concat([buff4, buff5], 3);

console.log(buff7.length);  // 3
console.log(buff7);  // <Buffer 01 02 03>

拷貝:buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])

使用比較簡(jiǎn)單,如果忽略后面三個(gè)參數(shù),那就是將buf的數(shù)據(jù)拷貝到target里去,如下所示:

var buff1 = Buffer.from([1, 2]);
var buff2 = Buffer.alloc(2);

buff1.copy(buff2);

console.log(buff2);  // <Buffer 01 02>

另外三個(gè)參數(shù)比較直觀,直接看官方例子

const buf1 = Buffer.allocUnsafe(26);
const buf2 = Buffer.allocUnsafe(26).fill('!');

for (let i = 0 ; i < 26 ; i++) {
  // 97 is the decimal ASCII value for 'a'
  buf1[i] = i + 97;
}

buf1.copy(buf2, 8, 16, 20);

// Prints: !!!!!!!!qrst!!!!!!!!!!!!!
console.log(buf2.toString('ascii', 0, 25));

查找:buf.indexOf(value[, byteOffset][, encoding])

跟數(shù)組的查找差不多,需要注意的是,value可能是String、Buffer、Integer中的任意類型。

  • String:如果是字符串,那么encoding就是其對(duì)應(yīng)的編碼,默認(rèn)是utf8。
  • Buffer:如果是Buffer實(shí)例,那么會(huì)將value中的完整數(shù)據(jù),跟buf進(jìn)行對(duì)比。
  • Integer:如果是數(shù)字,那么value會(huì)被當(dāng)做無(wú)符號(hào)的8位整數(shù),取值范圍是0到255。

另外,可以通過(guò)byteOffset來(lái)指定起始查找位置。

直接上代碼,官方例子妥妥的,耐心看完它基本就理解得差不多了。

const buf = Buffer.from('this is a buffer');

// Prints: 0
console.log(buf.indexOf('this'));

// Prints: 2
console.log(buf.indexOf('is'));

// Prints: 8
console.log(buf.indexOf(Buffer.from('a buffer')));

// Prints: 8
// (97 is the decimal ASCII value for 'a')
console.log(buf.indexOf(97));

// Prints: -1
console.log(buf.indexOf(Buffer.from('a buffer example')));

// Prints: 8
console.log(buf.indexOf(Buffer.from('a buffer example').slice(0, 8)));


const utf16Buffer = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2');

// Prints: 4
console.log(utf16Buffer.indexOf('\u03a3', 0, 'ucs2'));

// Prints: 6
console.log(utf16Buffer.indexOf('\u03a3', -4, 'ucs2'));

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

將sring寫入buf實(shí)例,同時(shí)返回寫入的字節(jié)數(shù)。

參數(shù)如下:

  • string:寫入的字符串。
  • offset:從buf的第幾位開始寫入,默認(rèn)是0。
  • length:寫入多少個(gè)字節(jié),默認(rèn)是 buf.length - offset。
  • encoding:字符串的編碼,默認(rèn)是utf8。

看個(gè)簡(jiǎn)單例子

var buff = Buffer.alloc(4);
buff.write('a');  // 返回 1
console.log(buff);  // 打印 <Buffer 61 00 00 00>

buff.write('ab');  // 返回 2
console.log(buff);  // 打印 <Buffer 61 62 00 00>

填充:buf.fill(value[, offset[, end]][, encoding])

value填充buf,常用于初始化buf。參數(shù)說(shuō)明如下:

  • value:用來(lái)填充的內(nèi)容,可以是Buffer、String或Integer。
  • offset:從第幾位開始填充,默認(rèn)是0。
  • end:停止填充的位置,默認(rèn)是 buf.length。
  • encoding:如果value是String,那么為value的編碼,默認(rèn)是utf8。

例子:

var buff = Buffer.alloc(20).fill('a');

console.log(buff.toString());  // aaaaaaaaaaaaaaaaaaaa

轉(zhuǎn)成字符串: buf.toString([encoding[, start[, end]]])

把buf解碼成字符串,用法比較直觀,看例子

var buff = Buffer.from('hello');

console.log( buff.toString() );  // hello

console.log( buff.toString('utf8', 0, 2) );  // he

轉(zhuǎn)成JSON字符串:buf.toJSON()

var buff = Buffer.from('hello');

console.log( buff.toJSON() );  // { type: 'Buffer', data: [ 104, 101, 108, 108, 111 ] }

遍歷:buf.values()、buf.keys()、buf.entries()

用于對(duì)buf進(jìn)行for...of遍歷,直接看例子。

var buff = Buffer.from('abcde');

for(const key of buff.keys()){
    console.log('key is %d', key);
}
// key is 0
// key is 1
// key is 2
// key is 3
// key is 4

for(const value of buff.values()){
    console.log('value is %d', value);
}
// value is 97
// value is 98
// value is 99
// value is 100
// value is 101

for(const pair of buff.entries()){
    console.log('buff[%d] === %d', pair[0], pair[1]);
}
// buff[0] === 97
// buff[1] === 98
// buff[2] === 99
// buff[3] === 100
// buff[4] === 101

截取:buf.slice([start[, end]])

用于截取buf,并返回一個(gè)新的Buffer實(shí)例。需要注意的是,這里返回的Buffer實(shí)例,指向的仍然是buf的內(nèi)存地址,所以對(duì)新Buffer實(shí)例的修改,也會(huì)影響到buf。

var buff1 = Buffer.from('abcde');
console.log(buff1);  // <Buffer 61 62 63 64 65>

var buff2 = buff1.slice();
console.log(buff2);  // <Buffer 61 62 63 64 65>

var buff3 = buff1.slice(1, 3);
console.log(buff3);  // <Buffer 62 63>

buff3[0] = 97;  // parseInt(61, 16) ==> 97
console.log(buff1);  // <Buffer 62 63>

TODO

  1. 創(chuàng)建、拷貝、截取、轉(zhuǎn)換、查找
  2. buffer、arraybuffer、dataview、typedarray
  3. buffer vs 編碼
  4. Buffer.from()、Buffer.alloc()、Buffer.alocUnsafe()
  5. Buffer vs TypedArray

文檔摘要

關(guān)于buffer內(nèi)存空間的動(dòng)態(tài)分配

Instances of the Buffer class are similar to arrays of integers but correspond to fixed-sized, raw memory allocations outside the V8 heap. The size of the Buffer is established when it is created and cannot be resized.

相關(guān)鏈接

unicode對(duì)照表
https://unicode-table.com/cn/#control-character

字符編碼筆記:ASCII,Unicode和UTF-8
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • https://nodejs.org/api/documentation.html 工具模塊 Assert 測(cè)試 ...
    KeKeMars閱讀 6,597評(píng)論 0 6
  • Node.js Buffer(緩沖區(qū)) JavaScript 語(yǔ)言自身只有字符串?dāng)?shù)據(jù)類型,沒(méi)有二進(jìn)制數(shù)據(jù)類型。但在...
    FTOLsXD閱讀 612評(píng)論 0 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,525評(píng)論 19 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,628評(píng)論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,995評(píng)論 0 11

友情鏈接更多精彩內(nèi)容