好程式設計師web前端分享Nodejs學習筆記之Stream模組
好程式設計師
web前端分享
Nodejs學習筆記之Stream模組
一,開篇分析
流是一個抽象介面,被
Node 中的很多物件所實現。比如對一個 HTTP 伺服器的請求是一個流,stdout 也是一個流。流是可讀,可寫或兼具兩者的。
最早接觸
Stream是從早期的unix開始的, 數十年的實踐證明Stream 思想可以很簡單的開發出一些龐大的系統。
在
unix裡,Stream是透過 "|" 實現的。在node中,作為內建的stream模組,很多核心模組和三方模組都使用到。
和
unix一樣,node stream主要的操作也是.pipe(),使用者可以使用反壓力機制來控制讀和寫的平衡。
Stream 可以為開發者提供可以重複使用統一的介面,透過抽象的Stream介面來控制Stream之間的讀寫平衡。
一個
TCP連線既是可讀流,又是可寫流,而Http連線則不同,一個http request物件是可讀流,而http response物件則是可寫流。
流的傳輸過程預設是以
buffer的形式傳輸的,除非你給他設定其他編碼形式,以下是一個例子:
1. <p><font size="3">
2. </font></p>
3. <p><font size="3"> var http = require('http') ;</font></p>
4. <p><font size="3"> var server = http.createServer(function(req,res){</font></p>
5. <p><font size="3"> res.writeHeader(200, {'Content-Type': 'text/plain'}) ;</font></p>
6. <p><font size="3"> res.end("Hello,大熊!") ;</font></p>
7. <p><font size="3"> }) ;</font></p>
8. <p><font size="3"> server.listen(8888) ;</font></p>
9. <p><font size="3"> console.log("http server running on port 8888 ...") ;</font></p>
執行後會有亂碼出現,原因就是沒有設定指定的字符集,比如:
“utf-8” 。
修改一下就好:
var http = require('http') ;
1.
2. <p><font size="3"> var server = http.createServer(function(req,res){</font></p>
3. <p><font size="3"> res.writeHeader(200,{</font></p>
4. <p><font size="3"> 'Content-Type' : 'text/plain;charset=utf-8' // 新增charset=utf-8</font></p>
5. <p><font size="3"> }) ;</font></p>
6. <p><font size="3"> res.end("Hello,大熊!") ;</font></p>
7. <p><font size="3"> }) ;</font></p>
8. <p><font size="3"> server.listen(8888) ;</font></p>
9. <p><font size="3"> console.log("http server running on port 8888 ...") ;</font></p>
執行結果:
為什麼使用
Stream
node中的I/O是非同步的,因此對磁碟和網路的讀寫需要透過回撥函式來讀取資料,下面是一個檔案下載例子
上程式碼:
1.
2. <p><font size="3"> var http = require('http') ;</font></p>
3. <p><font size="3"> var fs = require('fs') ;</font></p>
4. <p><font size="3"> var server = http.createServer(function (req, res) {</font></p>
5. <p><font size="3"> fs.readFile(__dirname + '/data.txt', function (err, data) {</font></p>
6. <p><font size="3"> res.end(data);</font></p>
7. <p><font size="3"> }) ;</font></p>
8. <p><font size="3"> }) ;</font></p>
9. <p><font size="3"> server.listen(8888) ;</font></p>
程式碼可以實現需要的功能,但是服務在傳送檔案資料之前需要快取整個檔案資料到記憶體,如果
"data.txt"檔案很
大並且併發量很大的話,會浪費很多記憶體。因為使用者需要等到整個檔案快取到記憶體才能接受的檔案資料,這樣導致
使用者體驗相當不好。不過還好
(req,res)兩個引數都是Stream,這樣我們可以用fs.createReadStream()代替fs.readFile()。如下:
var http = require('http') ;
1.
2. <p><font size="3"> var fs = require('fs') ;</font></p>
3. <p><font size="3"> var server = http.createServer(function (req, res) {</font></p>
4. <p><font size="3"> var stream = fs.createReadStream(__dirname + '/data.txt') ;</font></p>
5. <p><font size="3"> stream.pipe(res) ;</font></p>
6. <p><font size="3"> }) ;</font></p>
7. <p><font size="3"> server.listen(8888) ;</font></p>
.pipe()方法監聽fs.createReadStream()的'data' 和'end'事件,這樣"data.txt"檔案就不需要快取整
個檔案,當客戶端連線完成之後馬上可以傳送一個資料塊到客戶端。使用
.pipe()另一個好處是可以解決當客戶
端延遲非常大時導致的讀寫不平衡問題。
有五種基本的
Stream:readable,writable,transform,duplex,and "classic” 。(具體使用請自己查閱api)
二,例項引入
當記憶體中無法一次裝下需要處理的資料時,或者一邊讀取一邊處理更加高效時,我們就需要用到資料流。
NodeJS中透過各種Stream來提供對資料流的操作。
以大檔案複製程式為例,我們可以為資料來源建立一個只讀資料流,示例如下:
var rs = fs.createReadStream(pathname);
1.
2. <p><font size="3"> rs.on('data', function (chunk) {</font></p>
3. <p><font size="3"> doSomething(chunk) ; // 具體細節自己任意發揮</font></p>
4. <p><font size="3"> });</font></p>
5. <p><font size="3"> rs.on('end', function () {</font></p>
6. <p><font size="3"> cleanUp() ;</font></p>
7. <p><font size="3"> }) ;</font></p>
程式碼中
data事件會源源不斷地被觸發,不管doSomething函式是否處理得過來。程式碼可以繼續做如下改造,以解決這個問題。
1. <p><font size="3">
2. </font></p>
3. <p><font size="3"> var rs = fs.createReadStream(src) ;</font></p>
4. <p><font size="3"> rs.on('data', function (chunk) {</font></p>
5. <p><font size="3"> rs.pause() ;</font></p>
6. <p><font size="3"> doSomething(chunk, function () {</font></p>
7. <p><font size="3"> rs.resume() ;</font></p>
8. <p><font size="3"> }) ;</font></p>
9. <p><font size="3"> }) ;</font></p>
10. <p><font size="3"> rs.on('end', function () {</font></p>
11. <p><font size="3"> cleanUp();</font></p>
12. <p><font size="3"> }) ;</font></p>
給
doSomething函式加上了回撥,因此我們可以在處理資料前暫停資料讀取,並在處理資料後繼續讀取資料。
此外,我們也可以為資料目標建立一個只寫資料流,如下:
var rs = fs.createReadStream(src) ;
1.
2. <p><font size="3"> var ws = fs.createWriteStream(dst) ;</font></p>
3. <p><font size="3"> rs.on('data', function (chunk) {</font></p>
4. <p><font size="3"> ws.write(chunk);</font></p>
5. <p><font size="3"> }) ;</font></p>
6. <p><font size="3"> rs.on('end', function () {</font></p>
7. <p><font size="3"> ws.end();</font></p>
8. <p><font size="3"> }) ;</font></p>
doSomething換成了往只寫資料流裡寫入資料後,以上程式碼看起來就像是一個檔案複製程式了。但是以上程式碼存在上邊提到的問題,如果寫入速度跟不上讀取速度的話,只寫資料流內部的快取會爆倉。我們可以根據.write方法的返回值來判斷傳入的資料是寫入目標了,還是臨時放在了快取了,並根據drain事件來判斷什麼時候只寫資料流已經將快取中的資料寫入目標,可以傳入下一個待寫資料了。因此程式碼如下:
var rs = fs.createReadStream(src) ;
1.
2. <p><font size="3"> var ws = fs.createWriteStream(dst) ;</font></p>
3. <p><font size="3"> rs.on('data', function (chunk) {</font></p>
4. <p><font size="3"> if (ws.write(chunk) === false) {</font></p>
5. <p><font size="3"> rs.pause() ;</font></p>
6. <p><font size="3"> }</font></p>
7. <p><font size="3"> }) ;</font></p>
8. <p><font size="3"> rs.on('end', function () {</font></p>
9. <p><font size="3"> ws.end();</font></p>
10. <p><font size="3"> });</font></p>
11. <p><font size="3"> ws.on('drain', function () {</font></p>
12. <p><font size="3"> rs.resume();</font></p>
13. <p><font size="3"> }) ;</font></p>
最終實現了資料從只讀資料流到只寫資料流的搬運,幷包括了防爆倉控制。因為這種使用場景很多,例如上邊的大檔案複製程式,
NodeJS直接提供了.pipe方法來做這件事情,其內部實現方式與上邊的程式碼類似。
下面是一個更加完整的複製檔案的過程:
var fs = require('fs'),
1.
2. <p><font size="3"> path = require('path'),</font></p>
3. <p><font size="3"> out = process.stdout;</font></p>
4. <p><font size="3"> var filePath = '/bb/bigbear.mkv';</font></p>
5. <p><font size="3"> var readStream = fs.createReadStream(filePath);</font></p>
6. <p><font size="3"> var writeStream = fs.createWriteStream('file.mkv');</font></p>
7. <p><font size="3"> var stat = fs.statSync(filePath);</font></p>
8. <p><font size="3"> var totalSize = stat.size;</font></p>
9. <p><font size="3"> var passedLength = 0;</font></p>
10. <p><font size="3"> var lastSize = 0;</font></p>
11. <p><font size="3"> var startTime = Date.now();</font></p>
12. <p><font size="3"> readStream.on('data', function(chunk) {</font></p>
13. <p><font size="3"> passedLength += chunk.length;</font></p>
14. <p><font size="3"> if (writeStream.write(chunk) === false) {</font></p>
15. <p><font size="3"> readStream.pause();</font></p>
16. <p><font size="3"> }</font></p>
17. <p><font size="3"> });</font></p>
18. <p><font size="3"> readStream.on('end', function() {</font></p>
19. <p><font size="3"> writeStream.end();</font></p>
20. <p><font size="3"> });</font></p>
21. <p><font size="3"> writeStream.on('drain', function() {</font></p>
22. <p><font size="3"> readStream.resume();</font></p>
23. <p><font size="3"> });</font></p>
24. <p><font size="3"> setTimeout(function show() {</font></p>
25. <p><font size="3"> var percent = Math.ceil((passedLength / totalSize) * 100);</font></p>
26. <p><font size="3"> var size = Math.ceil(passedLength / 1000000);</font></p>
27. <p><font size="3"> var diff = size - lastSize;</font></p>
28. <p><font size="3"> lastSize = size;</font></p>
29. <p><font size="3"> out.clearLine();</font></p>
30. <p><font size="3"> out.cursorTo(0);</font></p>
31. <p><font size="3"> out.write('已完成' + size + 'MB, ' + percent + '%, 速度:' + diff * 2 +
32. 'MB/s');</font></p>
33. <p><font size="3"> if (passedLength < totalSize) {</font></p>
34. <p><font size="3"> setTimeout(show, 500);</font></p>
35. <p><font size="3"> } else {</font></p>
36. <p><font size="3"> var endTime = Date.now();</font></p>
37. <p><font size="3"> console.log();</font></p>
38. <p><font size="3"> console.log('共用時:' + (endTime - startTime) / 1000 + '秒。');</font></p>
39. <p><font size="3"> }</font></p>
40. <p><font size="3"> }, 500);</font></p>
可以把上面的程式碼儲存為
"copy.js" 試驗一下我們新增了一個遞迴的 setTimeout (或者直接使用setInterval)來做一個旁觀者,
每
500ms觀察一次完成進度,並把已完成的大小、百分比和複製速度一併寫到控制檯上,當複製完成時,計算總的耗費時間。
三,總結一下
(1),理解Stream概念。
(2),熟練使用相關Stream的api
(3),注意細節的把控,比如:大檔案的複製,採用的使用 “chunk data” 的形式進行分片處理。
(4),pipe的使用
(5),再次強調一個概念:一個TCP連線既是可讀流,又是可寫流,而Http連線則不同,一個http request物件是可讀流,而http response物件則是可寫流。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69913892/viewspace-2644730/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 好程式設計師web前端分享Vue學習筆記(一)程式設計師Web前端Vue筆記
- 好程式設計師web前端培訓分享JavaScript學習筆記之設計模式程式設計師Web前端JavaScript筆記設計模式
- 好程式設計師web前端培訓分享node學習筆記程式設計師Web前端筆記
- 好程式設計師web前端培訓分享JavaScript學習筆記之正則程式設計師Web前端JavaScript筆記
- 好程式設計師web前端教程分享JavaScript學習筆記之Event事件二程式設計師Web前端JavaScript筆記事件
- 好程式設計師web前端培訓分享JavaScript學習筆記之陣列程式設計師Web前端JavaScript筆記陣列
- 好程式設計師web前端培訓分享HTMLCSS學習筆記BFC程式設計師Web前端HTMLCSS筆記
- 好程式設計師web前端培訓分享JavaScript學習筆記Promise程式設計師Web前端JavaScript筆記Promise
- 好程式設計師web前端培訓分享JavaScript學習筆記cookie程式設計師Web前端JavaScript筆記Cookie
- 好程式設計師web前端培訓分享JavaScript學習筆記SASS程式設計師Web前端JavaScript筆記
- 好程式設計師web前端培訓分享React學習筆記(三)程式設計師Web前端React筆記
- 好程式設計師web前端培訓分享React學習筆記(一)程式設計師Web前端React筆記
- 好程式設計師web前端培訓分享React學習筆記(二)程式設計師Web前端React筆記
- 好程式設計師web前端分享菜鳥Vue學習筆記(二)程式設計師Web前端Vue筆記
- 好程式設計師web前端培訓分享JavaScript學習筆記之ES5程式設計師Web前端JavaScript筆記
- 好程式設計師web前端培訓分享JavaScript學習筆記之迴圈結構程式設計師Web前端JavaScript筆記
- 好程式設計師web前端培訓分享node學習筆記系列之四十一程式設計師Web前端筆記
- 好程式設計師web前端培訓學習筆記Vue學習筆記一程式設計師Web前端筆記Vue
- 好程式設計師web前端培訓分享JavaScript學習筆記分支結構程式設計師Web前端JavaScript筆記
- 好程式設計師web前端分享CSS Bug、CSS Hack和Filter學習筆記程式設計師Web前端CSSFilter筆記
- 好程式設計師web前端培訓分享之HTMLCSS學習筆記css3-多列程式設計師Web前端HTMLCSS筆記S3
- 好程式設計師web前端學習路線nodeJs學習過程之認識nodejs程式設計師Web前端NodeJS
- 好程式設計師web前端培訓學習筆記Vue學習筆記之二程式設計師Web前端筆記Vue
- 好程式設計師web前端培訓JavaScript學習筆記DOM程式設計師Web前端JavaScript筆記
- 好程式設計師web前端培訓JavaScript學習筆記--jQuery程式設計師Web前端JavaScript筆記jQuery
- 好程式設計師web前端培訓分享JavaScript學習筆記函式進階程式設計師Web前端JavaScript筆記函式
- 好程式設計師web前端培訓分享之HTMLCSS學習筆記媒體查詢+ rem用法程式設計師Web前端HTMLCSS筆記REM
- 好程式設計師web前端培訓分享學習JavaScript程式設計師Web前端JavaScript
- 好程式設計師Web前端教程分享Vue學習心得程式設計師Web前端Vue
- 好程式設計師web前端分享HTML5中的nav標籤學習筆記程式設計師Web前端HTML筆記
- 好程式設計師web前端培訓分享JavaScript學習筆記閉包與繼承程式設計師Web前端JavaScript筆記繼承
- 好程式設計師web前端培訓分享JavaScript學習筆記ajax及ajax封裝程式設計師Web前端JavaScript筆記封裝
- 好程式設計師web前端培訓分享之uni-app學習筆記uni-app詳解程式設計師Web前端APP筆記
- 好程式設計師web前端教程分享js中的模組化二程式設計師Web前端JS
- 好程式設計師web前端教程分享js中的模組化一程式設計師Web前端JS
- 好程式設計師web前端分享應該怎樣學好web前端?程式設計師Web前端
- 好程式設計師web前端教程分享前端javascript練習題之promise程式設計師Web前端JavaScriptPromise
- 好程式設計師web前端培訓分享JavaScript學習指南程式設計師Web前端JavaScript