這篇文章帶大家了解一下Node.js中的Buffer,看看Buffer結構、Buffer記憶體分配、Buffer的拼接等,希望對大家有幫助!
#JavaScript
對於字串的操作十分友善
#Buffer
是一個像Array
的對象,主要用於操作位元組。
Buffer
是一個典型的JavaScript和C 結合的模組,將效能相關部分用C 實現,將非效能相關部分用JavaScript實作。
Buffer所佔用的記憶體不是透過V8分配,屬於堆外記憶體。由於V8垃圾回收性能影響,將常用的操作對像用更有效率和專有的記憶體分配回收政策來管理是個不錯的思路。
Buffer在Node進程啟動時就已經價值,並且放在全域物件(global)上。所以使用buffer無需require引入
#Buffer物件的元素未16進位的兩位數,即0-255的數值
let buf01 = Buffer.alloc(8); console.log(buf01); // <Buffer 00 00 00 00 00 00 00 00>
可以使用fill
填充buf的值(預設為utf-8
編碼),如果填充的值超過buffer,將不會被寫入。
如果buffer長度大於內容,則會重複填入
如果想要清空先前填入的內容,可以直接fill()
buf01.fill('12345678910') console.log(buf01); // <Buffer 31 32 33 34 35 36 37 38> console.log(buf01.toString()); // 12345678
如果填入的內容是中文,在utf-8
的影響下,中文字會佔用3個元素,字母和半角標點符號佔用1個元素。
let buf02 = Buffer.alloc(18, '开始我们的新路程', 'utf-8'); console.log(buf02.toString()); // 开始我们的新
Buffer
受Array類型
影響很大,可以存取length屬性得到長度,也可以透過下標存取元素,也可以透過indexOf查看元素位置。
console.log(buf02); // <Buffer e5 bc 80 e5 a7 8b e6 88 91 e4 bb ac e7 9a 84 e6 96 b0> console.log(buf02.length) // 18字节 console.log(buf02[6]) // 230: e6 转换后就是 230 console.log(buf02.indexOf('我')) // 6:在第7个字节位置 console.log(buf02.slice(6, 9).toString()) // 我: 取得<Buffer e6 88 91>,转换后就是'我'
如果給位元組賦值不是0255之間的整數,或賦值時小數時,賦值小於0,則將該值逐次加256.直到得到0#255之間的整數。如果大於255,就逐次減去255。如果是小數,捨去小數部分(不做四捨五入)
Buffer
物件的記憶體分配不是在V8的堆記憶體中,而是在Node的C 層面實現記憶體的申請。因為處理大量的位元組資料不能採用需要一點記憶體就向作業系統申請一點記憶體的方式。為此Node在記憶體上使用的是在C 層級申請內存,並在JavaScript
中分配記憶體的方式
##Node採用了
slab分配機制,
slab是以中動態記憶體管理機制,目前在一些
*nix作業系統用中有廣泛的應用,例如
Linux
slab就是一塊申請好的固定大小的記憶體區域,slab有以下三種狀態:
8KB為界限來區分Buffer是大物件還是小物件
console.log(Buffer.poolSize); // 8192
#這個8KB的值就額是每個slab的大小值,在JavaScript層面,以它作為單位單元進行記憶體的分配
Buffer大小小於8KB,Node會依照小物件方式進行指派
物件1024KB,目前的
slab會被佔用1024KB,並且記錄下是從這個
slab的哪個位置開始使用的
對象,大小為3072KB。建構過程會判斷目前
slab剩餘空間是否足夠,如果足夠,使用剩餘空間,並更新
slab的分配狀態。 3072KB空間使用後,目前此slab剩餘空間4096KB。
,目前slab空間不足,會建構新的
slab (這會造成原slab剩餘空間浪費)
Buffer.alloc(1) Buffer.alloc(8192)
第一个slab
中只会存在1字节的buffer对象,而后一个buffer对象会构建一个新的slab存放
由于一个slab可能分配给多个Buffer对象使用,只有这些小buffer对象在作用域释放并都可以回收时,slab的空间才会被回收。 尽管只创建1字节的buffer对象,但是如果不释放,实际是8KB的内存都没有释放
小结:
真正的内存是在Node的C++层面提供,JavaScript层面只是使用。当进行小而频繁的Buffer操作时,采用slab的机制进行预先申请和时候分配,使得JavaScript到操作系统之间不必有过多的内存申请方面的系统调用。 对于大块的buffer,直接使用C++层面提供的内存即可,无需细腻的分配操作。
buffer在使用场景中,通常是以一段段的方式进行传输。
const fs = require('fs'); let rs = fs.createReadStream('./静夜思.txt', { flags:'r'}); let str = '' rs.on('data', (chunk)=>{ str += chunk; }) rs.on('end', ()=>{ console.log(str); })
以上是读取流的范例,data时间中获取到的chunk对象就是buffer对象。
但是当输入流中有宽字节编码(一个字占多个字节
)时,问题就会暴露。在str += chunk
中隐藏了toString()
操作。等价于str = str.toString() + chunk.toString()
。
下面将可读流的每次读取buffer长度限制为11.
fs.createReadStream('./静夜思.txt', { flags:'r', highWaterMark: 11});
输出得到:
上面出现了乱码,上面限制了buffer长度为11,对于任意长度的buffer而言,宽字节字符串都有可能存在被截断的情况,只不过buffer越长出现概率越低。
但是如果设置了encoding
为utf-8
,就不会出现此问题了。
fs.createReadStream('./静夜思.txt', { flags:'r', highWaterMark: 11, encoding:'utf-8'});
原因: 虽然无论怎么设置编码,流的触发次数都是一样,但是在调用setEncoding
时,可读流对象在内部设置了一个decoder对象
。每次data事件都会通过decoder对象
进行buffer到字符串的解码,然后传递给调用者。
string_decoder
模块提供了用于将 Buffer 对象解码为字符串(以保留编码的多字节 UTF-8 和 UTF-16 字符的方式)的 API
const { StringDecoder } = require('string_decoder'); let s1 = Buffer.from([0xe7, 0xaa, 0x97, 0xe5, 0x89, 0x8d, 0xe6, 0x98, 0x8e, 0xe6, 0x9c]) let s2 = Buffer.from([0x88, 0xe5, 0x85, 0x89, 0xef, 0xbc, 0x8c, 0x0d, 0x0a, 0xe7, 0x96]) console.log(s1.toString()); console.log(s2.toString()); console.log('------------------'); const decoder = new StringDecoder('utf8'); console.log(decoder.write(s1)); console.log(decoder.write(s2));
StringDecoder
在得到编码之后,知道了宽字节字符串在utf-8
编码下是以3个字节的方式存储的,所以第一次decoder.write
只会输出前9个字节转码的字符,后两个字节会被保留在StringDecoder
内部。
buffer在文件I/O和网络I/O中运用广泛,尤其在网络传输中,性能举足轻重。在应用中,通常会操作字符串,但是一旦在网络中传输,都需要转换成buffer,以进行二进制数据传输。 在web应用中,字符串转换到buffer是时时刻刻发生的,提高字符串到buffer的转换效率,可以很大程度地提高网络吞吐率。
如果通过纯字符串的方式向客户端发送,性能会比发送buffer对象更差,因为buffer对象无须在每次响应时进行转换。通过预先转换静态内容为buffer对象,可以有效地减少CPU重复使用,节省服务器资源。
可以选择将页面中动态和静态内容分离,静态内容部分预先转换为buffer的方式,使得性能得到提升。
在文件的读取时,highWaterMark
设置对性能影响至关重要。在理想状态下,每次读取的长度就是用户指定的highWaterMark
。
highWaterMark
大小对性能有两个影响的点:
更多node相关知识,请访问:nodejs 教程!!
以上是簡單理解Node.js中的Buffer模組的詳細內容。更多資訊請關注PHP中文網其他相關文章!