關(guān)于Node.js中Buffer的一些你可能不知道的用法
前言
在大多數(shù)介紹 Buffer 的文章中,主要是圍繞數(shù)據(jù)拼接和內(nèi)存分配這兩方面的。比如我們使用fs模塊來(lái)讀取文件內(nèi)容的時(shí)候,返回的就是一個(gè) Buffer:
fs.readFile('filename', function (err, buf) { // <Buffer 2f 2a 2a 0a 20 2a 20 53 75 ... > });
在使用net或http模塊來(lái)接收網(wǎng)絡(luò)數(shù)據(jù)時(shí),data事件的參數(shù)也是一個(gè) Buffer,這時(shí)我們還需要使用Buffer.concat()
來(lái)做數(shù)據(jù)拼接:
var bufs = []; conn.on('data', function (buf) { bufs.push(buf); }); conn.on('end', function () { // 接收數(shù)據(jù)結(jié)束后,拼接所有收到的 Buffer 對(duì)象 var buf = Buffer.concat(bufs); });
還可以利用Buffer.toString()
來(lái)做轉(zhuǎn)換base64或十六進(jìn)制字符的轉(zhuǎn)換,比如:
console.log(new Buffer('hello, world!').toString('base64')); // 轉(zhuǎn)換成 base64 字符串:aGVsbG8sIHdvcmxkIQ== console.log(new Buffer('aGVsbG8sIHdvcmxkIQ==', 'base64').toString()); // 還原 base64 字符串:hello, world! console.log(new Buffer('hello, world!').toString('hex')); // 轉(zhuǎn)換成十六進(jìn)制字符串:68656c6c6f2c20776f726c6421 console.log(new Buffer('68656c6c6f2c20776f726c6421', 'hex').toString()); // 還原十六進(jìn)制字符串:hello, world!
一般情況下,單個(gè) Node.js 進(jìn)程是有最大內(nèi)存限制的,以下是來(lái)自官方文檔中的說(shuō)明:
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 對(duì)象占用的內(nèi)存空間是不計(jì)算在 Node.js 進(jìn)程內(nèi)存空間限制上的,因此,我們也常常會(huì)使用 Buffer 來(lái)存儲(chǔ)需要占用大量?jī)?nèi)存的數(shù)據(jù):
// 分配一個(gè) 2G-1 字節(jié)的數(shù)據(jù) // 單次分配內(nèi)存超過(guò)此值會(huì)拋出異常 RangeError: Invalid typed array length var buf = new Buffer(1024 * 1024 * 1024 - 1);
以上便是 Buffer 的幾種常見用法。然而,閱讀 Buffer 的 API 文檔時(shí),我們會(huì)發(fā)現(xiàn)更多的是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 中操作數(shù)據(jù)提供了極大的便利。假設(shè)我們要將一個(gè)整形數(shù)值存儲(chǔ)到文件中,比如當(dāng)前時(shí)間戳為1447656645380,如果將其當(dāng)作一個(gè)字符串存儲(chǔ)時(shí),需要占用 11 字節(jié)的空間,而將其轉(zhuǎn)換為二進(jìn)制存儲(chǔ)時(shí)僅需 6 字節(jié)空間即可:
var buf = new Buffer(6); buf.writeUIntBE(1447656645380, 0, 6); // <Buffer 01 51 0f 0f 63 04> buf.readUIntBE(0, 6); // 1447656645380
在使用 Node.js 編寫一些底層功能時(shí),比如一個(gè)網(wǎng)絡(luò)通信模塊、某個(gè)數(shù)據(jù)庫(kù)的客戶端模塊,或者需要從文件中操作大量結(jié)構(gòu)化數(shù)據(jù)時(shí),以上 Buffer 對(duì)象提供的 API 都是必不可少的。
接下來(lái)將演示一個(gè)使用 Buffer 對(duì)象操作結(jié)構(gòu)化數(shù)據(jù)的例子。
操作結(jié)構(gòu)化數(shù)據(jù)
假設(shè)有一個(gè)學(xué)生考試成績(jī)數(shù)據(jù)庫(kù),每條記錄結(jié)構(gòu)如下:
學(xué)號(hào) | 課程代碼 | 分?jǐn)?shù) |
---|---|---|
XXXXXX | XXXX | XX |
其中學(xué)號(hào)是一個(gè) 6 位的數(shù)字,課程代碼是一個(gè) 4 位數(shù)字,分?jǐn)?shù)最高分為 100 分。
在使用文本來(lái)存儲(chǔ)這些數(shù)據(jù)時(shí),比如使用 CSV 格式存儲(chǔ)可能是這樣的:
100001,1001,99 100002,1001,67 100003,1001,88
其中每條記錄占用 15 字節(jié)的空間,而使用二進(jìn)制存儲(chǔ)時(shí)其結(jié)構(gòu)將會(huì)是這樣:
學(xué)號(hào) | 課程代碼 | 分?jǐn)?shù) |
---|---|---|
3 字節(jié) | 2 字節(jié) | 1 字節(jié) |
每一條記錄僅需要 6 字節(jié)的空間即可,僅僅是使用文本存儲(chǔ)的 40%!下面是用來(lái)操作這些記錄的程序:
// 讀取一條記錄 // buf Buffer 對(duì)象 // offset 本條記錄在 Buffer 對(duì)象的開始位置 // 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 對(duì)象 // offset 本條記錄在 Buffer 對(duì)象的開始位置 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 對(duì)象 function readList (buf) { var offset = 0; var list = []; while (offset < buf.length) { list.push(readRecord(buf, offset)); offset += 6; } return list; }
我們可以再編寫一段程序來(lái)看看效果:
var list = [ {number: 100001, lesson: 1001, score: 99}, {number: 100002, lesson: 1001, score: 88}, {number: 100003, lesson: 1001, score: 77}, {number: 100004, lesson: 1001, score: 66}, {number: 100005, lesson: 1001, score: 55}, ]; console.log(list); var buf = writeList(list); console.log(buf); // 輸出 <Buffer 01 86 a1 03 e9 63 01 86 a2 03 e9 58 01 86 a3 03 e9 4d 01 86 a4 03 e9 42 01 86 a5 03 e9 37> var ret = readList(buf); console.log(ret); /* 輸出 [ { number: 100001, lesson: 1001, score: 99 }, { number: 100002, lesson: 1001, score: 88 }, { number: 100003, lesson: 1001, score: 77 }, { number: 100004, lesson: 1001, score: 66 }, { number: 100005, lesson: 1001, score: 55 } ] */
lei-proto 模塊介紹
上面的例子中,當(dāng)每一條記錄的結(jié)構(gòu)有變化時(shí),我們需要修改readRecord()
和writeRecord()
,重新計(jì)算每一個(gè)字段在 Buffer 中的偏移量,當(dāng)記錄的字段比較復(fù)雜時(shí)很容易出錯(cuò)。為此我編寫了lei-proto模塊,它允許你通過(guò)簡(jiǎn)單定義每條記錄的結(jié)構(gòu)即可生成對(duì)應(yīng)的readRecord()
和`writeRecord()
函數(shù)。
首先執(zhí)行以下命令安裝此模塊:
$ npm install lei-proto --save
使用lei-proto模塊后,前文的例子可以改為這樣:
var parsePorto = require('lei-proto'); // 生成指定記錄結(jié)構(gòu)的數(shù)據(jù)編碼/解碼器 var record = parsePorto([ ['number', 'uint', 3], ['lesson', 'uint', 2], ['score', 'uint', 1] ]); function readList (buf) { var list = []; var offset = 0; while (offset < buf.length) { list.push(record.decode(buf.slice(offset, offset + 6))); offset += 6; } return list; } function writeList (list) { return Buffer.concat(list.map(record.encodeEx)); }
運(yùn)行與上文同樣的測(cè)試程序,可看到其結(jié)果是一樣的:
<Buffer 01 86 a1 03 e9 63 01 86 a2 03 e9 58 01 86 a3 03 e9 4d 01 86 a4 03 e9 42 01 86 a5 03 e9 37> [ { number: 100001, lesson: 1001, score: 99 }, { number: 100002, lesson: 1001, score: 88 }, { number: 100003, lesson: 1001, score: 77 }, { number: 100004, lesson: 1001, score: 66 }, { number: 100005, lesson: 1001, score: 55 } ]
關(guān)于lei-proto模塊的詳細(xì)使用方法可訪問(wèn)該模塊的主頁(yè)瀏覽:https://github.com/leizongmin/node-lei-proto
對(duì)此感興趣的讀者也可研究一下其實(shí)現(xiàn)原理。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Node.js五大應(yīng)用性能技巧小結(jié)(必須收藏)
本篇文章主要介紹了Node.js五大應(yīng)用性能技巧小結(jié)(必須收藏),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家2017-08-08關(guān)于node使用multer進(jìn)行文件的上傳與下載
這篇文章主要介紹了關(guān)于node使用multer進(jìn)行文件的上傳與下載,Multer是一個(gè)Node.js中間件,用于處理表單數(shù)據(jù)中的multipart/form-data類型,需要的朋友可以參考下2023-04-04Nodejs-child_process模塊詳細(xì)介紹
Node.js的child進(jìn)程模塊允許創(chuàng)建并行任務(wù),提高應(yīng)用性能,介紹了exec、execFile、spawn、fork等方法,解釋了它們的使用場(chǎng)景和優(yōu)勢(shì),通過(guò)子進(jìn)程模塊,可以執(zhí)行外部命令、腳本或創(chuàng)建新的Node.js實(shí)例,感興趣的朋友跟隨小編一起看看吧2024-09-09