1. 程式人生 > >NodeJS學習筆記 (15)二進制數據-buffer(ok)

NodeJS學習筆記 (15)二進制數據-buffer(ok)

int 無符號 cee safe type abc 例子 zed 需要

模塊概覽

Buffer是node的核心模塊,開發者可以利用它來處理二進制數據,比如文件流的讀寫、網絡請求數據的處理等。

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

創建

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

通過 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]);


驗證下:

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

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


通過 Buffer.alloc(length)

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

通過Buffer.from()

例子一:Buffer.from(array)

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

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

通過string創建buffer,跟將buffer轉成字符串時,記得編碼保持一致,不然會出現亂碼,如下所示。

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

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

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

對亂碼的分析如下:

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

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

例子三:Buffer.from(buffer)

創建新的Buffer實例,並將buffer的數據拷貝到新的實例子中去。

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)

判斷兩個buffer實例存儲的數據是否相同,如果是,返回true,否則返回false。

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

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

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

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

// 例子三:編碼不一樣,內容相同
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]]]])

同樣是對兩個buffer實例進行比較,不同的是:

  1. 可以指定特定比較的範圍(通過start、end指定)
  2. 返回值為整數,達標buf、target的大小關系

假設返回值為

  • 0:buf、target大小相同。
  • 1:buf大於target,也就是說buf應該排在target之後。
  • -1:buf小於target,也就是說buf應該排在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比較

buf.equals(otherBuffer)

判斷兩個buffer實例存儲的數據是否相同,如果是,返回true,否則返回false。

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

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

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

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

// 例子三:編碼不一樣,內容相同
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]]]])

同樣是對兩個buffer實例進行比較,不同的是:

  1. 可以指定特定比較的範圍(通過start、end指定)
  2. 返回值為整數,達標buf、target的大小關系

假設返回值為

  • 0:buf、target大小相同。
  • 1:buf大於target,也就是說buf應該排在target之後。
  • -1:buf小於target,也就是說buf應該排在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)。下面是官方文檔對API的說明,也就是說,每個array的元素對應1個字節(8位),取值從0到255。

Allocates a new Buffer using an array of octets.

數組元素為數字

首先看下,傳入的元素為數字的場景。下面分別是10進制、8進制、16進制,跟預期中的結果一致。

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

數組元素為字符串

再看下,傳入的元素為字符串的場景。

  1. 0開頭的字符串,在parseInt(‘062‘)時,可以解釋為62,也可以解釋為50(八進制),這裏看到采用了第一種解釋。
  2. 字符串的場景,跟parseInt()有沒有關系,暫未深入探究,只是這樣猜想。TODO(找時間研究下)
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

數組元素大小超出1個字節

感興趣的同學自行探究。

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


Buffer.from(‘1‘)

一開始不自覺的會將Buffer.from(‘1‘)[0]"1"劃等號,其實"1"對應的編碼是49。

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

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

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


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

備註:個人覺得totalLength這個參數挺多余的,從官方文檔來看,是處於性能提升的角度考慮。不過內部實現也只是遍歷list,將length累加得到totalLength,從這點來看,性能優化是幾乎可以忽略不計的。

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

除了上面提到的性能優化,totalLength還有兩點需要註意。假設list裏面所有buffer的長度累加和為length

  • totalLength > length:返回長度為totalLength的Buffer實例,超出長度的部分填充0。
  • totalLength < length:返回長度為totalLength的Buffer實例,後面部分舍棄。
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]]])

使用比較簡單,如果忽略後面三個參數,那就是將buf的數據拷貝到target裏去,如下所示:

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

buff1.copy(buff2);

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

另外三個參數比較直觀,直接看官方例子

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.copy(target[, targetStart[, sourceStart[, sourceEnd]]])

使用比較簡單,如果忽略後面三個參數,那就是將buf的數據拷貝到target裏去,如下所示:

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

buff1.copy(buff2);

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

另外三個參數比較直觀,直接看官方例子

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])

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

  • String:如果是字符串,那麽encoding就是其對應的編碼,默認是utf8。
  • Buffer:如果是Buffer實例,那麽會將value中的完整數據,跟buf進行對比。
  • Integer:如果是數字,那麽value會被當做無符號的8位整數,取值範圍是0到255。

另外,可以通過byteOffset來指定起始查找位置。

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

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實例,同時返回寫入的字節數。

參數如下:

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

看個簡單例子

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。參數說明如下:

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

例子:

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

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


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

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

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

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

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


轉成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()

用於對buf進行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,並返回一個新的Buffer實例。需要註意的是,這裏返回的Buffer實例,指向的仍然是buf的內存地址,所以對新Buffer實例的修改,也會影響到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 61 61 63 64 65>

TODO

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

文檔摘要

關於buffer內存空間的動態分配

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.

相關鏈接

unicode對照表 https://unicode-table.com/cn/#control-character

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

NodeJS學習筆記 (15)二進制數據-buffer(ok)