淺談Node.js:Buffer模塊
Javascript在客戶端對于unicode編碼的數(shù)據(jù)操作支持非常友好,但是對二進制數(shù)據(jù)的處理就不盡人意。Node.js為了能夠處理二進制數(shù)據(jù)或非unicode編碼的數(shù)據(jù),便設(shè)計了Buffer類,該類實現(xiàn)了Uint8Array接口,并對其進行了優(yōu)化,它的實例類似于整型數(shù)組,但是它的大小在創(chuàng)建后便不可調(diào)整。在介紹Buffer如何使用之前,先介紹幾個知識點。
1、V8引擎的內(nèi)存使用限制
V8引擎最大堆內(nèi)存使用在32位系統(tǒng)上默認為512M,在64位系統(tǒng)上是1GB,雖然可以使用--max-old-space-size
參數(shù)調(diào)整該值,但還是建議要用到大內(nèi)存的時候使用Buffer或Stream,因為Buffer的內(nèi)存分配不在V8的堆上。
2、單個Buffer實例大小限制
單個Buffer實例的大小最大數(shù)值為1GB-1(32位系統(tǒng))或2GB-1(64位系統(tǒng)),所以在創(chuàng)建Buffer實例的時候不能超過該值,或者使用readFile()
方法讀取大文件,否則將拋出RangeError錯誤。
3、8KB池
Nodejs在創(chuàng)建Buffer實例的時候,當用戶申請的空間大于8KB,會直接調(diào)用內(nèi)部的createUnsafeBuffer()
方法創(chuàng)建一個Buffer,如果申請的空間大于0且小于4KB,新的Buffer則會建立在當前的8kb SLAB上,并更新剩余空間,如下圖所示:
下面介紹Buffer API的簡單使用:
1、創(chuàng)建Buffer實例
使用Buffer.from(), Buffer.alloc(), Buffer.allocUnsafe()
等方法來創(chuàng)建一個Buffer實例,6.0版本以前直接使用構(gòu)造函數(shù)創(chuàng)建的方法new Buffer()
已被丟棄,不推薦使用,因為有可能會造成內(nèi)存泄漏。
方法Buffer.alloc(size[, fill[, encoding]])
,參數(shù)含義如下:
- size,指定buffer的長度,但不能超過buffer.kMaxLength,若不是數(shù)字則報錯
- fill,指定初始化buffer的值,默認為0
- encoding,如果fill是字符串,則該參數(shù)指定fill的編碼
使用如下所示:
const buf1 = Buffer.alloc(10); console.log(buf1);//<Buffer 00 00 00 00 00 00 00 00 00 00> const buf2 = Buffer.alloc(10,'hello'); console.log(buf2);//<Buffer 68 65 6c 6c 6f 68 65 6c 6c 6f> const buf3 = Buffer.alloc(10,'hello','base64'); console.log(buf3);//<Buffer 85 e9 65 85 e9 65 85 e9 65 85>
方法Buffer.allocUnsafe(size)
,size參數(shù)指定buffer的大小,該方法返回一個沒有初始化的buffer,因此可能還保留有敏感的數(shù)據(jù),造成信息的泄漏,建議使用buffer.fill(0)
函數(shù)初始化buffer,該方法與Buffer.alloc(size, fill)是不一樣的,有可能使用8KB池。使用如下所示:
const buf4 = Buffer.allocUnsafe(10); console.log(buf4);//<Buffer 68 fb 4d 00 00 00 00 00 08 00>,可以看出是有數(shù)據(jù)的 buf4.fill(0); console.log(buf4);//<Buffer 00 00 00 00 00 00 00 00 00 00>
方法Buffer.allocUnsafeSlow(size)
,參數(shù)含義同上,該方法不會使用Buffer池,容易造成內(nèi)存的浪費,使用如下所示:
const buf5 = Buffer.allocUnsafeSlow(10); console.log(buf5);//<Buffer 38 00 24 00 00 00 00 00 00 00>
方法Buffer.from(value,[...])
,這里分為四種情況,如下所示:
第一,value為16進制數(shù)組,將數(shù)組轉(zhuǎn)化為buffer,如果不是16進制,則會進行轉(zhuǎn)換,如下:
const buf6 = Buffer.from([1,2,3,5,17]); console.log(buf6);//<Buffer 01 02 03 05 11>
第二,value為字符串,則轉(zhuǎn)換字符串為buffer,該方法會使用buffer池,如下:
const buf7 = Buffer.from('hello world!'); console.log(buf7);//<Buffer 01 02 03 05 11>
第三,value為buffer實例,則將value拷貝至新的buffer中,這里只是值的拷貝,不會共享內(nèi)存,如下:
const buf8 = Buffer.from('hello world'); const buf9 = Buffer.from(buf8); console.log(buf8);//<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64> console.log(buf9);//<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64> buf9[0] = 0x66; console.log(buf8);//<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64> console.log(buf9);//<Buffer 66 65 6c 6c 6f 20 77 6f 72 6c 64>
第四,value為arrayBuffer時,還有兩個可選參數(shù)[, byteOffset[, length]],byteOffset指定從arrayBuffer開始復(fù)制的位置,length復(fù)制的長度。如下:
const arr = new Uint8Array(2); arr[0] = 128; arr[1] = 200; const buf10 = Buffer.from(arr,0,2); console.log(buf10);//<Buffer 80 c8>
如果引用的是arr.buffer,則新創(chuàng)建的buffer buf10與arr共享內(nèi)存,如下:
const arr = new Uint8Array(2); arr[0] = 128; arr[1] = 200; const buf10 = Buffer.from(arr.buffer); arr[0] = 254; console.log(buf10);//<Buffer fe c8>
2、buffer解碼
使用buf.toString([encoding[, start[, end]]])
方法將buffer轉(zhuǎn)換成字符串,encoding指定字符編碼,默認為'utf8',start開始位置,end結(jié)束位置(不包括),目前encoding只支持'ascii,utf8,utf16le,ucs2,base64,latin1,binary,hex',使用如下所示:
const buf12 = Buffer.from('我愛中國'); console.log(buf12.toString('base64'));//5oiR54ix5Lit5Zu9 console.log(buf12.toString('utf8'));//我愛中國 console.log(buf12.toString('hex'));//e68891e788b1e4b8ade59bbd
3、buffer拼接、復(fù)制、填充、分割
方法buf.fill(value[, offset[, end]][, encoding])
使用指定的值填充buffer,參數(shù)offset指定填充的起始位置,end為結(jié)束位置,使用如下所示:
console.log(Buffer.allocUnsafe(5).fill('a').toString());//aaaaa console.log(Buffer.allocUnsafe(5).fill(65).toString('utf8'));//AAAAA
方法Buffer.concat(list[, totalLength])
將多個buffer合并在一起,并返回一個新的buffer實例,參數(shù)totalLength為指定的buffers的長度總和,如果不提供該值,函數(shù)內(nèi)部會循環(huán)去獲取每一個buffer的長度,然后進行拼接,因此為了速度,最好指定一個總長度,使用如下:
function bufferInjoin(buffArr){ var len = 0; buffArr.forEach((buff,idx,arr)=>{ len+=buff.length; }); var buffer = Buffer.concat(buffArr,len); return buffer; } var buff = bufferInjoin([Buffer.from('hehe'),Buffer.allocUnsafe(5).fill('a')]); console.log(buff);//<Buffer 68 65 68 65 61 61 61 61 61> console.log(buff.length);//9 console.log(buff.toString());//heheaaaaa
方法buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])
可以實現(xiàn)buf到target的復(fù)制,參數(shù)含義如下:
- target,復(fù)制目標
- targetStart,復(fù)制目標開始被覆蓋的位置
- sourceStart,復(fù)制源開始復(fù)制的位置
- sourceEnd,復(fù)制源復(fù)制結(jié)束的位置
使用如下所示:
const buf1 = Buffer.from('hello world!'); const buf2 = Buffer.allocUnsafe(5).fill('x'); buf1.copy(buf2,0,0,5); console.log(buf2.toString());//hello
方法buf.slice([start[, end]])
可以分割buffer,返回一個新的buffer,但是仍然是引用原buffer,因此改變原buffer數(shù)據(jù),該新buffer也會跟著改變,如果參數(shù)start,end為負數(shù),則先要加上buffer的長度再進行計算,如下所示:
const buf1 = Buffer.from('hello world.'); const buf2 = buf1.slice(0); console.log(buf2);//<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64 2e> buf2[0] = 88; console.log(buf1);//<Buffer 58 65 6c 6c 6f 20 77 6f 72 6c 64 2e> const buf3 = buf1.slice(-6,-1); console.log(buf3.toString());//world
3、buffer讀寫
buffer寫操作通過write開頭的寫api來完成,主要有以下這些:
- buf.write(string[, offset[, length]][, encoding]),向buffer寫入字符串
- buf.writeDoubleBE(value, offset[, noAssert])寫入64位浮點型數(shù)字,大端對齊
- buf.writeDoubleLE(value, offset[, noAssert]),寫入64位浮點型數(shù)字,小端對齊
- buf.writeFloatBE(value, offset[, noAssert]),寫入32位浮點型數(shù)字,大端對齊
- buf.writeFloatLE(value, offset[, noAssert]),寫入32位浮點型數(shù)字,小端對齊
- buf.writeInt8(value, offset[, noAssert]),寫入有符號8位整型數(shù)字
- buf.writeInt16BE(value, offset[, noAssert]),寫入有符號16位整型數(shù)字,大端對齊
- buf.writeInt16LE(value, offset[, noAssert]),寫入有符號16位整型數(shù)字,小端對齊
- buf.writeInt32BE(value, offset[, noAssert]),寫入有符號32位整型數(shù)字,大端對齊
- buf.writeInt32LE(value, offset[, noAssert]),寫入有符號32位整型數(shù)字,小端對齊
- buf.writeIntBE(value, offset, byteLength[, noAssert]),以下便不再累述
- buf.writeIntLE(value, offset, byteLength[, noAssert])
- buf.writeUInt8(value, offset[, noAssert])
- buf.writeUInt16BE(value, offset[, noAssert])
- buf.writeUInt16LE(value, offset[, noAssert])
- buf.writeUInt32BE(value, offset[, noAssert])
- buf.writeUInt32LE(value, offset[, noAssert])
- buf.writeUIntBE(value, offset, byteLength[, noAssert])
- buf.writeUIntLE(value, offset, byteLength[, noAssert])
buffer讀操作由read開頭的api完成,主要有以下這些:
- buf.readDoubleBE(offset[, noAssert])
- buf.readDoubleLE(offset[, noAssert])
- buf.readFloatBE(offset[, noAssert])
- buf.readFloatLE(offset[, noAssert])
- buf.readInt8(offset[, noAssert])
- buf.readInt16BE(offset[, noAssert])
- buf.readInt16LE(offset[, noAssert])
- buf.readInt32BE(offset[, noAssert])
- buf.readInt32LE(offset[, noAssert])
- buf.readIntBE(offset, byteLength[, noAssert])
- buf.readIntLE(offset, byteLength[, noAssert])
- buf.readUInt8(offset[, noAssert])
- buf.readUInt16BE(offset[, noAssert])
- buf.readUInt16LE(offset[, noAssert])
- buf.readUInt32BE(offset[, noAssert])
- buf.readUInt32LE(offset[, noAssert])
- buf.readUIntBE(offset, byteLength[, noAssert])
- buf.readUIntLE(offset, byteLength[, noAssert])
使用如下所示,以32無符號整型為例:
const buf = Buffer.allocUnsafe(8); buf.writeUInt32BE(0x12345678,0) console.log(buf); const data = buf.readUInt32BE(0); console.log(data.toString(16));
最后利用buffer讀API完成一個獲取PNG格式圖片尺寸的小工具,在開始編碼之前,先簡單介紹下PNG文件組成,如下所示:
PNG文件標志 | PNG數(shù)據(jù)塊 | …… | PNG數(shù)據(jù)塊 |
---|
這里我們只要用到PNG文件標識和PNG數(shù)據(jù)塊的第一個塊IHDR文件頭數(shù)據(jù)塊。文件標識是固定的8個字節(jié),為89 50 4E 47 0D 0A 1A 0A
,IHDR數(shù)據(jù)塊的長度為13個字節(jié),格式如下:
域的名稱 | 字節(jié)數(shù) | 說明 |
---|---|---|
Width | 4 bytes | 寬度 |
Height | 4 bytes | 高度 |
Bit depth | 1 bytes | 圖像深度 |
ColorType | 1 bytes | 顏色類型 |
Compression method | 1 bytes | 壓縮方法 |
Filter method | 1 bytes | 濾波器方法 |
Interlace method | 1 bytes | 隔行掃描方法 |
開始編碼,如下所示:
const fs = require('fs'); const path = require('path'); const argvs = process.argv.slice(2); if(argvs.length<=0){ console.error('請輸入圖片:png.js img1 img2 ...'); process.exit(-1); } argvs.forEach((img,idx,arr)=>{ var stat = fs.statSync(img); fs.open(img,'r',(err,fd)=>{ if(err) throw err; var buff = Buffer.alloc(stat.size); fs.read(fd,buff,0,stat.size,0,(err, bytesRead, buffer)=>{ if(err) throw err; fs.close(fd,()=>{}); getImgDimension(buff,(err,dimension)=>{ if(err) throw err; console.log(`${img}的尺寸為:${dimension.width}x${dimension.height}`); }); }); }); }); function getImgDimension(buff,cb){ if((buff.toString('utf8',1,8) === 'PNG\r\n\x1a\n') && (buff.toString('utf8',12,16) === 'IHDR')){ return cb(null,{ width:buff.readUInt32BE(16), height:buff.readUInt32BE(20) }),!0; }else{ return cb(new Error('不是PNG圖片'),{}),!1; } }
執(zhí)行結(jié)果如下:
E:\developmentdocument\nodejsdemo>node png.js 20160824083157.png 下載.png
20160824083157.png的尺寸為:195x195
下載.png的尺寸為:720x600
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
NodeJS連接MongoDB數(shù)據(jù)庫時報錯的快速解決方法
下面小編就為大家?guī)硪黄狽odeJS連接MongoDB數(shù)據(jù)庫時報錯的快速解決方法。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考2016-05-05Nuxt配合Node在實際生產(chǎn)中的應(yīng)用詳解
這篇文章主要介紹了Nuxt配合Node在實際生產(chǎn)中的應(yīng)用詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08