Node.js的Buffer那些你可能不知道的用法
在大多數介紹Buffer的文章中,主要是圍繞數據拼接和內存分配這兩方面的。比如我們使用fs
模塊來讀取文件內容的時候,返回的就是一個Buffer:
fs.readFile(‘filename‘, function (err, buf) {
// <Buffer 2f 2a 2a 0a 20 2a 20 53 75 ... >
});
在使用net
或http
模塊來接收網絡數據時,data
事件的參數也是一個Buffer,這時我們還需要使用Buffer.concat()
來做數據拼接:
var bufs = [];
conn.on(‘data‘, function (buf) {
bufs.push(buf);
});
conn.on(‘end‘, function () {
// 接收數據結束後,拼接所有收到的Buffer對象
var buf = Buffer.concat(bufs);
});
還可以利用Buffer.toString()
來做轉換base64
或十六進制字符的轉換,比如:
console.log(new Buffer(‘hello, world!‘).toString(‘base64‘));
// 轉換成base64字符串:aGVsbG8sIHdvcmxkIQ==
console.log(new Buffer(‘aGVsbG8sIHdvcmxkIQ==‘, ‘base64‘).toString());
// 還原base64字符串:hello, world!
console.log(new Buffer(‘hello, world!‘).toString(‘hex‘));
// 轉換成十六進制字符串:68656c6c6f2c20776f726c6421
console.log(new Buffer(‘68656c6c6f2c20776f726c6421‘, ‘hex‘).toString());
// 還原十六進制字符串:hello, world!
一般情況下,單個Node.js進程是有最大內存限制的,以下是來自官方文檔中的說明:
What is the memory limit on a node process?
Currently, by default v8 has a memory limit of 512MB on 32-bit systems, and 1.4GB on 64-bit systems. The limit can be raised by setting --max_old_space_size to a maximum of ~1024 (~1 GB) (32-bit) and ~4096 (~4GB) (64-bit), but it is recommended that you split your single process into several workers if you are hitting memory limits.
由於Buffer對象占用的內存空間是不計算在Node.js進程內存空間限制上的,因此,我們也常常會使用Buffer來存儲需要占用大量內存的數據:
// 分配一個2G-1字節的數據
// 單次分配內存超過此值會拋出異常 RangeError: Invalid typed array length
var buf = new Buffer(1024 * 1024 * 1024 - 1);
以上便是Buffer的幾種常見用法。然而,閱讀Buffer的API文檔時,我們會發現更多的是readXXX()
和writeXXX()
開頭的API,具體如下:
- buf.readUIntLE(offset, byteLength[, noAssert])
- buf.readUIntBE(offset, byteLength[, noAssert])
- buf.readIntLE(offset, byteLength[, noAssert])
- buf.readIntBE(offset, byteLength[, noAssert])
- buf.readUInt8(offset[, noAssert])
- buf.readUInt16LE(offset[, noAssert])
- buf.readUInt16BE(offset[, noAssert])
- buf.readUInt32LE(offset[, noAssert])
- buf.readUInt32BE(offset[, noAssert])
- buf.readInt8(offset[, noAssert])
- buf.readInt16LE(offset[, noAssert])
- buf.readInt16BE(offset[, noAssert])
- buf.readInt32LE(offset[, noAssert])
- buf.readInt32BE(offset[, noAssert])
- buf.readFloatLE(offset[, noAssert])
- buf.readFloatBE(offset[, noAssert])
- buf.readDoubleLE(offset[, noAssert])
- buf.readDoubleBE(offset[, noAssert])
- buf.write(string[, offset][, length][, encoding])
- buf.writeUIntLE(value, offset, byteLength[, noAssert])
- buf.writeUIntBE(value, offset, byteLength[, noAssert])
- buf.writeIntLE(value, offset, byteLength[, noAssert])
- buf.writeIntBE(value, offset, byteLength[, noAssert])
- buf.writeUInt8(value, offset[, noAssert])
- buf.writeUInt16LE(value, offset[, noAssert])
- buf.writeUInt16BE(value, offset[, noAssert])
- buf.writeUInt32LE(value, offset[, noAssert])
- buf.writeUInt32BE(value, offset[, noAssert])
- buf.writeInt8(value, offset[, noAssert])
- buf.writeInt16LE(value, offset[, noAssert])
- buf.writeInt16BE(value, offset[, noAssert])
- buf.writeInt32LE(value, offset[, noAssert])
- buf.writeInt32BE(value, offset[, noAssert])
- buf.writeFloatLE(value, offset[, noAssert])
- buf.writeFloatBE(value, offset[, noAssert])
- buf.writeDoubleLE(value, offset[, noAssert])
- buf.writeDoubleBE(value, offset[, noAssert])
這些API為在Node.js中操作數據提供了極大的便利。假設我們要將一個整形數值存儲到文件中,比如當前時間戳為1447656645380
,如果將其當作一個字符串存儲時,需要占用11字節的空間,而將其轉換為二進制存儲時僅需6字節空間即可:
var buf = new Buffer(6);
buf.writeUIntBE(1447656645380, 0, 6);
// <Buffer 01 51 0f 0f 63 04>
buf.readUIntBE(0, 6);
// 1447656645380
在使用Node.js編寫一些底層功能時,比如一個網絡通信模塊、某個數據庫的客戶端模塊,或者需要從文件中操作大量結構化數據時,以上Buffer對象提供的API都是必不可少的。
接下來將演示一個使用Buffer對象操作結構化數據的例子。
操作結構化數據
假設有一個學生考試成績數據庫,每條記錄結構如下:
學號 | 課程代碼 | 分數 |
---|---|---|
XXXXXX | XXXX | XX |
其中學號是一個6位的數字,課程代碼是一個4位數字,分數最高分為100分。
在使用文本來存儲這些數據時,比如使用CSV格式存儲可能是這樣的:
100001,1001,99
100002,1001,67
100003,1001,88
其中每條記錄占用15字節的空間,而使用二進制存儲時其結構將會是這樣:
學號 | 課程代碼 | 分數 |
---|---|---|
3字節 | 2字節 | 1字節 |
每一條記錄僅需要6字節的空間即可,僅僅是使用文本存儲的40%!下面是用來操作這些記錄的程序:
// 讀取一條記錄
// buf Buffer對象
// offset 本條記錄在Buffer對象的開始位置
// data {number, lesson, score}
function writeRecord (buf, offset, data) {
buf.writeUIntBE(data.number, offset, 3);
buf.writeUInt16BE(data.lesson, offset + 3);
buf.writeInt8(data.score, offset + 5);
}
// 寫入一條記錄
// buf Buffer對象
// offset 本條記錄在Buffer對象的開始位置
function readRecord (buf, offset) {
return {
number: buf.readUIntBE(offset, 3),
lesson: buf.readUInt16BE(offset + 3),
score: buf.readInt8(offset + 5)
};
}
// 寫入記錄列表
// list 記錄列表,每一條包含 {number, lesson, score}
function writeList (list) {
var buf = new Buffer(list.length * 6);
var offset = 0;
for (var i = 0; i < list.length; i++) {
writeRecord(buf, offset, list[i]);
offset += 6;
}
return buf;
}
// 讀取記錄列表
// buf Buffer對象
function readList (buf) {
var offset = 0;
var list = [];
while (offset < buf.length) {
list.push(readRecord(buf, offset));
offset +=