- 什么是Buffer
- Buffer的結構
- Buffer對象API解析
- 解決Buffer拼接時導致的亂碼問題
- Buffer的性能
一、什么是Buffer?
首先Buffer是nodejs全局上的一個內置模塊,可以直接在不用require引入就可以直接調用的模塊。
Buffer的作用就是讓JavaScript可以直接操作二進制數據,關於Buffer就離不開一些關鍵名詞:二進制、流、管道、IO。關於二進制不需要做太多的解釋了,像圖片、文件這些都是以二進制的方式存儲在磁盤中。當需要使用這些資源的時候,就是要去拿到這些數據,拿數據的這個操作就是讀數據。拿到數據后就需要提供給程序,讓這些數據作用到具體的應用上,而作用到具體應用的數據基本上不是直接使用二進制數據,而是應用能使用的另一種編碼格式的數據。並且當這個數據被作用到具體應用后,這個數據就需要以這個編碼格式一直保持被引用的狀態,直到應用不在使用這個數據然后被系統回收。
從上面使用數據的過程來看就可以明白,拿到二進制數據后需要轉換編碼格式,然后還需要一個存放這個數據物理資源,這個物理資源通常被稱作緩存,所以很多時候也將Buffer稱作緩存。
從二進制數據到其他進制的編碼格式,然后在被作用到具體應用,這就是數據從二進制到具體應用表達的過程,這個過程可以理解為一個流程,這個流程里可能還會有程序添加的一些其他操作,這一系列過程被簡稱為流。(在nodejs中有一個專門用於處理流操作的模塊Stream,后面具體解析)
在很多數據操作的時候並不是一次性將一個二進制文件全部讀出來,更多可能是基於程序的需要一點點的讀取,這時候就需要一個程序邏輯來處理這種操作,將每一個需要的讀取文件流程按照邏輯作用到具體應用中,這個程序邏輯我們將其稱作管道。(在nodejs中的FS模塊中有一個pipe方法,這個方法就可以簡單的理解為管道)
反之,向磁盤存儲數據也一樣,將上面描述的數據讀取操作反過來就是文件寫操作。文件的讀寫操作就被稱為IO。(在nodejs中負責處理文件操作的FS模塊,就是nodejs讀寫操作“IO”的具體表達)
二、Buffer的結構
上面解析了什么是Buffer和與其相關的一些概念,為了更深入的了解nodejs中的Buffer,還需要了解Buffer這個模塊在nodejs的結構。
2.1Buffer模塊結構
前面說過Buffer是全局作用域上的一個模塊,可以理解為它是全局上的一個屬性,這個屬性引用着Bufeer模塊對象,從這個角度來說它是一個JavaScript模塊。但是JavaScript自身不具備直接操作二進制文件的能力,所以實質上Buffer在nodejs底層上還有一個C++模塊。這是因為IO操作是非常消耗性能的,所以nodejs在Buffer模塊構建設計上,將性能部分用C++實現,將非性能部分用JavaScript實現。如圖所示:
2.2Buffer對象結構
Buffer在nodejs的javaScript中是一個對象,與數組非常類似,它的元素為16進制的兩位數,即0到255的數值。
let str = "nodejs的學習路徑"; let buf = new Buffer(str,'utf-8'); console.log(buf); //打印結果:<Buffer 6e 6f 64 65 6a 73 e7 9a 84 e5 ad a6 e4 b9 a0 e8 b7 af e5 be 84>
不同編碼的字符占用的元素各不相同,utf-8編碼漢字占用3個元素,字母和半角標點符號占用1個元素,所以上面的str轉成Buffer由21個元素組成。根數組一樣,Buffer可以使用length屬性獲取長度,也可以通過下標訪問元素,構造Buffer對象時也可以和數組一樣直接設置長度。
console.log(buf.length); //21 console.log(buf[3]); //101 console.log(new Buffer(30));//<Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>
了解了Buffer對象的基本結構,接着就來看看它的元素取值策略,前面說過它的元素為16進制,那它在不賦值的情況下會如何取值?如果超出16進制取值范圍會發生什么?先來看下面的測試代碼:
let buf = new Buffer(10); console.log(buf[10]);//undefined buf[0] = -10; buf[1] = -260; buf[2] = 260; buf[3] = 515; console.log(buf); //<Buffer f6 fc 04 03 00 00 00 00 00 00>
當使用new Buffer()創建一個新的空Buffer對象時,它的元素值nodejs8版本前是0~255之間的隨機值(為什么是隨機值后面會解釋),之后時0。然后是關於Buffer對象元素的賦值,前面說過Buffer對象元素是16進制的兩位數,即0~255。
賦值小於0時,就將該值逐次加256直至這個元素值大於或等於0。所以buf[0]賦值為-10實際上的取值是-10+256=246,即f6;賦值為-260的buf[1]實際取值是-260+256+256=252。
賦值大與256時,就將該值逐次減去256直至這個元素值小於或等於256。所以buf[2]賦值為260實際取值是260-256=4,即04;賦值為515的buf[3]實際取值是515-256-256=3,即03。
2.3Buff對象在內存中的結構,即Buffer的內存分配:
在介紹這一部分內容之前,建議先了解nodejs的內存機制,不然相關內容不好理解,參考博客: nodejs內存控制 。
還記得在nodejs內存控制中通過process.memoryUsage()方法獲取當前進程的內存使用情況,它返回的對象中包含一個屬性arrayBuffer,在nodejs內存控制那篇博客中講其稱為獨立內存,也就是不受v8進程內存限制的內存使用大小。而接下來所說的Buffer對象使用的內存就都是指這個獨立的內存,不使用獨立內存或其他描述區分。
Buffer對象的內存分配不是v8的堆內存中,而是在nodejs的C++層面實現內存的申請。因為需要處理大量的字節數據不能采用一點內存就向系統申請一點內存,這會導致大量的內存申請的系統調用,對操作系統有一定壓力。為此Nodejs在內存使用上應用的是C++層面申請內存、在JavaScript中分配內存的策略。
為了高效的使用申請來的內存,nodejs采用了slab分配機制。slab是一種動態內存管理機制,最早誕生於SunOS操作系統(Solaris)中,目前*nix操作系統有廣泛的應用。
slab就是一塊申請好的固定大小的內存區域,slab有以下三種狀態:full(完全分配狀態)、partial(部分分配狀態)、empty(沒有被分配狀態)。
在nodejs中默認8KB為界限來區分Buffer是大對象還是小對象,這個8KB的值也就是每個slab的大小值,在JavaScript層面以它作為單位單元進行內存分配。
console.log(Buffer.poolSize); //8192
分配小Buffer對象:
如果指定Buffer的大小少於8KB,nodejs會按照小對象的方式分配,所謂小對象分配就是如果當前的slab已經申請的一塊內存的剩余空間,大於當前的Buffer需要的內存,就直接將這個Buffer分配到這個slab中。如果當前Buffer對象需要的內存大於當前slab的剩余空間,就重新申請一個全新的slab來存儲Buffer對象的數據。並且之前沒有被分配完的slab不在接受內存分配,這部分沒有被分配的內存就會被閑置。
分配大Buffer對象:
如果需要超過8KB的Buffer對象,將會直接分配一個對應Buffer大小的slab單元,這個Buffer對象會獨占該slab單元。
2.4創建Buffer對象(參考官方V16版本文檔)
在nodejs中創建Buffer有四種方式,首先分別來介紹四種方式:Buffer.from()、Buffer.alloc()、Buffer.allocUnsafe()、Buffer.allocUnsafeSlow()。可能這里會疑惑為什么沒有new Buffer(),這種方式雖然依然可以創建Buffer對象,但在新版本中已經被上面的四個Buffer類的靜態方法替代了,在新版本中使用new Buffer()會出現警告提示。
--Buffer.from():接收數據創建Buffer對象,Buffer內存大小由傳入由傳入的數據決定。只要其需要的內存大小少於當前已創建的Slab剩余內存空間,就直接使用當前的Slab中的剩余空間。
--Buffer.alloc():創建指定大小的Buffer對象,並初始化Buffer。只要其指定的大小少於已創建的Slab剩余內存空間,就直接使用當前Slab中的剩余空間,並對使用的這部分空間做初識化處理。
--Buffer.allocUnsafe():創建指定大小的Buffer對象,未初始化Buffer存在風險。只要其指定的大小少於Buffer.poolSize的一半,並且也少於當前已創建的Slab剩余內存空間,就直接使用當前Slab中的剩余空間,但不會對使用的這部分空間做初識化處理,存在內存暴露的風險。
--Buffer.allocUnsafeSlow():分配一塊全新的內存空間(slab內存單元),創建指定大小的Buffer對象,未初始化Buffer存在風險。由於每次都需要分配全新的slab內存單元,會導致性能下降,而且同樣它不會對使用的這部分內存空間做初識化處理。
關於創建Buff對象因為nodejs版本更新變化導致的差異,這里推薦參考官方的版本更新說明:創建Buffer對象的版本差異問題。這里把幾個關鍵的內容提出來解析:
在新版本中為什么不直接使用Buffer()和new Buffer()創建Buffer對象?
答:傳入參數如果是數值字符串會被識別為創建一個指定內存大小的Buffer對象,而不是創建一個指定數據的Buffer對象,需要額外將數值字符串JSON化。並且這兩種方式如果是創建一個指定空間大小的Buffer對象不會對使用的內存做初始化處理,存在內存暴露的風險。
什么是內存做初識化處理?內存暴露是什么意思?
答:但內存管理在做內存垃圾回收時,它只是將內存不在被對象引用的內存空間資源釋放出來,然后沒的程序執行可以使用這部分內存空間,但原來的內存數據並沒有被抹除。內存初識化就是將當前使用的內存空間使用0或者fill指定的內存覆蓋原來的數據,如果不做內存初識化處理,而此時新創建的Buffer對象內就直接可以將原來的內存數據讀取出來,這就可能導致內存原來的敏感數據被惡意讀取使用,這就是內存暴露。
為什么有的關於nodejs文檔中Buffer(num)和new Buffer(num)創建新的Buffer對象,其元素值是0~255的隨機數?
答:這個問題其實前面兩個問題的解析看明白了就已經回答了,因為Buffer對象內的元素是16進制即0~255,而很多文檔和資料都是在nodejs8版本前編寫的,如前面所說nodejs8版本之前不會對創建的Buffer對象做初識化操作,所以所謂的0~255的隨機數就是內存中原來的數據。
三、Buffer對象的API解析
3.1創建Buffer對象:
在第二節中已經介紹了創建Buffer對象的四個方法,但只針對性的解析了四種方式創建Buffer對象與物理存儲結構之間的關系,這里詳細的就創建對象API的參數解析,也就是實際應用。
Buffer.from():支持String、Array、ArrayBuffer、Object、buffer五種類型,也就是可以將這五種JavaScript類型的數據轉換成十六進制數據,但部分類型並不能完全支持。然后就是可選參數包括:編碼(encoding)、偏移量、長度,但不是五種類型數據所可選的參數不是一樣的,詳細見示例及注釋。
--Buffer.from(String [,encoding]);
1 let bufStr = Buffer.from('至若春和景明,波瀾不驚'); //默認編碼utf-8。web標准編碼也是utf-8,所以不需要設置編碼模式,如果js不是標准編碼需要注意設置編碼 2 let bufStrBase64 = Buffer.from('6Iez6Iul5pil5ZKM5pmv5piO77yM5rOi5r6c5LiN5oOK','base64');//'至若春和景明,波瀾不驚'的base64編碼 3 console.log(bufStr); 4 console.log(bufStrBase64); 5 console.log(bufStr.toString());//同樣toString將Buffer對象轉換成字符串時,也是默認utf-8編碼 6 console.log(bufStrBase64.toString('base64'));//如果使用時依然需要base64編碼,還是要使用base編碼轉換字符串 7 console.log(bufStrBase64.toString());//如果使用時使用utf8編碼模式,就可以直接使用默認編碼 8 //打印結果 9 //<Buffer e8 87 b3 e8 8b a5 e6 98 a5 e5 92 8c e6 99 af e6 98 8e ef bc 8c e6 b3 a2 e6 be 9c e4 b8 8d e6 83 8a> 10 //<Buffer e8 87 b3 e8 8b a5 e6 98 a5 e5 92 8c e6 99 af e6 98 8e ef bc 8c e6 b3 a2 e6 be 9c e4 b8 8d e6 83 8a> 11 //至若春和景明,波瀾不驚 12 //6Iez6Iul5pil5ZKM5pmv5piO77yM5rOi5r6c5LiN5oOK 13 //至若春和景明,波瀾不驚
這里需要注意的是,Buffer在內存中存儲的永遠是字符編碼對應的字符的十六進制數據,而我們在使用console.log直接打印Buffer時內部將十六進制數據用數字加字母的形式呈現出來。所以只需要將你存儲的字符和對應的編碼在寫入時設定好,在內存中存儲的十六進制數據只要再取出時,編碼模式中包含對應的字符就能讀取對應編碼模式的值。
--Buffer.from(Array);
1 let arr = [1,2,3,4]; 2 let arrStr = [1,2,3,'四']; 3 let arrObj = [1,2,3,{a:'aa'}]; 4 let arrBuf = Buffer.from(arr); 5 let arrStrBuf = Buffer.from(arrStr); 6 let arrObjBuf = Buffer.from(arrObj); 7 console.log(arrBuf); 8 console.log(arrStrBuf);//寫入的元素‘四’的十六進制顯然不可能是00,也就是說Buffer只能存儲純數字元素數組 9 console.log(arrObjBuf);//同上 10 console.log(arrBuf.toString());//Buffer對象沒有提供直接將寫入的數組提取出來的接口,這里會什么都讀不到 11 //打印結果 12 //<Buffer 01 02 03 04> 13 //<Buffer 01 02 03 00> 14 //<Buffer 01 02 03 00>
需要注意的是,Buffer.from(array)只能接收純數字數組,不需要設置編碼,而且nodejs的Buffer模塊沒有提供直接讀取為數組的API。解決Buffer.from(array)存儲包含非數字元素的情況可以使用JSON.stringify,但不能包含Function類型的元素,因為JSON也無法轉換Function類型的值。
1 let obj = {a:"a",b:"b"}; 2 let fun = function(){console.log(obj.a)}; 3 let arrCite = [obj,fun]; 4 let arrCiteBuf = Buffer.from(arrCite); 5 let arrCiteJsonBuf = Buffer.from(JSON.stringify(arrCite)); //實際這里本質上就是給Buffer.from傳入了一個String類型的參數 6 console.log(arrCiteBuf); 7 console.log(arrCiteJsonBuf); 8 console.log(arrCiteBuf.toString()); //這里什么也不會答應 9 console.log(arrCiteJsonBuf.toString());//獲取數組的JSON類型 10 console.log(JSON.parse(arrCiteJsonBuf.toString()));//獲取數組 11 //打印結果 12 //<Buffer 00 00> 13 //<Buffer 5b 7b 22 61 22 3a 22 61 22 2c 22 62 22 3a 22 62 22 7d 2c 6e 75 6c 6c 5d> 14 // 15 //[{"a":"a","b":"b"},null] 16 //[ { a: 'a', b: 'b' }, null ]
使用JSON實現Buffer存儲非數組元素數組,是典型的用空間換時間,而且並不能實現function類型元素的存儲。
--Buffer.from(object[, offsetOrEncoding[, length]]);
1 let objStrBuf = Buffer.from(new Object('至若春和景明,波瀾不驚')); 2 let objArrBuf = Buffer.from(new Object([1,2,3])); 3 let objArr1Buf = Buffer.from(new Object([1,2,3,'四'])); //最后一個元素‘四’無法正常寫入內存 4 console.log(objStrBuf); 5 console.log(objStrBuf.toString()); 6 console.log(objArrBuf); 7 console.log(objArr1Buf); 8 //打印結果 9 //<Buffer e8 87 b3 e8 8b a5 e6 98 a5 e5 92 8c e6 99 af e6 98 8e ef bc 8c e6 b3 a2 e6 be 9c e4 b8 8d e6 83 8a> 10 //至若春和景明,波瀾不驚 11 //<Buffer 01 02 03> 12 //<Buffer 01 02 03 00>
在nodejs中實際上是不能將Object類型直接寫入Buffer中的,當Buffer.from()接收到一個Object對象時會隱性執行Object.valueOf(),然后再將String、Array寫入Buffer。如果傳入的對象就是一個[object Object]類型的話會報錯。當然如果必須寫入一個Object對象的話還是使用JSON,但Function類型的值依然不能正常寫入。
1 let obj = { 2 a:'aaa', 3 b:[1,2,3,'四'], 4 c:function(){console.log('ccc')} 5 }; 6 let objBuf = Buffer.from(JSON.stringify(obj)); 7 console.log(objBuf); 8 console.log(objBuf.toString()); 9 console.log(JSON.parse(objBuf.toString())); 10 //打印結果 11 //<Buffer 7b 22 61 22 3a 22 61 61 61 22 2c 22 62 22 3a 5b 31 2c 32 2c 33 2c 22 e5 9b 9b 22 5d 7d> 12 //{"a":"aaa","b":[1,2,3,"四"]} 13 //{ a: 'aaa', b: [ 1, 2, 3, '四' ] }
實際上上面的這些示例沒有多大意義,一般情況下的需求都是寫入字符串(String)和純數字元素(Array),介紹這些主要是為了更深入的理解Buffer內存寫入數據的底層原理。接着來看一下這段代碼:
1 let bufStr = Buffer.from('至若春和景明,波瀾不驚'); 2 let bufBase = Buffer.from('6Iez6Iul5pil5ZKM5pmv5piO77yM5rOi5r6c5LiN5oOK'); 3 console.log(bufStr); 4 console.log(bufBase); 5 //打印結果 6 //<Buffer e8 87 b3 e8 8b a5 e6 98 a5 e5 92 8c e6 99 af e6 98 8e ef bc 8c e6 b3 a2 e6 be 9c e4 b8 8d e6 83 8a> 7 //<Buffer 36 49 65 7a 36 49 75 6c 35 70 69 6c 35 5a 4b 4d 35 70 6d 76 35 70 69 4f 37 37 79 4d 35 72 4f 69 35 72 36 63 35 4c 69 4e 35 6f 4f 4b>
這里將字符串和字符串的Base64編碼寫入Buffer來比較是應為我們知道字母和數字在utf-8中只占用一個字節,如果不比較的話可能會誤以為直接寫入字符串的Base64為編碼字符串比寫入字符串本身占用空間少,實際不然。
Buffer.alloc(size[, fill[, encoding]]):這是一個創建Buffer對象並對內存中的數據做初始化處理,並且可以通過fill可選參數指定初識化信息,和通過encoding指定初識化數據的編碼類型。
1 let buf64 = Buffer.alloc(44,5,'base64'); 2 let bufUtf8 = Buffer.alloc(33,2,'utf8'); 3 console.log(buf64); 4 console.log(bufUtf8); 5 buf64.write('6Iez6Iul5pil5ZKM5pmv5piO77yM5rOi5r6c5LiN5oOK','base64'); //需要注意的是這里寫入還需要設置寫入數據的編碼,前面alloc的編碼是指定初識化數據的 6 bufUtf8.write('至若春和景明,波瀾不驚');//這里是默認的utf-8所以在寫入時就不需要再指定了 7 console.log(buf64); 8 console.log(bufUtf8); 9 console.log(buf64.toString()); 10 console.log(bufUtf8.toString()); 11 //打印結果 12 //<Buffer 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05 05> 13 //<Buffer 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02> 14 //<Buffer e8 87 b3 e8 8b a5 e6 98 a5 e5 92 8c e6 99 af e6 98 8e ef bc 8c e6 b3 a2 e6 be 9c e4 b8 8d e6 83 8a 05 05 05 05 05 05 05 05 05 05 05> 15 //<Buffer e8 87 b3 e8 8b a5 e6 98 a5 e5 92 8c e6 99 af e6 98 8e ef bc 8c e6 b3 a2 e6 be 9c e4 b8 8d e6 83 8a> 16 //至若春和景明,波瀾不驚 17 //至若春和景明,波瀾不驚
Buffer.allocUnsafe(size)與Buffer.allocUnsafeSlow(size):這兩個方法與alloc的差別在讀寫上沒有什么差別,它們與alloc的差別就是內存的使用和初識化內存數據這兩個問題上,由於不做初識化處理它們只需要接收一個參數size設置使用內存的空間大小就可以了。所以這里就不再重復介紹了,關於對象創建的API解析到這里就介紹完了。
3.2Buffer對象的屬性和方法,以及一些應用:
--Buffer.concat(list[, totalLength]):這是一個拼接Buffer的靜態方法,參數list是所有拼接的Buffer對象的列表,totalLength是指定拼接成新的Buffer對象的長度,如果長度不夠則會忽略后面數據。
let buf1 = Buffer.from('至若春和景明'); let buf2 = Buffer.from('波瀾不驚'); let buf = Buffer.concat([buf1,buf2]); console.log(buf); console.log(buf.toString()); //打印結果 //<Buffer e8 87 b3 e8 8b a5 e6 98 a5 e5 92 8c e6 99 af e6 98 8e e6 b3 a2 e6 be 9c e4 b8 8d e6 83 8a> //至若春和景明波瀾不驚
--Buffer.isBuffer(obj);這是一個Buffer靜態方法,用來判斷對選是否是一個Buffer對象。
let buf = Buffer.from('若春和景明,波瀾不驚'); let obj = {a:"aa"}; console.log(Buffer.isBuffer(buf));//true console.log(Buffer.isBuffer(obj));//false
--Buffer.keys():這是一個Buffer對象方法(公有方法),用來獲取Buffer對象的key迭代器。
let buf = Buffer.from('若春和景明,波瀾不驚'); for(const key of buf.keys()){ console.log(key); //逐個打印key,Buffer類似數組,所以key可理解為索引 }
四、解決Buffer拼接時導致的亂碼問題
使用Buffer除了性能編碼等需要非常屬性以外,還需要注意讀取Buffer后拼接導致的亂碼問題,比如下面的示例:
1 let buf = Buffer.from('至若春和景明,波瀾不驚,上下天光,一碧萬頃,沙鷗翔集,錦鱗游泳,岸芷汀蘭,郁郁青青。'); 2 let start = 0; 3 let end = 10; 4 let str = ''; 5 while(start < buf.length){ 6 str += buf.subarray(start,end); //subarray獲取Buffer對象中的指定片段,與字符串拼接時會默認調用toString方法 7 start = end ; 8 end = start + 10; 9 } 10 console.log(str); 11 //打印結果 12 //至若春���景明��波瀾不驚,上���天光��一碧萬頃,沙���翔集��錦鱗游泳,岸���汀蘭��郁郁青青。
這個問題並不好解決,因為想UTF-8存在字符不等長字節的現象,沒辦法直接通過固定的字符字節長倍數讀取的方式來解決。針對這種情況nodejs提供了一個字符串解碼器模塊(string_decoder),這個模塊不會默認加載,需要手動引入使用,詳細參考下面的示例代碼:
字符串解碼器官方文檔:http://nodejs.cn/api/string_decoder.html
1 const StringDecoder = require('string_decoder').StringDecoder; 2 let decoder = new StringDecoder('utf8'); 3 let buf = Buffer.from('至若春和景明,波瀾不驚,上下天光,一碧萬頃,沙鷗翔集,錦鱗游泳,岸芷汀蘭,郁郁青青。'); 4 let start = 0; 5 let end = 9; 6 let str = ''; 7 while(start < buf.length){ 8 str += decoder.write(buf.subarray(start,end)); 9 start = end ; 10 end = start + 9; 11 } 12 console.log(str); 13 //打印結果 14 //至若春和景明,波瀾不驚,上下天光,一碧萬頃,沙鷗翔集,錦鱗游泳,岸芷汀蘭,郁郁青青。
五、Buffer的性能
5.1在網絡響應數據之前,將靜態數據轉換為Buffer可以有效的減少CPU的重復使用,節省服務器資源。
1 let http =require('http'); 2 let helloworld = ''; 3 for(let i = 0; i < 1024 * 10; i++){ 4 helloworld += 'a'; 5 } 6 helloworldBuf = new Buffer.from(helloworld); 7 http.createServer(function(req,res){ 8 res.writeHead(200); 9 // res.end(helloworld);//因為網絡上傳輸的數據是二進制,這里里會隱式轉換成Buffer 10 res.end(helloworldBuf); //這里直接使用緩存的Buffer,節省了在傳輸環節做轉換的時間 11 }).listen(12306);
5.2Buffer在文件讀取中的性能問題:
//fs.createReadStream(path, opts):opts { ... highWaterMark:64 * 1024 //默認一次最大讀取大小 }
1.highWaterMark設置對buffer內存的分配和使用有一定影響
2.highWaterMark設置過小,可能導致系統調用次數過多
文件讀取基於buffer分配,buffer基於Slowbuffer分配,如果文件過小,則可能造成slab的浪費。
另外,fs.createReadStream()內部使用了fs.read()實現,會多次調用系統磁盤,如果文件過大的話,highWaterMark將會決定出發系統調用的次數和data事件的次數。