Node.js缓冲模块Buffer
前言
Javascript是為瀏覽器而設計的,能很好的處理unicode編碼的字符串,但對于二進制或非unicode編碼的數據就顯得無能為力。 Node.js繼承Javascript的語言特性,同時又擴展了Javascript語言,為二進制的數據處理提供了Buffer類,讓Node.js 可以像其他程序語言一樣,能處理各種類型的數據了。
網上有很多講Buffer的文章,大都講的是原理,怎么使用幾乎找不到,文章將重點介紹Buffer的使用。
目錄
1. Buffer介紹
在Node.js中,Buffer類是隨Node內核一起發布的核心庫。Buffer庫為Node.js帶來了一種存儲原始數據的方法,可以讓 Nodejs處理二進制數據,每當需要在Nodejs中處理I/O操作中移動的數據時,就有可能使用Buffer庫。原始數據存儲在 Buffer 類的實例中。一個 Buffer 類似于一個整數數組,但它對應于 V8 堆內存之外的一塊原始內存。
Buffer 和 Javascript 字符串對象之間的轉換需要顯式地調用編碼方法來完成。以下是幾種不同的字符串編碼:
- ‘ascii’ – 僅用于 7 位 ASCII 字符。這種編碼方法非常快,并且會丟棄高位數據。
- ‘utf8′ – 多字節編碼的 Unicode 字符。許多網頁和其他文件格式使用 UTF-8。
- ‘ucs2′ – 兩個字節,以小尾字節序(little-endian)編碼的 Unicode 字符。它只能對 BMP(基本多文種平面,U+0000 – U+FFFF) 范圍內的字符編碼。
- ‘base64′ – Base64 字符串編碼。
- ‘binary’ – 一種將原始二進制數據轉換成字符串的編碼方式,僅使用每個字符的前 8 位。這種編碼方法已經過時,應當盡可能地使用 Buffer 對象。Node 的后續版本將會刪除這種編碼。
Buffer官方文檔:http://nodejs.org/api/buffer.html
2. Buffer的基本使用
Buffer的基本使用,主要就是API所提供的操作,主要包括3個部分 創建Buffer類、讀Buffer、寫Buffer。由于基本操作在官方文檔中詳細的使用介紹,我只是簡單列舉一下。
系統環境
- Win7 64bit
- Nodejs:v0.10.31
- Npm:1.4.23
創建項目
~ cd D:\workspace\javascript> ~ D:\workspace\javascript>mkdir nodejs-buffer && cd nodejs-buffer2.1 創建Buffer類
要創建一個Buffer的實例,我們要通過new Buffer來創建。新建文件buffer_new.js。
~ vi buffer_new.js// 長度為0的Buffer實例 var a = new Buffer(0); console.log(a); > <Buffer >// 長度為0的Buffer實例相同,a1,a2是一個實例 var a2 = new Buffer(0); console.log(a2); > <Buffer >// 長度為10的Buffer實例 var a10 = new Buffer(10); console.log(a10); > <Buffer 22 37 02 00 00 00 00 04 00 00>// 數組 var b = new Buffer(['a','b',12]) console.log(b); > <Buffer 00 00 0c>// 字符編碼 var b2 = new Buffer('你好','utf-8'); console.log(b2); > <Buffer e4 bd a0 e5 a5 bd>Buffer類有5個類方法,用于Buffer類的輔助操作。
1) 編碼檢查,上文中提到Buffer和Javascript字符串轉換時,需要顯式的設置編碼,那么這幾種編碼類型是Buffer所支持的。像中文處理只能使用utf-8編碼,對于幾年前常用的gbk,gb2312等編碼是無法解析的。
// 支持的編碼 console.log(Buffer.isEncoding('utf-8')) console.log(Buffer.isEncoding('binary')) console.log(Buffer.isEncoding('ascii')) console.log(Buffer.isEncoding('ucs2')) console.log(Buffer.isEncoding('base64')) console.log(Buffer.isEncoding('hex')) # 16制進 > true//不支持的編碼 console.log(Buffer.isEncoding('gbk')) console.log(Buffer.isEncoding('gb2312')) > false2) Buffer檢查,很多時候我們需要判斷數據的類型,對應后續的操作。
// 是Buffer類 console.log(Buffer.isBuffer(new Buffer('a'))) > true// 不是Buffer console.log(Buffer.isBuffer('adfd')) console.log(Buffer.isBuffer('\u00bd\u00bd')) > false3) 字符串的字節長度,由于字符串編碼不同,所以字符串長度和字節長度有時是不一樣的。比如,1個中文字符是3個字節,通過utf-8編碼輸出就是4個中文字符,占12個字節。
var str2 = '粉絲日志'; console.log(str2 + ": " + str2.length + " characters, " + Buffer.byteLength(str2, 'utf8') + " bytes"); > 粉絲日志: 4 characters, 12 bytes console.log(str2 + ": " + str2.length + " characters, " + Buffer.byteLength(str2, 'ascii') + " bytes"); > 粉絲日志: 4 characters, 4 bytes4) Buffer的連接,用于連接Buffer的數組。我們可以手動分配Buffer對象合并后的Buffer空間大小,如果Buffer空間不夠了,則數據會被截斷。
var b1 = new Buffer("abcd"); var b2 = new Buffer("1234"); var b3 = Buffer.concat([b1,b2],8); console.log(b3.toString()); > abcd1234var b4 = Buffer.concat([b1,b2],32); console.log(b4.toString()); console.log(b4.toString('hex'));//16進制輸出 > abcd1234 亂碼.... > 616263643132333404000000000000000000000000000000082a330200000000var b5 = Buffer.concat([b1,b2],4); console.log(b5.toString()); > abcd程序運行的截圖
5) Buffer的比較,用于Buffer的內容排序,按字符串的順序。
var a1 = new Buffer('10'); var a2 = new Buffer('50'); var a3 = new Buffer('123');// a1小于a2 console.log(Buffer.compare(a1,a2)); > -1// a2小于a3 console.log(Buffer.compare(a2,a3)); > 1// a1,a2,a3排序輸出 console.log([a1,a2,a3].sort(Buffer.compare)); > [ <Buffer 31 30>, <Buffer 31 32 33>, <Buffer 35 30> ]// a1,a2,a3排序輸出,以utf-8的編碼輸出 console.log([a1,a2,a3].sort(Buffer.compare).toString()); > 10,123,502.2 寫入Buffer
把數據寫入到Buffer的操作,新建文件buffer_write.js。
~ vi buffer_write.js// // Buffer寫入 //// 創建空間大小為64字節的Buffer var buf = new Buffer(64);// 從開始寫入Buffer,偏移0 var len1 = buf.write('從開始寫入');// 打印數據的長度,打印Buffer的0到len1位置的數據 console.log(len1 + " bytes: " + buf.toString('utf8', 0, len1));// 重新寫入Buffer,偏移0,將覆蓋之前的Buffer內存 len1 = buf.write('重新寫入'); console.log(len1 + " bytes: " + buf.toString('utf8', 0, len1));// 繼續寫入Buffer,偏移len1,寫入unicode的字符串 var len2 = buf.write('\u00bd + \u00bc = \u00be',len1); console.log(len2 + " bytes: " + buf.toString('utf8', 0, len1+len2));// 繼續寫入Buffer,偏移30 var len3 = buf.write('從第30位寫入', 30); console.log(len3 + " bytes: " + buf.toString('utf8', 0, 30+len3));// Buffer總長度和數據 console.log(buf.length + " bytes: " + buf.toString('utf8', 0, buf.length));// 繼續寫入Buffer,偏移30+len3 var len4 = buf.write('寫入的數據長度超過Buffer的總長度!',30+len3);// 超過Buffer空間的數據,沒有被寫入到Buffer中 console.log(buf.length + " bytes: " + buf.toString('utf8', 0, buf.length));Node.js的節點的緩沖區,根據讀寫整數的范圍,提供了不同寬度的支持,使從1到8個字節(8位、16位、32位)的整數、浮點數(float)、雙 精度浮點數(double)可以被訪問,分別對應不同的writeXXX()函數,使用方法與buf.write()類似。
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])另外,關于Buffer寫入操作,還有一些Buffer類的原型函數可以操作。
Buffer復制函數 buf.copy(targetBuffer[, targetStart][, sourceStart][, sourceEnd])。
// 新建兩個Buffer實例 var buf1 = new Buffer(26); var buf2 = new Buffer(26);// 分別向2個實例中寫入數據 for (var i = 0 ; i < 26 ; i++) {buf1[i] = i + 97; // 97是ASCII的abuf2[i] = 50; // 50是ASCII的2 }// 把buf1的內存復制給buf2 buf1.copy(buf2, 5, 0, 10); // 從buf2的第5個字節位置開始插入,復制buf1的從0-10字節的數據到buf2中 console.log(buf2.toString('ascii', 0, 25)); // 輸入buf2的0-25字節 > 22222abcdefghij2222222222Buffer填充函數 buf.fill(value[, offset][, end])。
// 新建Buffer實例,長度20節節 var buf = new Buffer(20);// 向Buffer中填充數據 buf.fill("h"); console.log(buf) > <Buffer 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68 68> console.log("buf:"+buf.toString()) > buf:hhhhhhhhhhhhhhhhhhhh // 清空Buffer中的數據 buf.fill(); console.log("buf:"+buf.toString()) > buf:Buffer裁剪,buf.slice([start][, end])。返回一個新的緩沖區,它和舊緩沖區指向同一塊內存,但是從索引 start 到 end 的位置剪裁。
var buf1 = new Buffer(26); for (var i = 0 ; i < 26 ; i++) {buf1[i] = i + 97; }// 從剪切buf1中的0-3的位置的字節,新生成的buf2是buf1的一個切片。 var buf2 = buf1.slice(0, 3); console.log(buf2.toString('ascii', 0, buf2.length)); > abc// 當修改buf1時,buf2同時也會發生改變 buf1[0] = 33; console.log(buf2.toString('ascii', 0, buf2.length)); > !bc2.3 讀取Buffer
我們把數據寫入Buffer后,我們還需要把數據從Buffer中讀出來,新建文件buffer_read.js。我們可以通過readXXX() 函數獲得對應該寫入時編碼的索引值,再轉換原始值取出,有這種方法操作中文字符就會變得麻煩,最常用的讀取Buffer的方法,其實就是 toString()。
~ vi buffer_read.js// // Buffer 讀取 //var buf = new Buffer(10); for (var i = 0 ; i < 10 ; i++) {buf[i] = i + 97; } console.log(buf.length + " bytes: " + buf.toString('utf-8')); > 10 bytes: abcdefghij// 讀取數據 for (ii = 0; ii < buf.length; ii++) {var ch = buf.readUInt8(ii); // 獲得ASCII索引console.log(ch + ":"+ String.fromCharCode(ch)); } > 97:a 98:b 99:c 100:d 101:e 102:f 103:g 104:h 105:i 106:j寫入中文數據,以readXXX進行讀取,會3個字節來表示一個中文字。
var buf = new Buffer(10); buf.write('abcd') buf.write('數據',4) for (var i = 0; i < buf.length; i++) {console.log(buf.readUInt8(i)); }>97 98 99 100 230 // 230,149,176 代表“數” 149 176 230 // 230,141,174 代表“據” 141 174如果想輸出正確的中文,那么我們可以用toString(‘utf-8′)的函數來操作。
console.log("buffer :"+buf); // 默認調用了toString()的函數 > buffer :abcd數據 console.log("utf-8 :"+buf.toString('utf-8')); > utf-8 :abcd數據 console.log("ascii :"+buf.toString('ascii'));//有亂碼,中文不能被正確解析 > ascii :abcdf0f . console.log("hex :"+buf.toString('hex')); //16進制 > hex :61626364e695b0e68dae對于Buffer的輸出,我們用的最多的操作就是toString(),按照存入的編碼進行讀取。除了toString()函數,還可以用toJSON()直接Buffer解析成JSON對象。
var buf = new Buffer('test'); console.log(buf.toJSON()); > { type: 'Buffer', data: [ 116, 101, 115, 116 ] }3. Buffer的性能測試
通過上文中對Buffer的介紹,我們已經了解了Buffer的基本使用,接下來,我們要開始做Buffer做一些測試。
3.1 8K的創建測試
每次我們創建一個新的Buffer實例時,都會檢查當前Buffer的內存池是否已經滿,當前內存池對于新建的Buffer實例是共享的,內存池的大小為8K。
如果新創建的Buffer實例大于8K時,就把Buffer交給SlowBuffer實例存儲;如果新創建的Buffer實例小于8K,同時小于當 前內存池的剩余空間,那么這個Buffer存入當前的內存池;如果Buffer實例不大0,則統一返回默認的zerobuffer實例。
下面我們創建2個Buffer實例,第一個是以4k為空間,第二個以4.001k為空間,循環創建10萬次。
var num = 100*1000; console.time("test1"); for(var i=0;i<num;i++){new Buffer(1024*4); } console.timeEnd("test1"); > test1: 132msconsole.time("test2"); for(var j=0;j<num;j++){new Buffer(1024*4+1); } console.timeEnd("test2"); > test2: 163ms第二個以4.001k為空間的耗時多23%,這就意味著第二個,每二次循環就要重新申請一次內存池的空間。這是需要我們非常注意的。
3.2 多Buffer還是單一Buffer
當我們需要對數據進行緩存時,創建多個小的Buffer實例好,還是創建一個大的Buffer實例好?比如我們要創建1萬個長度在1-2048之間不等的字符串。
var max = 2048; //最大長度 var time = 10*1000; //循環1萬次// 根據長度創建字符串 function getString(size){var ret = ""for(var i=0;i<size;i++) ret += "a";return ret; }// 生成字符串數組,1萬條記錄 var arr1=[]; for(var i=0;i<time;i++){var size = Math.ceil(Math.random()*max)arr1.push(getString(size)); } //console.log(arr1);// 創建1萬個小Buffer實例 console.time('test3'); var arr_3 = []; for(var i=0;i<time;i++){arr_3.push(new Buffer(arr1[i])); } console.timeEnd('test3'); > test3: 217ms// 創建一個大實例,和一個offset數組用于讀取數據。 console.time('test4'); var buf = new Buffer(time*max); var offset=0; var arr_4=[]; for(var i=0;i<time;i++){arr_4[i]=offset;buf.write(arr1[i],offset,arr1[i].length);offset=offset+arr1[i].length; } console.timeEnd('test4'); > test4: 12ms讀取索引為2的數據。
console.log("src:[2]="+arr1[2]); console.log("test3:[2]="+arr_3[2].toString()); console.log("test4:[2]="+buf.toString('utf-8',arr_4[2],arr_4[3]));運行結果如圖所示。
對于這類的需求來說,提前生成一個大的Buffer實例進行存儲,要比每次生成小的Buffer實例高效的多,能提升一個數量級的計算效率。所以,理解并用好Buffer是非常重要的!!
?
3.3 string VS Buffer
?
有了Buffer我們是否需求把所有String的連接,都換成Buffer的連接?那么我們就需要測試一下,String和Buffer做字符串連接時,哪個更快一點?
?
下面我們進行字符串連接,循環30萬次。
//測試三,Buffer VS string var time = 300*1000; var txt = "aaa"var str = ""; console.time('test5') for(var i=0;i<time;i++){str += txt; } console.timeEnd('test5') > test5: 24msconsole.time('test6') var buf = new Buffer(time * txt.length) var offset = 0; for(var i=0;i<time;i++){var end = offset + txt.length;buf.write(txt,offset,end);offset=end; } console.timeEnd('test6') > test6: 85ms從測試結果,我們可以明顯的看到,String對字符串的連接操作,要遠快于Buffer的連接操作。所以我們在保存字符串的時候,該用string還是要用string。那么只有在保存非utf-8的字符串以及二進制數據的情況,我們才用Buffer。
6. 程序代碼
本文的程序代碼,可以直接從Github上面下載本文項目中的源代碼,按照片文章中的介紹學習buffer,下載地址:https://github.com/bsspirit/nodejs-buffer
也可以直接用github命令行來下載:
~ git clone git@github.com:bsspirit/nodejs-buffer.git # 下載github項目 ~ cd nodejs-buffer # 進入下載目錄關于Node.js的底層,本人接觸并不多,未能從V8(C++)的做更深入的研究,僅僅在使用層次上,寫出我的總結。對于文中的錯誤或描述不清楚的地方,還請大牛予以指正!!
參考文章:
淺析nodejs的buffer類
轉載請注明出處:
http://blog.fens.me/nodejs-buffer/
?
轉載于:https://www.cnblogs.com/duhuo/p/4538568.html
總結
以上是生活随笔為你收集整理的Node.js缓冲模块Buffer的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于z-index的总结
- 下一篇: nosql-redis学习 数据类型