本文的主要內容是對nodejs提供的一些重要模組,結合官方API進行介紹,遇到精彩的文章,我會附在文中並標明瞭出處。主要包括如下7個模組
- path 模組
- http 模組
- fs 模組
- url 模組
- query string 模組
- os 模組
- stream 模組
轉載請註明出處,多謝支援~
path 路徑相關模組
模組簡介
nodejs path 模組提供了一些用於處理檔案路徑的工具函式,我們可以通過如下方式引用它
var path = require("path")
複製程式碼
path.normalize(p)
標準化路徑字串,處理冗餘的“..”、“.”、“/”字元:
原則:
-
對window系統,目錄分隔為'',對於UNIX系統,分隔符為'/',針對'..'返回上一級;/與\都被統一轉換path.normalize(p);
-
如果路徑為空,返回.,相當於當前的工作路徑。
-
將對路徑中重複的路徑分隔符(比如linux下的/)合併為一個。
-
對路徑中的.、..進行處理。(類似於shell裡的cd ..)
-
如果路徑最後有/,那麼保留該/。
var url1 = path.normalize('a/b/c/../user/vajoy/bin'); var url2 = path.normalize('a/b/c///../user/vajoy/bin/'); var url3 = path.normalize('a/b/c/../../user/vajoy/bin'); var url4 = path.normalize('a/b/c/.././///../user/vajoy/bin/..'); var url5 = path.normalize('a/b/c/../../user/vajoy/bin/../../'); var url6 = path.normalize('a/../../user/vajoy/bin/../../'); var url7 = path.normalize('a/../../user/vajoy/bin/../../../../'); var url8 = path.normalize('./a/.././user/vajoy/bin/./');
console.log('url1:',url1); // a\b\user\vajoy\bin console.log('url2:',url2); // a\b\user\vajoy\bin
console.log('url3:',url3); // a\user\vajoy\bin console.log('url4:',url4); // a\user\vajoy console.log('url5:',url5); // a\user
console.log('url6:',url6); // ..\user
console.log('url7:',url7); // ....
console.log('url8:',url8); // user\vajoy\bin\
path.join([path1], [path2], [...])
將多個路徑結合在一起,並轉換為標準化的路徑
var url1 = path.join('////./a', 'b////c', 'user/', 'vajoy', '..');
var url2 = path.join('a', '../../', 'user/', 'vajoy', '..');
var url3 = path.join('a', '../../', {}, 'vajoy', '..');
var url4 = path.join('path1', 'path2//pp\\', ../path3');
console.log('url1:',url1); // \a\b\c\user
console.log('url2:',url2); // ..\user
console.log('url3:',url3); // 存在非路徑字串,故丟擲異常
console.log('url4:',url4); // path1\path2\path3
複製程式碼
path.resolve([from ...], to)
從源地址 from 到目的地址 to 的絕對路徑
原則 以應用程式根目錄為起點,根據引數字串解析出一個絕對路徑 要注意的是,如果某個 from 或 to 引數是絕對路徑(比如 'E:/abc',或是以“/”開頭的路徑),則將忽略之前的 from 引數。
// 下文中app的根目錄為D:\temp\test
var url1 = path.resolve('.', 'testFiles/..', 'trdLayer');
var url2 = path.resolve('..', 'testFiles', 'a.txt');
var url3 = path.resolve('D:/vajoy', 'abc', 'D:/a');
var url4 = path.resolve('abc', 'vajoy', 'ok.gif');
var url5 = path.resolve('abc', '/vajoy', '..', 'a/../subfile'); //'abc'引數將被忽略,源路徑改從'D:/vajoy'開始
console.log('url1:',url1); //D:\temp\test\trdLayer
console.log('url2:',url2); //D:\temp\testFiles\a.txt
console.log('url3:',url3); //D:\a
console.log('url4:',url4); //D:\temp\test\abc\vajoy\ok.gif
console.log('url5:',url5); //D:\subfile
複製程式碼
path.relative(from, to)
獲取從 from 到 to 的相對路徑(即,基於from路徑的兩路徑間的相互關係),可以看作 path.resolve 的相反實現
var url1 = path.relative('C:\\vajoy\\test\\aaa', 'C:\\vajoy\\impl\\bbb');
var url2 = path.relative('C:/vajoy/test/aaa', 'C:/vajoy/bbb');
var url3 = path.relative('C:/vajoy/test/aaa', 'D:/vajoy/bbb');
console.log('url1:',url1); //..\..\impl\bbb
console.log('url2:',url2); //..\..\bbb
console.log('url3:',url3); //D:\vajoy\bbb
複製程式碼
- 如果from、to指向同個路徑,那麼,返回空字串。
- 如果from、to中任一者為空,那麼,返回當前工作路徑。
path.isAbsolute(path)
判斷 path 是否絕對路徑。這塊可以理解為,path 是否真的是一個絕對路徑(比如 'E:/abc'),或者是以“/”開頭的路徑,二者都會返回true
var url1 = path.isAbsolute('../testFiles/secLayer');
var url2 = path.isAbsolute('./join.js');
var url3 = path.isAbsolute('temp');
var url4 = path.isAbsolute('/temp/../..');
var url5 = path.isAbsolute('E:/github/nodeAPI/abc/efg');
var url6 = path.isAbsolute('///temp123');
console.log('url1:',url1); // false
console.log('url2:',url2); // false
console.log('url3:',url3); // false
console.log('url4:',url4); // true
console.log('url5:',url5); // true
console.log('url6:',url6); // true
複製程式碼
path.dirname(p)
返回路徑中資料夾的路徑
var url1 = path.dirname('/foo/bar/baz/asdf/a.txt');
var url2 = path.dirname('/foo/bar/baz/asdf/');
var url3 = path.dirname('C:/vajoy/test/aaa');
var url4 = path.dirname(__dirname + '/docs/a.txt')
console.log('url1:',url1); // /foo/bar/baz/asdf
console.log('url2:',url2); // /foo/bar/baz
console.log('url3:',url3); // C:/vajoy/test
console.log(url4);// D:\mobileWeb\temp\test/docs
複製程式碼
path.basename(p, [ext])
返回路徑中的最後一部分(通常為檔名),類似於Unix 的 basename 命令。 ext 為需要截掉的尾綴內容
var url1 = path.basename('/foo/bar/baz/asdf/a.txt');
var url2 = path.basename('/foo/bar/baz/asdf/a.txt','.txt');
var url3 = path.basename('/foo/bar/baz/asdf/');
var url4 = path.basename('C:/vajoy/test/aaa');
console.log('url1:',url1); // a.txt
console.log('url2:',url2); // a
console.log('url3:',url3); // asdf
console.log('url4:',url4); // aaa
複製程式碼
path.extname(p)
返回路徑檔案中的副檔名(若存在)
var url1 = path.extname('/foo/bar/baz/asdf/a.txt');
var url2 = path.extname('/foo/bar/baz/asdf/a.txt.html');
var url3 = path.extname('/foo/bar/baz/asdf/a.');
var url4 = path.extname('C:/vajoy/test/.');
var url5 = path.extname('C:/vajoy/test/a');
console.log('url1:',url1); // .txt
console.log('url2:',url2); // .html
console.log('url3:',url3); // .
console.log('url4:',url4); //
console.log('url5:',url5); //
複製程式碼
path.parse(pathString)
返回路徑字串的物件
var url1 = path.parse('/foo/bar/baz/asdf/a.txt');
url1: {
root: '/',//根目錄
dir: '/foo/bar/baz/asdf',//檔案所在目錄
base: 'a.txt',//檔名,輸出檔名稱以base為準,base為空,則不輸出檔名
ext: '.txt',//副檔名
name: 'a',//檔名稱, 不含副檔名(name返回的是檔名或最後資料夾名)
}
var url2=path.parse('C:\\path\\dir\\');
{ root: 'C:\\',
dir: 'C:\\path',
base: 'dir',
ext: '',
name: 'dir'
}
var url3=path.format({
root:'f:',
dir:'f:\\dir1\\dir2',
name:'file',
base:'file.nanme',
ext:'.txt'
});
//f:\dir1\dir2\file.nanme
var url3=path.format({
root:'f:',
dir:'f:\\dir1\\dir2',
name:'file',
ext:'.txt'
});
//f:\dir1\dir2\
複製程式碼
path.format(pathObject)
從物件中返回路徑字串,和 path.parse 相反
var pathObj = {
root: '/',
dir: '/foo/bar/baz/asdf',
base: 'a.txt',
ext: '.txt',
name: 'a'
}
var url1 = path.format(pathObj);
console.log('url1:',url1);//url1: /foo/bar/baz/asdf\a.txt
複製程式碼
path.sep
*返回對應平臺下的資料夾分隔符,win下為'',nix下為'/'
var url1 = path.sep;
var url2 = 'foo\\bar\\baz'.split(path.sep);
var url3 = 'foo/bar/baz'.split(path.sep);
console.log('url1:',url1); // win下為\,*nix下為/
console.log('url2:',url2); // [ 'foo', 'bar', 'baz' ]?
console.log('url3:',url3); // win下返回[ 'foo/bar/baz' ],但在*nix系統下會返回[ 'foo', 'bar', 'baz' ]
複製程式碼
path.delimiter
*返回對應平臺下的路徑分隔符,win下為';',nix下為':'
var env = process.env.PATH; //當前系統的環境變數PATH
var url1 = env.split(path.delimiter);
console.log(path.delimiter);
//win下為“;”,*nix下為“:”
console.log('env:',env);
// C:\ProgramData\Oracle\Java\javapath;C:\Program Files (x86)\Intel\iCLS Client\;
console.log('url1:',url1);
// ['C:\ProgramData\Oracle\Java\javapath','C:\Program Files (x86)\Intel\iCLS Client\']
複製程式碼
http 網路請求模組
推薦文章
fs 檔案系統操作模組
模組簡介
nodejs path 模組提供了一些用於處理檔案系統的小工具,我們可以通過如下方式引用它
var path = require("fs")
複製程式碼
同步&&非同步API
使用require('fs')
載入fs模組,模組中所有方法都有同步和非同步兩種形式。
非同步方法中回撥函式的第一個引數總是留給異常引數(exception),如果方法成功完成,該引數為null或undefined。
fs.readFile('./test.txt', function(err, data) {
if (err) throw err;
console.log('檔案內容:'+ data);
});
複製程式碼
同步寫法,一般都是在非同步方法名後拼接Sycn
字串,表示是同步方法
var data = fs.readFileSync('./test.txt');
console.log('檔案內容:'+ data);
複製程式碼
同步方法執行完並返回結果後,才能執行後續的程式碼。而非同步方法採用回撥函式接收返回結果,可以立即執行後續程式碼。下面的程式碼演示,都已非同步邏輯為主。
fs.readFile
/**
* filename, 必選引數,檔名
* [options],可選引數,可指定flag(檔案操作選項,如r+ 讀寫;w+ 讀寫,檔案不存在則建立)及encoding屬性
* callback 讀取檔案後的回撥函式,引數預設第一個err,第二個data 資料
*/
fs.readFile(__dirname + '/test.txt', {flag: 'r+', encoding: 'utf8'}, function (err, data) {
if(err) throw err;
console.log(data);
});
複製程式碼
fs.writeFile
var w_data = '這是一段通過fs.writeFile函式寫入的內容;\r\n';
//w_data = new Buffer(w_data);//可以將字串轉換成Buffer型別
/**
* filename, 必選引數,檔名
* data, 寫入的資料,可以字元或一個Buffer物件
* [options],flag,mode(許可權),encoding
* callback 讀取檔案後的回撥函式,引數預設第一個err,第二個data 資料
*/
fs.writeFile(__dirname + '/test.txt', w_data, {flag: 'a'}, function (err) {
if(err) throw err;
console.log('寫入成功');
});
複製程式碼
{flag: 'a'}
加上這個引數,內容將會被以追加方式寫入檔案,不加上這個引數則會先清空內容,再寫入資料
fs.open(filename, flags, [mode], callback);
/**
* filename, 必選引數,檔名
* flags, 操作標識,如"r",讀方式開啟
* [mode],許可權,如777,表示任何使用者讀寫可執行
* callback 開啟檔案後回撥函式,引數預設第一個err,第二個fd為一個整數,表示開啟檔案返回的檔案描述符,window中又稱檔案控制程式碼
*/
fs.open(__dirname + '/test.txt', 'r', '0666', function (err, fd) {
console.log(fd);
});
複製程式碼
fs.read(fd, buffer, offset, length, position, callback);
講檔案內容讀入快取區 /** * fd, 使用fs.open開啟成功後返回的檔案描述符 * buffer, 一個Buffer物件,v8引擎分配的一段記憶體 * offset, 整數,向快取區中寫入時的初始位置,以位元組為單位 * length, 整數,讀取檔案的長度 * position, 整數,讀取檔案初始位置;檔案大小以位元組為單位 * callback(err, bytesRead, buffer), 讀取執行完成後回撥函式,bytesRead實際讀取位元組數,被讀取的快取區物件 */
fs.open(__dirname + '/test.txt', 'r', function (err, fd) {
if(err) {
console.error(err);
return;
} else {
var buffer = new Buffer(255);
console.log(buffer.length);
//每一個漢字utf8編碼是3個位元組,英文是1個位元組
fs.read(fd, buffer, 0, 9, 3, function (err, bytesRead, buffer) {
if(err) {
throw err;
} else {
console.log(bytesRead);
console.log(buffer.slice(0, bytesRead).toString());
//讀取完後,再使用fd讀取時,基點是基於上次讀取位置計算;
fs.read(fd, buffer, 0, 9, null, function (err, bytesRead, buffer) {
console.log(bytesRead);
console.log(buffer.slice(0, bytesRead).toString());
});
}
});
}
});
複製程式碼
fs.write(fd, buffer, offset, length, position, callback);
寫檔案,將緩衝區內資料寫入使用fs.open開啟的檔案
/**
* fd, 使用fs.open開啟成功後返回的檔案描述符
* buffer, 一個Buffer物件,v8引擎分配的一段記憶體
* offset, 整數,從快取區中讀取時的初始位置,以位元組為單位
* length, 整數,從快取區中讀取資料的位元組數
* position, 整數,寫入檔案初始位置;
* callback(err, written, buffer), 寫入操作執行完成後回撥函式,written實際寫入位元組數,buffer被讀取的快取區物件
*/
fs.open(__dirname + '/test.txt', 'a', function (err, fd) {
if(err) {
console.error(err);
return;
} else {
var buffer = new Buffer('寫入檔案資料內容');
//寫入'入檔案'三個字
fs.write(fd, buffer, 3, 9, 12, function (err, written, buffer) {
if(err) {
console.log('寫入檔案失敗');
console.error(err);
return;
} else {
console.log(buffer.toString());
//寫入'資料內'三個字
fs.write(fd, buffer, 12, 9, null, function (err, written, buffer) {
console.log(buffer.toString());
// 使用fs.write寫入檔案時,作業系統是將資料讀到記憶體,再把資料寫入到檔案中,所以當資料讀完時並不代表資料已經寫完,因為有一部分還可能在內在緩衝區內。
// 因此可以使用fs.fsync方法將記憶體中資料寫入檔案,重新整理記憶體緩衝區;
fs.fsync(fd);
fs.close(fd);
})
}
});
}
});
複製程式碼
fs.mkdir(path, [mode], callback)
建立目錄
/**
* path, 被建立目錄的完整路徑及目錄名;
* [mode], 目錄許可權,預設0777
* [callback(err)], 建立完目錄回撥函式,err錯誤物件
*/
fs.mkdir(__dirname + '/fsDir', function (err) {
if(err)
throw err;
console.log('建立目錄成功')
});
複製程式碼
fs.readdir(path, callback)
讀取目錄
/**
* path, 要讀取目錄的完整路徑及目錄名;
* [callback(err, files)], 讀完目錄回撥函式;err錯誤物件,files陣列,存放讀取到的目錄中的所有檔名
*/
fs.readdir(__dirname + '/fsDir/', function (err, files) {
if(err) {
console.error(err);
return;
} else {
files.forEach(function (file) {
var filePath = path.normalize(__dirname + '/fsDir/' + file);
fs.stat(filePath, function (err, stat) {
if(stat.isFile()) {
console.log(filePath + ' is: ' + 'file');
}
if(stat.isDirectory()) {
console.log(filePath + ' is: ' + 'dir');
}
});
});
}
});
複製程式碼
fs.stat(path, callback);
檢視檔案與目錄資訊
fs.stat(__dirname + '/test.txt', function (err, stat) {
console.log('訪問時間: ' + stat.atime.toString() + '; \n修改時間:' + stat.mtime);
console.log(stat.mode);
})
複製程式碼
fs.exists(path, callback);
檢視檔案與目錄是否存在
/**
* path, 要檢視目錄/檔案的完整路徑及名;
* [callback(exists)], 操作完成回撥函式;exists true存在,false表示不存在
*/
fs.exists(__dirname + '/test', function (exists) {
var retTxt = exists ? retTxt = '檔案存在' : '檔案不存在';
console.log(retTxt);
});
複製程式碼
fs.rename(oldPath, newPath, callback);
移動/重新命名檔案或目錄
/**
* oldPath, 原目錄/檔案的完整路徑及名;
* newPath, 新目錄/檔案的完整路徑及名;如果新路徑與原路徑相同,而只檔名不同,則是重新命名
* [callback(err)], 操作完成回撥函式;err操作失敗物件
*/
fs.rename(__dirname + '/test', __dirname + '/fsDir', function (err) {
if(err) {
console.error(err);
return;
}
console.log('重新命名成功')
});
複製程式碼
fs.rmdir(path, callback);
刪除空目錄
/**
* path, 目錄的完整路徑及目錄名;
* [callback(err)], 操作完成回撥函式;err操作失敗物件
*/
fs.mkdir(__dirname + '/test', function(err){
fs.rmdir(__dirname + '/test', function (err) {
if(err) {
console.log('刪除空目錄失敗,可能原因:1、目錄不存在,2、目錄不為空')
console.error(err);
return;
}
console.log('刪除空目錄成功!');
});
})
複製程式碼
fs.watchFile(filename, [options], listener);
對檔案進行監視,並且在監視到檔案被修改時執行處理
/**
* filename, 完整路徑及檔名;
* [options], persistent true表示持續監視,不退出程式;interval 單位毫秒,表示每隔多少毫秒監視一次檔案
* listener, 檔案發生變化時回撥,有兩個引數:curr為一個fs.Stat物件,被修改後檔案,prev,一個fs.Stat物件,表示修改前物件
*/
fs.watchFile(__dirname + '/test.txt', {interval: 20}, function (curr, prev) {
if(Date.parse(prev.ctime) == 0) {
console.log('檔案被建立!');//?
} else if(Date.parse(curr.ctime) == 0) {
console.log('檔案被刪除!')
} else if(Date.parse(curr.mtime) != Date.parse(prev.mtime)) {
console.log('檔案有修改');
}
});
fs.watchFile(__dirname + '/test.txt', function (curr, prev) {
console.log('這是第二個watch,監視到檔案有修改');
});
複製程式碼
fs.watch(filename, [options], [listener]);
對檔案或目錄進行監視,並且在監視到修改時執行處理; fs.watch返回一個fs.FSWatcher物件,擁有一個close方法,用於停止watch操作; 當fs.watch有檔案變化時,會觸發fs.FSWatcher物件的change(err, filename)事件,err錯誤物件,filename發生變化的檔名
/**
* filename, 完整路徑及檔名或目錄名;
* [listener(event, filename], 監聽器事件,有兩個引數:event 為rename表示指定的檔案或目錄中有重新命名、刪除或移動操作或change表示有修改,filename表示發生變化的檔案路徑
*/
var fsWatcher = fs.watch(__dirname + '/test', function (event, filename) {
//console.log(event)
});
//console.log(fsWatcher instanceof FSWatcher);
fsWatcher.on('change', function (event, filename) {
console.log(filename + ' 發生變化')
});
//30秒後關閉監視
setTimeout(function () {
console.log('關閉')
fsWatcher.close(function (err) {
if(err) {
console.error(err)
}
console.log('關閉watch')
});
}, 30000);
複製程式碼
檔案流
/*
* 流,在應用程式中表示一組有序的、有起點有終點的位元組資料的傳輸手段;
* Node.js中實現了stream.Readable/stream.Writeable介面的物件進行流資料讀寫;以上介面都繼承自EventEmitter類,因此在讀/寫流不同狀態時,觸發不同事件;
* 關於流讀取:Node.js不斷將檔案一小塊內容讀入緩衝區,再從緩衝區中讀取內容;
* 關於流寫入:Node.js不斷將流資料寫入內在緩衝區,待緩衝區滿後再將緩衝區寫入到檔案中;重複上面操作直到要寫入內容寫寫完;
* readFile、read、writeFile、write都是將整個檔案放入記憶體而再操作,而則是檔案一部分資料一部分資料操作;
*
* -----------------------流讀取-------------------------------------
* 讀取資料物件:
* fs.ReadStream 讀取檔案
* http.IncomingMessage 客戶端請求或伺服器端響應
* net.Socket Socket埠物件
* child.stdout 子程式標準輸出
* child.stdin 子程式標準入
* process.stdin 用於建立程式標準輸入流
* Gzip、Deflate、DeflateRaw 資料壓縮
*
* 觸發事件:
* readable 資料可讀時
* data 資料讀取後
* end 資料讀取完成時
* error 資料讀取錯誤時
* close 關閉流物件時
*
* 讀取資料的物件操作方法:
* read 讀取資料方法
* setEncoding 設定讀取資料的編
* pause 通知物件眾目停止觸發data事件
* resume 通知物件恢復觸發data事件
* pipe 設定資料通道,將讀入流資料接入寫入流;
* unpipe 取消通道
* unshift 當流資料繫結一個解析器時,此方法取消解析器
*
* ------------------------流寫入-------------------------------------
* 寫資料物件:
* fs.WriteStream 寫入檔案物件
* http.clientRequest 寫入HTTP客戶端請求資料
* http.ServerResponse 寫入HTTP伺服器端響應資料
* net.Socket 讀寫TCP流或UNIX流,需要connection事件傳遞給使用者
* child.stdout 子程式標準輸出
* child.stdin 子程式標準入
* Gzip、Deflate、DeflateRaw 資料壓縮
*
* 寫入資料觸發事件:
* drain 當write方法返回false時,表示快取區中已經輸出到目標物件中,可以繼續寫入資料到快取區
* finish 當end方法呼叫,全部資料寫入完成
* pipe 當用於讀取資料的物件的pipe方法被呼叫時
* unpipe 當unpipe方法被呼叫
* error 當發生錯誤
*
* 寫入資料方法:
* write 用於寫入資料
* end 結束寫入,之後再寫入會報錯;
*/
複製程式碼
fs.createReadStream(path, [options])
建立讀取流
/**
* path 檔案路徑
* [options] flags:指定檔案操作,預設'r',讀操作;encoding,指定讀取流編碼;autoClose, 是否讀取完成後自動關閉,預設true;start指定檔案開始讀取位置;end指定檔案開始讀結束位置
*/
var rs = fs.createReadStream(__dirname + '/test.txt', {start: 0, end: 2});
//open是ReadStream物件中表示檔案開啟時事件,
rs.on('open', function (fd) {
console.log('開始讀取檔案');
});
rs.on('data', function (data) {
console.log(data.toString());
});
rs.on('end', function () {
console.log('讀取檔案結束')
});
rs.on('close', function () {
console.log('檔案關閉');
});
rs.on('error', function (err) {
console.error(err);
});
//暫停和回覆檔案讀取;
rs.on('open', function () {
console.log('開始讀取檔案');
});
rs.pause();
rs.on('data', function (data) {
console.log(data.toString());
});
setTimeout(function () {
rs.resume();
}, 2000);
複製程式碼
fs.createWriteStream(path, [options])
建立寫入流
/**
* path 檔案路徑
* [options] flags:指定檔案操作,預設'w',;encoding,指定讀取流編碼;start指定寫入檔案的位置
*/
/* ws.write(chunk, [encoding], [callback]);
* chunk, 可以為Buffer物件或一個字串,要寫入的資料
* [encoding], 編碼
* [callback], 寫入後回撥
*/
/* ws.end([chunk], [encoding], [callback]);
* [chunk], 要寫入的資料
* [encoding], 編碼
* [callback], 寫入後回撥
*/
var ws = fs.createWriteStream(__dirname + '/test.txt', {start: 0});
var buffer = new Buffer('我也喜歡你');
ws.write(buffer, 'utf8', function (err, buffer) {
console.log(arguments);
console.log('寫入完成,回撥函式沒有引數')
});
//最後再寫入的內容
ws.end('再見');
//使用流完成複製檔案操作
var rs = fs.createReadStream(__dirname + '/test.txt')
var ws = fs.createWriteStream(__dirname + '/test/test.txt');
rs.on('data', function (data) {
ws.write(data)
});
ws.on('open', function (fd) {
console.log('要寫入的資料檔案已經開啟,檔案描述符是: ' + fd);
});
rs.on('end', function () {
console.log('檔案讀取完成');
ws.end('完成', function () {
console.log('檔案全部寫入完成')
});
});
//關於WriteStream物件的write方法返回一個布林型別,當快取區中資料全部寫滿時,返回false;
//表示快取區寫滿,並將立即輸出到目標物件中
//第一個例子
var ws = fs.createWriteStream(__dirname + '/test/test.txt');
for (var i = 0; i < 10000; i++) {
var w_flag = ws.write(i.toString());
//當快取區寫滿時,輸出false
console.log(w_flag);
}
//第二個例子
var ws = fs.createWriteStream(__dirname + '/test/untiyou.mp3');
var rs = fs.createReadStream(__dirname + '/test/Until You.mp3');
rs.on('data', function (data) {
var flag = ws.write(data);
console.log(flag);
});
//系統快取區資料已經全部輸出觸發drain事件
ws.on('drain', function () {
console.log('系統快取區資料已經全部輸出。')
});
複製程式碼
rs.pipe(destination, [options]);
管道pipe實現流讀寫
//rs.pipe(destination, [options]);
/**
* destination 必須一個可寫入流資料物件
* [opations] end 預設為true,表示讀取完成立即關閉檔案;
*/
var rs = fs.createReadStream(__dirname + '/test/Until You.mp3');
var ws = fs.createWriteStream(__dirname + '/test/untiyou.mp3');
rs.pipe(ws);
rs.on('data', function (data) {
console.log('資料可讀')
});
rs.on('end', function () {
console.log('檔案讀取完成');
//ws.end('再見')
});
複製程式碼
推薦文章
url 處理模組
引用
var url = require("url");
複製程式碼
URL 的組成介紹
對於一個 URL 字串,其組成部分會有所有不同,其中有些部分只有在URL字串中存在時,對應欄位才會出現在解析後物件中。以下是一個 URL 例子:
http://user:pass@host.com:8080/p/a/t/h?query=string#hash
href: 解析前的完整原始 URL,協議名和主機名已轉為小寫
例如: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'
protocol: 請求協議,小寫
例如: 'http:'
slashes: 協議的“:”號後是否有“/”
例如: true or false
auth: URL中的認證資訊
例如: 'user:pass'
host: URL主機名,包括埠資訊,小寫
例如: 'host.com:8080'
hostname: 主機名,小寫
例如: 'host.com'
port: 主機的埠號
例如: '8080'
path: pathname 和 search的合集
例如: '/p/a/t/h?query=string'
pathname: URL中路徑
例如: '/p/a/t/h'
search: 查詢物件,即:queryString,包括之前的問號“?”
例如: '?query=string'
query: 查詢字串中的引數部分(問號後面部分字串),或者使用 querystring.parse() 解析後返回的物件
例如: 'query=string' or {'query':'string'}
hash: 錨點部分(即:“#”及其後的部分)
例如: '#hash'
複製程式碼
url.parse(urlStr[, parseQueryString][, slashesDenoteHost])
將URL字串轉換為JSON物件
var urlString = 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash';
var result = url.parse(urlString);
console.log(result);
//輸出結果如下
{ protocol: 'http:',
slashes: true,
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/p/a/t/h',
path: '/p/a/t/h?query=string',
href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'
}
//第二個可選引數設定為true時,會使用querystring模組來解析URL中德查詢字串部分,預設為 false。
var result1 = url.parse(urlString, true);
console.log(result1);
//輸出結果如下
{ protocol: 'http:',
slashes: true,
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: {query:"string"},
pathname: '/p/a/t/h',
path: '/p/a/t/h?query=string',
href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'
}
複製程式碼
url.format(urlObj)
用於格式化URL物件。輸入一個 URL 物件,返回格式化後的 URL 字串。示例如下
var urlObj = {
protocol: 'http:',
slashes: true,
hostname: 'jianshu.com',
port: 80,
hash: '#hash',
search: '?query=string',
path: '/nodejs?query=string'
}
var result = url.format(urlObj);
console.log(result);
//輸出結果如下
http://jianshu.com:80?query=string#hash
/*
*傳入的URL物件會做以下處理:
*
*href 屬性會被忽略
*protocol無論是否有末尾的 : (冒號),會同樣的處理
**這些協議包括 http, https, ftp, gopher, file 字尾是 :// (冒號-斜槓-斜槓).
**所有其他的協議如 mailto, xmpp, aim, sftp, foo, 等 會加上字尾 : (冒號)
*auth 如果有將會出現.
*host 優先使用,將會替代 hostname 和port
*hostname 如果 host 屬性沒被定義,則會使用此屬性.
*port 如果 host 屬性沒被定義,則會使用此屬性.
*pathname 將會同樣處理無論結尾是否有/ (斜槓)
*search 將會替代 query屬性
*query (object型別; 詳細請看 querystring) 如果沒有 search,將會使用此屬性.
*search 無論前面是否有 ? (問號),都會同樣的處理
*hash無論前面是否有# (井號, 錨點),都會同樣處理
*/
複製程式碼
url.resolve(from, to)
用於拼接路徑
url.resolve('/one/two/three', 'four') // '/one/two/four'
url.resolve('http://example.com/', '/one') // 'http://example.com/one'
url.resolve('http://example.com/one', '/two') // 'http://example.com/two'
複製程式碼
query string 引數處理模組
引用
var querystring = require('querystring')
複製程式碼
querystring.stringify(obj, [sep], [eq])
物件格式化成引數字串 ,obj就是要格式化的物件,必選引數;[sep]指分隔符 預設'&'; [eq]指分配符 預設'='
var querystring = require('querystring')
var param = {name:"feng",age:"33"};
var paramStr1 = querystring.stringify(param);
console.log(paramStr1);//name=feng&age=33
var paramStr2 = querystring.stringify(param,'$','-');
console.log(paramStr2);//name-feng$age-33
複製程式碼
本方法會自動編碼漢字
querystring.parse(str, [sep], [eq], [options])
引數字串格式化成物件
var paramStr1 = 'name=feng&age=33';
var paramStr2 = 'name-feng$age-33';
var param1 = querystring.parse(paramStr1);
console.log(param1);//{ name: 'feng', age: '33' }
var param2 = querystring.parse(paramStr2, '$', '-');
console.log(param2);//{ name: 'feng', age: '33' }
複製程式碼
querystring.escape
引數編碼
var param = "name=阿峰&age=33";
console.log(querystring.escape(param));
//name%3D%E9%98%BF%E5%B3%B0%26age%3D33
複製程式碼
querystring.unescape
引數解碼
var param = "name=阿峰&age=33";
console.log(querystring.unescape(querystring.escape(param)));
//name=阿峰&age=33
複製程式碼
os
引用
var os = require('os');
複製程式碼
常用函式
//cpu架構
os.arch();
//作業系統核心
os.type();
//作業系統平臺
os.platform();
//系統開機時間
os.uptime();
//主機名
os.hostname();
//主目錄
os.homedir();
//記憶體
os.totalmem();//總記憶體
os.freemem();// 空閒記憶體
//cpu
const cpus = os.cpus();
cpus.forEach((cpu,idx,arr)=>{
var times = cpu.times;
console.log(`cpu${idx}:`);
console.log(`型號:${cpu.model}`);
console.log(`頻率:${cpu.speed}MHz`);
console.log(`使用率:${((1-times.idle/(times.idle+times.user+times.nice+times.sys+times.irq))*100).toFixed(2)}%`);
});
//網路卡
const networksObj = os.networkInterfaces();
for(let nw in networksObj){
let objArr = networksObj[nw];
console.log(`\r\n${nw}:`);
objArr.forEach((obj,idx,arr)=>{
console.log(`地址:${obj.address}`);
console.log(`掩碼:${obj.netmask}`);
console.log(`實體地址:${obj.mac}`);
console.log(`協議族:${obj.family}`);
});
}
複製程式碼
stream
為什麼使用流
nodejs的fs模組並沒有提供一個copy的方法,但我們可以很容易的實現一個,比如:
var source = fs.readFileSync('/path/to/source', {encoding: 'utf8'});
fs.writeFileSync('/path/to/dest', source);
複製程式碼
上面的這段程式碼並沒有什麼問題,但是在每次請求時,我們都會把整個原始檔讀入到記憶體中,然後再把結果返回給客戶端。想想看,如果原始檔非常大,在響應大量使用者的併發請求時,程式可能會消耗大量的記憶體,這樣很可能會造成使用者連線緩慢的問題。
理想的方法應該是讀一部分,寫一部分,不管檔案有多大,只要時間允許,總會處理完成,這裡就需要用到流的概念。
上面的檔案複製可以簡單實現一下:
var fs = require('fs');
var readStream = fs.createReadStream('/path/to/source');
var writeStream = fs.createWriteStream('/path/to/dest');
readStream.on('data', function(chunk) { // 當有資料流出時,寫入資料
writeStream.write(chunk);
});
readStream.on('end', function() { // 當沒有資料時,關閉資料流
writeStream.end();
});
複製程式碼
上面的寫法有一些問題,如果寫入的速度跟不上讀取的速度,有可能導致資料丟失。正常的情況應該是,寫完一段,再讀取下一段,如果沒有寫完的話,就讓讀取流先暫停,等寫完再繼續,於是程式碼可以修改為:
var fs = require('fs');
var readStream = fs.createReadStream('/path/to/source');
var writeStream = fs.createWriteStream('/path/to/dest');
readStream.on('data', function(chunk) { // 當有資料流出時,寫入資料
if (writeStream.write(chunk) === false) { // 如果沒有寫完,暫停讀取流
readStream.pause();
}
});
writeStream.on('drain', function() { // 寫完後,繼續讀取
readStream.resume();
});
readStream.on('end', function() { // 當沒有資料時,關閉資料流
writeStream.end();
});
複製程式碼
或者使用更直接的pipe
// pipe自動呼叫了data,end等事件
fs.createReadStream('/path/to/source').pipe(fs.createWriteStream('/path/to/dest'));
複製程式碼
下面是一個完整的複製檔案的過程
var fs = require('fs'),
path = require('path'),
out = process.stdout;
var filePath = 'Users/feng/Documents/something/kobe.gif';
var readStream = fs.createReadStream(filePath);
var writeStream = fs.createWriteStream('file.gif');
var stat = fs.statSync(filePath);
var totalSize = stat.size;
var passedLength = 0;
var lastSize = 0;
var startTime = Date.now();
readStream.on('data', function(chunk) {
passedLength += chunk.length;
if (writeStream.write(chunk) === false) {
readStream.pause();
}
});
readStream.on('end', function() {
writeStream.end();
});
writeStream.on('drain', function() {
readStream.resume();
});
setTimeout(function show() {
var percent = Math.ceil((passedLength / totalSize) * 100);
var size = Math.ceil(passedLength / 1000000);
var diff = size - lastSize;
lastSize = size;
out.clearLine();
out.cursorTo(0);
out.write('已完成' + size + 'MB, ' + percent + '%, 速度:' + diff + 'MB/s');
if (passedLength < totalSize) {
setTimeout(show, 500);
} else {
var endTime = Date.now();
console.log();
console.log('共用時:' + (endTime - startTime) / 1000 + '秒。');
}
}, 500);
複製程式碼
Readable流
fs.createReadStream(path[, options])
用來開啟一個可讀的檔案流,它返回一個fs.ReadStream
物件。path引數指定檔案的路徑,可選的options是一個JS物件,可以指定一些選項,類似下面這樣:
{ flags: 'r',
encoding: 'utf8',
fd: null,
mode: 0666,
autoClose: true
}
複製程式碼
options的flags屬性指定用什麼模式開啟檔案:
- ’w’代表寫,’r’代表讀,類似的還有’r+’、’w+’、’a’等,與Linux下的open函式接受的讀寫模式類似。
- encoding指定開啟檔案時使用編碼格式,預設就是“utf8”,你還可以為它指定”ascii”或”base64”。
- fd屬性預設為null,當你指定了這個屬性時,createReadableStream會根據傳入的fd建立一個流,忽略path。另外你要是想讀取一個檔案的特定區域,可以配置start、end屬性,指定起始和結束(包含在內)的位元組偏移。
- autoClose屬性為true(預設行為)時,當發生錯誤或檔案讀取結束時會自動關閉檔案描述符。
Readable還提供了一些函式,我們可以用它們讀取或操作流:
- read([size]):如果你給read方法傳遞了一個制定大小作為引數,那它會返回指定數量的資料,如果資料不足,就會返回null。如果你不給read方法傳參,它會返回內部緩衝區裡的所有資料,如果沒有資料,會返回null,此時有可能說明遇到了檔案末尾。read返回的資料可能是Buffer物件,也可能是String物件。
- setEncoding(encoding):給流設定一個編碼格式,用於解碼讀到的資料。呼叫此方法後,read([size])方法返回String物件。
- pause():暫停可讀流,不再發出data事件
- resume():恢復可讀流,繼續發出data事件
- pipe(destination,[options]):把這個可讀流的輸出傳遞給destination指定的Writable流,兩個流組成一個管道。options是一個JS物件,這個物件有一個布林型別的end屬性,預設值為true,當end為true時,Readable結束時自動結束Writable。注意,我們可以把一個Readable與若干Writable連在一起,組成多個管道,每一個Writable都能得到同樣的資料。這個方法返回destination,如果destination本身又是Readable流,就可以級聯呼叫pipe(比如我們在使用gzip壓縮、解壓縮時就會這樣,馬上會講到)。
- unpipe([destination]):埠與指定destination的管道。不傳遞destination時,斷開與這個可讀流連在一起的所有管道。
Readable流提供了以下事件:
- readable:在資料塊可以從流中讀取的時候發出。它對應的處理器沒有引數,可以在處理器裡呼叫read([size])方法讀取資料。
- data:有資料可讀時發出。它對應的處理器有一個引數,代表資料。如果你只想快快地讀取一個流的資料,給data關聯一個處理器是最方便的辦法。處理器的引數是Buffer物件,如果你呼叫了Readable的setEncoding(encoding)方法,處理器的引數就是String物件。
- end:當資料被讀完時發出。對應的處理器沒有引數。
- close:當底層的資源,如檔案,已關閉時發出。不是所有的Readable流都會發出這個事件。對應的處理器沒有引數。
- error:當在接收資料中出現錯誤時發出。對應的處理器引數是Error的例項,它的message屬性描述了錯誤原因,stack屬性儲存了發生錯誤時的堆疊資訊。
一個基本的可讀流例項
var fs = require('fs');
var readable = fs.createReadStream('text.js',{
flags: 'r',
encoding: 'utf8',
autoClose: true,
mode: 0666,
});
readable.on('open', function(fd){
console.log('file was opened, fd - ', fd);
});
readable.on('readable', function(){
console.log('received readable');
});
readable.on('data', function(chunk){
console.log('read %d bytes: %s', chunk.length, chunk);
});
readable.on('end', function(){
console.log('read end');
});
readable.on('close', function(){
console.log('file was closed.');
});
readable.on('error', function(err){
console.log('error occured: %s', err.message);
});
複製程式碼
Writable
Writable流提供了一個介面,用來把資料寫入到目的裝置(或記憶體)中。
Writable提供了一些函式來操作流
-
write(chunk[,encoding][,callback])
可以把資料寫入流中。其中,chunk是待寫入的資料,是Buffer或String物件。這個引數是必須的,其它引數都是可選的。如果chunk是String物件,encoding可以用來指定字串的編碼格式,write會根據編碼格式將chunk解碼成位元組流再來寫入。callback是資料完全重新整理到流中時會執行的回撥函式。write方法返回布林值,當資料被完全處理後返回true(不一定是完全寫入裝置哦)。 -
end([chunk] [,encoding][,callback])
方法可以用來結束一個可寫流。它的三個引數都是可選的。chunk和encoding的含義與write方法類似。callback是一個可選的回撥,當你提供它時,它會被關聯到Writable的finish事件上,這樣當finish事件發射時它就會被呼叫。
現在我們來看看Writable公開的事件:
finish: 在end()被呼叫、所有資料都已被寫入底層裝置後發射。對應的處理器函式沒有引數。 pipe: 當你在Readable流上呼叫pipe()方法時,Writable流會發射這個事件,對應的處理器函式有一個引數,型別是Readable,指向與它連線的那個Readable流。 unpipe: 當你在Readable流上呼叫unpipe()方法時,Writable流會發射這個事件,對應的處理器函式有一個引數,型別是Readable,指向與剛與它斷開連線的那個Readable流。 error: 出錯時發射,對應的處理器函式的引數是Error物件。
看一個簡單例項
var fs = require('fs');
var writable = fs.createWriteStream('example.txt',{
flags: 'w',
defaultEncoding: 'utf8',
mode: 0666,
});
writable.on('finish', function(){
console.log('write finished');
process.exit(0);
});
writable.on('error', function(err){
console.log('write error - %s', err.message);
});
writable.write('hello 地方', 'utf8');
writable.end();
複製程式碼
其他流
略