Nodejs教程24:Stream流

閱讀更多系列文章請訪問個人GitHub博客,示例代碼請訪問這裏

File System的問題

咱們一般會使用File System模塊對文件進行讀取,以下:git

fs.readFile('./test.txt', (error, buffer) => {
  if (error) {
    console.error(error)
  } else {
    // 讀取文件成功
    res.write(buffer)
  }
})
複製代碼

這樣操做簡單有效,但這也存在一些問題:github

  1. 佔用內存 使用fs讀取文件,它是一次性將文件的全部內容讀取到內存中,再一次性發送到客戶端,所以會佔用大量內存。
  2. 資源使用效率低 從磁盤讀取文件期間,磁盤處於忙碌狀態,而網絡處於空閒狀態。 磁盤讀取完成後,開始發送文件時,狀況正相反,網絡處於忙碌狀態,此時磁盤卻處於空閒狀態。

Stream流

相比File System,Stream流讀取文件是讀一份,發一份,Stream流的寫入操做也有一樣特色,所以能夠解決File System在上面提到的2個問題。瀏覽器

接下來實現一個簡單的流,將1.txt文件的內容寫入到2.txt中:bash

示例代碼:/lesson24/stream.js網絡

const fs = require('fs')

// 建立一個可讀流。
const readStream = fs.createReadStream('./1.txt')

// 建立一個可寫流。
const writeStream = fs.createWriteStream('./2.txt')

// 將可讀流讀取的數據,經過管道pipe推送到寫入流中,便可將1.txt的內容,寫入到2.txt中。
readStream.pipe(writeStream)

// 讀取出現錯誤時會觸發error事件。
readStream.on('error', (error) => {
  console.error(error)
})

// 寫入完成時,觸發finish事件。
writeStream.on('finish', () => {
  console.log('finish')
})
複製代碼

使用Zlib壓縮文件

可使用Zlib模塊,配合Stream流,實現文件壓縮功能,以下:less

示例代碼:/lesson24/gzip.jside

const fs = require('fs')
// 引入zlib模塊,用於實現壓縮功能
const zlib = require('zlib')

// 建立一個可讀流。
const readStream = fs.createReadStream('./google.jpg')

// 建立一個可寫流。
const writeStream = fs.createWriteStream('./google.jpg.gz')

// 建立一個Gzip對象,用於將文件壓縮成.gz文件
const gzip = zlib.createGzip()

// 將可讀流讀取的數據,先經過管道pipe推送到gzip中,再推送到寫入流中。
// 也就是先將可讀流的數據壓縮,再推送到可寫流中。
readStream.pipe(gzip).pipe(writeStream)

// 讀取出現錯誤時會觸發error事件。
readStream.on('error', (error) => {
  console.error(error)
})

// 寫入完成時,觸發finish事件。
writeStream.on('finish', () => {
  console.log('finish')
})
複製代碼

使用流傳輸文件到前臺

學習了流,咱們就能夠更加高效地將文件傳輸到前臺:學習

示例代碼:/lesson24/server.js網站

const http = require('http')
const zlib = require('zlib')
const url = require('url')
const fs = require('fs')

const server = http.createServer((req, res) => {
  const {
    pathname
  } = url.parse(req.url, true)

// 建立一個可讀流。
  const readStream = fs.createReadStream(`./${pathname}`)

  // 建立一個Gzip對象,用於將文件壓縮成.gz文件
  const gzip = zlib.createGzip()

  // 將讀取的內容,在經過管道推送到res中,該方法不通過壓縮
  readStream.pipe(res)

  // 處理可讀流報錯,防止請求不存在的文件
  readStream.on('error', (error) => {
    console.error(error);
    res.writeHead(404)
    res.write('Not Found')
    res.end()
  })
})

server.listen(8080)
複製代碼

但能夠看到,在這個例子裏,雖然實現了使用流傳輸文件,但並無用到gzip壓縮,在傳輸時仍是更多地消耗網絡資源,接下來能夠引入gzip壓縮。ui

文件通過gzip壓縮後傳輸到前臺

但此時若是隻是簡單的用readStream.pipe(gzip).pipe(res)傳輸文件,瀏覽器在訪問時沒法直接打開,而是會觸發文件下載。

這是由於未設置請求頭屬性content-encoding的值,致使瀏覽器沒法識別用gzip壓縮過的文件,這就須要修改請求頭res.setHeader('content-encoding', 'gzip'),讓瀏覽器能夠識別。

這樣瀏覽器就能夠正常打開文件了,但若瀏覽器訪問的是不存在的文件,瀏覽器會報錯「沒法訪問此網站」,這是由於請求頭屬性content-encoding已被設置爲gzip,但服務端傳給瀏覽器的是Not Found字符串,瀏覽器沒法識別。

此時可使用fs.stat方法,先檢查文件是否存在,若不存在則返回Not Found,若存在則繼續傳輸。

示例代碼:/lesson24/server_gzip.js

const http = require('http')
const zlib = require('zlib')
const url = require('url')
const fs = require('fs')

const server = http.createServer((req, res) => {
  const {
    pathname
  } = url.parse(req.url, true)

  // 文件的相對路徑
  const filepath = `./${pathname}`

  // 檢查文件是否存在
  fs.stat(filepath, (error, stat) => {
    if (error) {
      console.error(error);
      res.setHeader('content-encoding', 'identity')
      res.writeHead(404)
      res.write('Not Found')
      res.end()
    } else {
      // 建立一個可讀流。
      const readStream = fs.createReadStream(filepath)

      // 建立一個Gzip對象,用於將文件壓縮成.gz文件
      const gzip = zlib.createGzip()

      // 向瀏覽器發送通過gzip壓縮的文件,設置響應頭,不然瀏覽器沒法識別,會自動進行下載。
      res.setHeader('content-encoding', 'gzip')
      // 將讀取的內容,經過gzip壓縮以後,在經過管道推送到res中,因爲res繼承自Stream流,所以也能夠接收管道的推送。
      readStream.pipe(gzip).pipe(res)

      // 處理可讀流報錯,防止文件中途被刪除或出錯,致使報錯。
      readStream.on('error', (error) => {
        console.error(error);
        res.setHeader('content-encoding', 'identity')
        res.writeHead(404)
        res.write('Not Found')
        res.end()
      })
    }
  })
})

server.listen(8080)
複製代碼
相關文章
相關標籤/搜索