Nodejs API 學習系列(一)

艾倫先生發表於2017-12-14

本文的主要內容是對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('再見')
});
複製程式碼

推薦文章

fs模組例項參考

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();
複製程式碼

其他流

參考文章

nodejs stream手冊

其他系列文章

Nodejs模組學習筆記 極客學院 nodejs官方文件

未完待續~

相關文章