標籤:

node.js教程3--文件操作

Node.js 文件系統

Node.js提供本地文件的讀寫能力,基本上類似 UNIX(POSIX)標準的文件操作API。 所有的方法都有非同步和同步的形式。例如讀取文件內容的函數有非同步的fs.readFile() 和同步的 fs.readFileSync()。

非同步的方法函數最後一個參數為回調函數,回調函數的第一個參數包含了錯誤信息(error),則第一個參數會是 null 或 undefined。

const fs = require(fs)n/*非同步讀取n *==================================*/nfs.readFile(README.md, function (err, data) {n if (err) {n return console.error(err)n }n console.log("非同步讀取: " + data.toString())n})nconsole.log("程序執行完畢。")n

結果:程序執行完畢。會被先列印出來

/*同步讀取n *==================================*/nconst fs = require(fs)nconst data = fs.readFileSync(README.md)nconsole.log("同步讀取: " + data.toString())nnconsole.log("程序執行完畢。")n

結果:程序執行完畢。後列印出來 強烈推薦大家是用非同步方法,比起同步,非同步方法性能更高,速度更快,而且沒有阻塞

接下來我們一起來看看fs模塊的常用方法

寫入文件

fs.writeFile(file, data[, options], callback)n

參數說明:

  • file - 文件名或文件描述符
  • data - 要寫入文件的數據,可以是 String(字元串) 或 Buffer(流) 對象
  • options - 該參數是一個對象,包含 {encoding, mode, flag}。默認編碼為 utf8, 模式為 0666 , flag 為 w,*如果是一個字元串,則它指定了字元編碼
  • callback - 回調函數

以追加模式往README.me寫入字元串Hello Node.js

fs.writeFile(README.md, Hello Node.js, {flag: a+}, (err) => {n if (err) throw errn console.log(Its saved!)n})n

這裡我們介紹下flags

Flag描述r以讀取模式打開文件。如果文件不存在拋出異常。r+以讀寫模式打開文件。如果文件不存在拋出異常。rs以同步的方式讀取文件。rs+以同步的方式讀取和寫入文件。w以寫入模式打開文件,如果文件不存在則創建。wx類似 w,但是如果文件路徑存在,則文件寫入失敗。w+以讀寫模式打開文件,如果文件不存在則創建。wx+類似 w+, 但是如果文件路徑存在,則文件讀寫失敗。a以追加模式打開文件,如果文件不存在則創建。ax類似 a, 但是如果文件路徑存在,則文件追加失敗。a+以讀取追加模式打開文件,如果文件不存在則創建。ax+類似 a+, 但是如果文件路徑存在,則文件讀取追加失敗。

打開文件

  • 同步 fs.open(path, flags[, mode], callback)
  • 非同步fs.openSync(path, flags[, mode])

參數說明:

  • path - 文件的路徑
  • flags - 文件打開的行為。具體值詳見下文
  • mode - 設置文件模式(許可權),文件創建默認許可權為 0666(可讀,可寫)
  • callback - 回調函數,帶有兩個參數如:callback(err, fd)

const fs = require("fs");nnfs.open(README.md, r+, function(err, fd) {n if (err) {n return console.error(err)n }n console.log("文件打開成功!")n})n

讀取文件

fs.read(fd, buffer, offset, length, position, callback)n

參數說明:

  • fd - 通過 fs.open() 方法返回的文件描述符
  • buffer - 是數據將被寫入到的 buffer
  • offset - 是 buffer 中開始寫入的偏移量
  • length - 是一個整數,指定要讀取的位元組數
  • position - 是一個整數,指定從文件中開始讀取的位置。 如果 positionnull,則數據從當前文件位置開始讀取
  • callback - 回調函數,有三個參數err, bytesRead, buffer,err 為錯誤信息, bytesRead 表示讀取的位元組數,buffer 為緩衝區對象

const fs = require("fs");nlet buf = new Buffer(1024)nfs.open(README.md, r+, function(err, fd) {n if (err) {n return console.error(err)n }n fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){n if (err){n console.error(err);n }n console.log(bytes + " 位元組被讀取")n // 僅輸出讀取的位元組n if(bytes > 0){n console.log(buf.slice(0, bytes).toString())n }n })n})n

現在我們就可以從README.md中讀取出1kb的數據

讀取目錄

readdir方法用於讀取目錄,返回一個所包含的文件和子目錄的數組。

fs.readdir(path[, options], callback)n

同步版本:

fs.readdirSync(path[, options])n

我們來寫個遍歷目錄的方法吧!這是個同步的*(同步版本簡單點)*

遍歷目錄時一般使用遞歸演算法,否則就難以編寫出簡潔的代碼。遞歸演算法與數學歸納法類似,通過不斷縮小問題的規模來解決問題,目錄是一個樹狀結構,在遍歷時一般使用深度優先+先序遍歷演算法

function travel(dir, callback) {n fs.readdirSync(dir).forEach(function (file) {n var pathname = path.join(dir, file)nn if (fs.statSync(pathname).isDirectory()) {n travel(pathname, callback)n } else {n callback(pathname)n }n })n}n

該函數以某個目錄作為遍歷的起點。遇到一個子目錄時,就先接著遍歷子目錄。遇到一個文件時,就把文件的絕對路徑傳給回調函數。回調函數拿到文件路徑後,就可以做各種判斷和處理了:

travel(__dirname, function (pathname) {n console.log(pathname)n})n

接下來,大家可以試著去實現非同步遍歷,原理都是一樣的

const fs = require("fs");nconst path = require("path");nnfunction travel(dir, callback) {n fs.readdirSync(dir).forEach(function (file) {n var pathname = path.join(dir, file)nn if (fs.statSync(pathname).isDirectory()) {n travel(pathname, callback)n } else {n callback(pathname)n }n })n}nnntravel(__dirname, function (pathname) {n console.log(pathname)n})n

關於File System (文件系統)的更多API請自行查看Node中文網,

__dirname:全局變數,存儲的是文件所在的文件目錄n__filename:全局變數,存儲的是文件名n

Path模塊

path 模塊提供了一些工具函數,用於處理文件與目錄的路徑,path 模塊的默認操作會根據 Node.js 應用程序運行的操作系統的不同而變化。 比如,當運行在 Windows 操作系統上時,path 模塊會認為使用的是 Windows 風格的路徑

常用方法介紹

path.join([...paths])方法用於連接路徑

path.join(foo, "bar");n// 返回: /foo/barnnpath.join(foo, {}, bar)n// 拋出 TypeError: path.join 的參數必須為字元串n

path.resolve() 方法會把一個路徑或路徑片段的序列解析為一個絕對路徑方法用於將相對路徑轉為絕對路徑

path.resolve(/foo/bar, ./baz)n// 返回: /foo/bar/baznnpath.resolve(/foo/bar, /tmp/file/)n// 返回: /tmp/filennpath.resolve(wwwroot, static_files/png/, ../gif/image.gif)n// 如果當前工作目錄為 /home/myself/node,n// 則返回 /home/myself/node/wwwroot/static_files/gif/image.gifn

path.extname() 方法返回 path 的擴展名,即從 path 的最後一部分中的最後一個 .(句號)字元到字元串結束

path.extname(index.html)n// 返回: .htmlnnpath.extname(index.coffee.md)n// 返回: .mdn

關於Path (路徑)的更多API請自行查看Node中文網

上面介紹了fs與path模塊的幾個常用API,在使用時我們應該經常查看API文檔,上面所學習的方法已經足夠打造一靜態文件伺服器了,下面我們就一起開完成這個小案例吧。

Node靜態文件伺服器

這個靜態文件伺服器大致是這樣的:瀏覽器發送URL,服務端解析URL,對應到硬碟上的文件。如果文件存在,返回200狀態碼,並發送文件到瀏覽器端,我們要實現的功能如下:

  • 主頁顯示當前目錄下的文件和文件夾
  • 點擊鏈接可以打開文件或者文件夾

有圖有真相,話不多說看圖:

現在我們來一步一步實現這個小項目

1.首先起一個http伺服器

我們先來初始化工作:

  1. 新建文件夾file-server
  2. npm init初識初識化生成package.json
  3. 新建index.js文件

現在我們在index.js文件中起一個伺服器:

const http = require(http)nnconst hostname = 127.0.0.1nconst port = 3000nnconst server = http.createServer((req, res) => {n res.statusCode = 200n res.setHeader(Content-Type, text/plain)n res.end(Hello Worldn)n})nnserver.listen(port, hostname, () => {n console.log(`伺服器運行在 http://${hostname}:${port}`)n})n

還記得我們第一節讓大家自己如了解的supervisor工具嗎?它可以實現監測文件修改並自動重啟應用

$ supervisor --harmony index.jsnRunning node-supervisor withn program --harmony index.jsn --watch .n --extensions node,jsn --exec nodennStarting child process with node --harmony index.jsnWatching directory /Users/lx/Documents/workspace/node-abc/lesson4/file-server for changes.nPress rs for restarting the process.n伺服器運行在 http://127.0.0.1:3000n

??現在我們這個伺服器跑了起來,而且每次更該文件後不需要手動重啟服務。

2.處理URL請求

現在我們就要url模塊與path來識別請求的文件*(還記得第二節了解的url模塊嗎?)*

const http = require(http)nconst url = require(url) //引入url模塊nnconst hostname = 127.0.0.1nconst port = 3000nnconst server = http.createServer((req, res) => {n if(req.url == /favicon.ico) return //不響應favicon請求n // 獲取url->patnname 即文件名n let pathname = path.join(__dirname, url.parse(req.url).pathname)n pathname = decodeURIComponent(pathname) // url解碼,防止中文路徑出錯n console.log(pathname) // .../node-abc/lesson4/file-server/ 請求的pathnamen})nnserver.listen(port, hostname, () => {n console.log(`伺服器運行在 http://${hostname}:${port}`)n})n

3. 讀取文件發送給瀏覽器

接下來我們就運用本節所講的文件系統的知識來處理文件,

1.先來處理文件夾:

....n if(req.url == /favicon.ico) return //不響應favicon請求n // 獲取url->patnname 即文件名n let pathname = path.join(__dirname, url.parse(req.url).pathname)n pathname = decodeURIComponent(pathname) // url解碼,防止中文路徑出錯n console.log(pathname) // .../node-abc/lesson4/file-server/ 請求的pathnamen n /**n * 判斷文件是否是文件夾n * 是:返迴文件列表n * 否:讀取文件內容n */n // stat方法的參數是一個文件或目錄,它產生一個對象,該對象包含了該文件或目錄的具體信息。我們往往通過該方法,判斷正在處理的到底是一個文件,還是一個目錄,這兒使用的是它的同步版本n if(fs.statSync(pathname).isDirectory()){n // 設置響應頭n res.writeHead(200, {Content-Type: text/html; charset=utf-8})n fs.readdir(pathname, (err, files)=>{n res.write(<ul>)n files.forEach((item)=>{n t// 處理路徑n let link = path.join(url.parse(req.url).pathname, item)n res.write(`<li><a href="${link}">${item}</a></li>`)n })n res.end(</ul>)n })n }n...n

我們先用fs.statSync(pathname).isDirectory()來判斷是否為文件夾,是則給瀏覽器返回當前文件夾下的文件列表,請求/直返回:

此處我們新建了個test文件夾放了一些文件作為測試文件

2.文件處理

因為我們的伺服器同時要存放html, css, js, png, gif, jpg等等文件。並非每一種文件的MIME類型都是text/html的,所以這裡我們引入了mime模塊,來處理mime支持

const mime = require(mime);nnmime.lookup(/path/to/file.txt); // => text/plainnmime.lookup(file.txt); // => text/plainnmime.lookup(.TXT); // => text/plainnmime.lookup(htm); // => text/htmln//具體使用非同步官網n

這兒用到了前面講的文件讀取:

else{n // 以binary讀取文件n fs.readFile(pathname, binary, (err, data)=>{n if(err){n res.writeHead(500, { Content-Type: text/plain})n res.end(JSON.stringify(err))n return falsen }n res.writeHead(200, { n Content-Type: `${mime.lookup(pathname)};charset:UTF-8`n })n res.write(data, binary)n res.end()n })n}n

如果路徑不是文件夾,就讀取具體的文件,這兒我們以二進位(binary)編碼讀取,你也可以試試UTF-8比較他們的區別。

到此我們這個Node靜態文件伺服器就算搭建完成??????,當然了還有許多優化的地方如:緩存、Gzip、頁面美化等等…在此就不做介紹了,請自行搜索了解。

源碼地址node-abc

總結:

這一節我們了解了fs模塊path模塊。並運用這些知識搭建一簡單的文件伺服器

一切學問最重要的是融會貫通,要把它轉變成自身的學問.

推薦閱讀:

管理 Node.js 進程從未如此優雅 - Pandora.js 的 procfile.js
深入 Promise(一)——Promise 實現詳解
Koa2 源碼賞析
Node.js 性能調優之內存篇(一)——gcore+llnode

TAG:Nodejs |