<1>js 裡面的fs子產品
- readFile(),readFileSync()
- writeFile(),writeFileSync()
- exists(path, callback)
- mkdir(),writeFile(),readFile()
- mkdirSync(),writeFileSync(),readFileSync()
- readdir(),readdirSync()
- stat()
- watchfile(),unwatchfile()
- createReadStream()
- createWriteStream()
重要說明:本教程已經搬遷,此處不再維護,請通路新網址:wangdoc.com/javascript。
fs
是
filesystem
的縮寫,該子產品提供本地檔案的讀寫能力,基本上是POSIX檔案操作指令的簡單包裝。但是,這個子產品幾乎對所有操作提供異步和同步兩種操作方式,供開發者選擇。
readFile(),readFileSync()
readFile
方法用于異步讀取資料。
fs.readFile('./image.png', function (err, buffer) {
if (err) throw err;
process(buffer);
});
readFile
方法的第一個參數是檔案的路徑,可以是絕對路徑,也可以是相對路徑。注意,如果是相對路徑,是相對于目前程序所在的路徑(
process.cwd()
),而不是相對于目前腳本所在的路徑。
readFile
方法的第二個參數是讀取完成後的回調函數。該函數的第一個參數是發生錯誤時的錯誤對象,第二個參數是代表檔案内容的
Buffer
執行個體。
readFileSync
方法用于同步讀取檔案,傳回一個字元串。
var text = fs.readFileSync(fileName, 'utf8');
// 将檔案按行拆成數組
text.split(/\r?\n/).forEach(function (line) {
// ...
});
readFileSync
方法的第一個參數是檔案路徑,第二個參數可以是一個表示配置的對象,也可以是一個表示文本檔案編碼的字元串。預設的配置對象是
{ encoding: null, flag: 'r' }
,即檔案編碼預設為
null
,讀取模式預設為
r
(隻讀)。如果第二個參數不指定編碼(
encoding
),
readFileSync
方法傳回一個
Buffer
執行個體,否則傳回的是一個字元串。
不同系統的行結尾字元不同,可以用下面的方法判斷。
// 方法一,查詢現有的行結尾字元
var EOL =
fileContents.indexOf('\r\n') >= 0 ? '\r\n' : '\n';
// 方法二,根據目前系統處理
var EOL =
(process.platform === 'win32' ? '\r\n' : '\n');
writeFile(),writeFileSync()
writeFile
方法用于異步寫入檔案。
fs.writeFile('message.txt', 'Hello Node.js', (err) => {
if (err) throw err;
console.log('It\'s saved!');
});
上面代碼中,
writeFile
方法的第一個參數是寫入的檔案名,第二個參數是寫入的字元串,第三個參數是回調函數。
回調函數前面,還可以再加一個參數,表示寫入字元串的編碼(預設是
utf8
)。
fs.writeFile('message.txt', 'Hello Node.js', 'utf8', callback);
writeFileSync
方法用于同步寫入檔案。
fs.writeFileSync(fileName, str, 'utf8');
它的第一個參數是檔案路徑,第二個參數是寫入檔案的字元串,第三個參數是檔案編碼,預設為utf8。
exists(path, callback)
exists方法用來判斷給定路徑是否存在,然後不管結果如何,都會調用回調函數。
fs.exists('/path/to/file', function (exists) {
util.debug(exists ? "it's there" : "no file!");
});
上面代碼表明,回調函數的參數是一個表示檔案是否存在的布爾值。
需要注意的是,不要在
open
方法之前調用
exists
方法,open方法本身就能檢查檔案是否存在。
下面的例子是如果給定目錄存在,就删除它。
if (fs.existsSync(outputFolder)) {
console.log('Removing ' + outputFolder);
fs.rmdirSync(outputFolder);
}
mkdir(),writeFile(),readFile()
mkdir方法用于建立目錄。
var fs = require('fs');
fs.mkdir('./helloDir',0777, function (err) {
if (err) throw err;
});
mkdir接受三個參數,第一個是目錄名,第二個是權限值,第三個是回調函數。
writeFile方法用于寫入檔案。
var fs = require('fs');
fs.writeFile('./helloDir/message.txt', 'Hello Node', function (err) {
if (err) throw err;
console.log('檔案寫入成功');
});
readFile方法用于讀取檔案内容。
var fs = require('fs');
fs.readFile('./helloDir/message.txt','UTF-8' ,function (err, data) {
if (err) throw err;
console.log(data);
});
上面代碼使用readFile方法讀取檔案。readFile方法的第一個參數是檔案名,第二個參數是檔案編碼,第三個參數是回調函數。可用的檔案編碼包括“ascii”、“utf8”和“base64”。如果沒有指定檔案編碼,傳回的是原始的緩存二進制資料,這時需要調用buffer對象的toString方法,将其轉為字元串。
var fs = require('fs');
fs.readFile('example_log.txt', function (err, logData) {
if (err) throw err;
var text = logData.toString();
});
readFile方法是異步操作,是以必須小心,不要同時發起多個readFile請求。
for(var i = 1; i <= 1000; i++) {
fs.readFile('./'+i+'.txt', function() {
// do something with the file
});
}
上面代碼會同時發起1000個readFile異步請求,很快就會耗盡系統資源。
mkdirSync(),writeFileSync(),readFileSync()
這三個方法是建立目錄、寫入檔案、讀取檔案的同步版本。
fs.mkdirSync('./helloDirSync',0777);
fs.writeFileSync('./helloDirSync/message.txt', 'Hello Node');
var data = fs.readFileSync('./helloDirSync/message.txt','UTF-8');
console.log('file created with contents:');
console.log(data);
對于流量較大的伺服器,最好還是采用異步操作,因為同步操作時,隻有前一個操作結束,才會開始後一個操作,如果某個操作特别耗時(常常發生在讀寫資料時),會導緻整個程式停頓。
readdir(),readdirSync()
readdir
方法用于讀取目錄,傳回一個所包含的檔案和子目錄的數組。
fs.readdir(process.cwd(), function (err, files) {
if (err) {
console.log(err);
return;
}
var count = files.length;
var results = {};
files.forEach(function (filename) {
fs.readFile(filename, function (data) {
results[filename] = data;
count--;
if (count <= 0) {
// 對所有檔案進行處理
}
});
});
});
readdirSync
方法是
readdir
方法的同步版本。下面是同步列出目錄内容的代碼。
var files = fs.readdirSync(dir);
files.forEach(function (filename) {
var fullname = path.join(dir,filename);
var stats = fs.statSync(fullname);
if (stats.isDirectory()) filename += '/';
process.stdout.write(filename + '\t' +
stats.size + '\t' +
stats.mtime + '\n'
);
});
stat()
stat方法的參數是一個檔案或目錄,它産生一個對象,該對象包含了該檔案或目錄的具體資訊。我們往往通過該方法,判斷正在處理的到底是一個檔案,還是一個目錄。
var fs = require('fs');
fs.readdir('/etc/', function (err, files) {
if (err) throw err;
files.forEach( function (file) {
fs.stat('/etc/' + file, function (err, stats) {
if (err) throw err;
if (stats.isFile()) {
console.log("%s is file", file);
}
else if (stats.isDirectory ()) {
console.log("%s is a directory", file);
}
console.log('stats: %s',JSON.stringify(stats));
});
});
});
watchfile(),unwatchfile()
watchfile方法監聽一個檔案,如果該檔案發生變化,就會自動觸發回調函數。
var fs = require('fs');
fs.watchFile('./testFile.txt', function (curr, prev) {
console.log('the current mtime is: ' + curr.mtime);
console.log('the previous mtime was: ' + prev.mtime);
});
fs.writeFile('./testFile.txt', "changed", function (err) {
if (err) throw err;
console.log("file write complete");
});
unwatchfile
方法用于解除對檔案的監聽。
createReadStream()
createReadStream
方法往往用于打開大型的文本檔案,建立一個讀取操作的資料流。所謂大型文本檔案,指的是文本檔案的體積很大,讀取操作的緩存裝不下,隻能分成幾次發送,每次發送會觸發一個
data
事件,發送結束會觸發
end
事件。
var fs = require('fs');
function readLines(input, func) {
var remaining = '';
input.on('data', function(data) {
remaining += data;
var index = remaining.indexOf('\n');
var last = 0;
while (index > -1) {
var line = remaining.substring(last, index);
last = index + 1;
func(line);
index = remaining.indexOf('\n', last);
}
remaining = remaining.substring(last);
});
input.on('end', function() {
if (remaining.length > 0) {
func(remaining);
}
});
}
function func(data) {
console.log('Line: ' + data);
}
var input = fs.createReadStream('lines.txt');
readLines(input, func);
createWriteStream()
createWriteStream
方法建立一個寫入資料流對象,該對象的
write
方法用于寫入資料,
end
方法用于結束寫入操作。
var out = fs.createWriteStream(fileName, {
encoding: 'utf8'
});
out.write(str);
out.end();
createWriteStream
方法和
createReadStream
方法配合,可以實作拷貝大型檔案。
function fileCopy(filename1, filename2, done) {
var input = fs.createReadStream(filename1);
var output = fs.createWriteStream(filename2);
input.on('data', function(d) { output.write(d); });
input.on('error', function(err) { throw err; });
input.on('end', function() {
output.end();
if (done) done();
});
}
<2>NodeJs 裡面的fs子產品
Node.js 提供一組類似 UNIX(POSIX)标準的檔案操作API。 Node 導入檔案系統子產品(fs)文法如下所示:
var fs = require("fs")
對檔案的操作
檔案讀取
Node.js 檔案系統(fs 子產品)子產品中的方法均有異步和同步版本,例如讀取檔案内容的函數有異步的 fs.readFile() 和同步的 fs.readFileSync()。
異步的方法函數最後一個參數為回調函數,回調函數的第一個參數包含了錯誤資訊(error)。
建議大家使用異步方法,比起同步,異步方法性能更高,速度更快,而且沒有阻塞。
同步
//同步 所有同步的函數都是函數後面加Sync;
var res = fs.writeFileSync("1.txt","我是寫入内容");
異步
//檔案的讀取
fs.readFile("1.txt",function (err,data) {
if (err){
console.log(err)
}else {
console.log(data.toString())
}
})
檔案寫入
//建立: writeFile(檔案名,寫入内容,配置參數,回調函數) 異步
//配置參數:
/*
* a :追加
* w :寫入
* r :讀取
* */
fs.writeFile("2.txt","我是2.txt",{flag:"a"},function (err) {
if(err){
console.log(err);
}else {
console.log("寫入成功");
}
})
// 追加寫入
fs.appendFile("2.txt","我是追加的字元",function (err) {
if(err){
return console.log(err);
}else {
console.log("追加成功");
}
})
檔案名修改
//檔案修改(檔案名的修改)
fs.rename("5.txt","1.txt",function (err) {
if(err){
console.log(err)
}else {
console.log("修改成功");
}
})
//檔案删除
fs.unlink("2.txt",function (err) {
if(err){
return console.log(err)
}else {
console.log("删除成功")
}
})
由于fs子產品中沒有對檔案的複制,我們可以自己通過以上操作封裝一個
function mycopy(file1,file2) {
//讀取檔案
var res = fs.readFile(file1,function (err,data) {
if (err){
console.log(err)
}else {
var res = data.toString()
fs.writeFile(file2,res,function (err) {
if(err){
console.log(err)
}else {
console.log("寫入成功");
}
})
}
})
}
//
function mycopy(src,dest) {
fs.writeFileSync(dest,fs.readFileSync(src));
}
對檔案夾的操作
檔案夾的操作/目錄操作
// 目錄建立:
// 1、1:執行-x 2、2:寫-w 3、4:讀-r
fs.mkdir("10",0666,function (err) {
if(err){
console.log(err)
}else {
console.log("建立成功");
}
})
修改檔案夾權限
fs.chmod("11",0777,function (err) {
if(err){
console.log(err)
}else {
console.log("修改權限成功")
}
})
判斷檔案或者檔案夾是否存在
fs.exists("10",function (exists) {
if(exists){
console.log("檔案夾已存在");
}else {
fs.mkdir("10",0777,function (err) {
if(err){
return console.log(err);
}else{
console.log("建立成功");
}
})
}
})
删除檔案夾 : 隻能删除空檔案夾
fs.rmdir("10",function (err) {
if(err){
return console.log(err)
}else {
console.log("删除成功");
}
})
讀取檔案夾
fs.readdir("10",function (err,data) {
if(err){
console.log(err);
}else {
console.log(data);
}
})
顯示檔案的詳細資訊:
//針對詳細資訊來判斷是否是檔案
fs.stat("10",function (err,data) {
if(err){
return console.log(err)
}else {
// console.log(data);
//判斷是否是檔案
var res = data.isFile();
//判斷是否是檔案夾
// data.isDirectory();
if(res){
console.log("是檔案")
}else {
console.log("是檔案夾")
}
}
})
由于node.js中沒有删除包含檔案的檔案夾的函數,是以我們仿寫一個函數來删除包含檔案的檔案的函數
// 删除檔案夾的函數 同步
var removeDir = function(src) {
// 擷取到檔案夾裡的内容
var arr = fs.readdirSync(src);
//判斷是否是檔案,如果是檔案就删除;如果是檔案夾再執行相同過程
for(var i=0;i<arr.length;i++){
//子檔案的詳細資訊
// 組裝檔案或者檔案夾的路徑
var url = src+"/"+arr[i];
var data = fs.statSync(url);
//判斷每個元素是檔案或者是檔案夾
if(data.isFile()){
//是檔案
fs.unlinkSync(url);
}else {
//是檔案夾
removeDir(url)
}
}
//删除空檔案夾
fs.rmdirSync(src);
}
<3>Node.js檔案系統的API fs子產品
Node.js的檔案系統的Api
//公共引用
var fs = require('fs'),
path = require('path');
1、讀取檔案readFile函數
//readFile(filename,[options],callback);
/**
* filename, 必選參數,檔案名
* [options],可選參數,可指定flag(檔案操作選項,如r+ 讀寫;w+ 讀寫,檔案不存在則建立)及encoding屬性
* callback 讀取檔案後的回調函數,參數預設第一個err,第二個data 資料
*/
fs.readFile(__dirname + '/test.txt', {flag: 'r+', encoding: 'utf8'}, function (err, data) {
if(err) {
console.error(err);
return;
}
console.log(data);
});
2、寫檔案
// fs.writeFile(filename,data,[options],callback);
var w_data = '這是一段通過fs.writeFile函數寫入的内容;\r\n';
var w_data = new Buffer(w_data);
/**
* filename, 必選參數,檔案名
* data, 寫入的資料,可以字元或一個Buffer對象
* [options],flag,mode(權限),encoding
* callback 讀取檔案後的回調函數,參數預設第一個err,第二個data 資料
*/
fs.writeFile(__dirname + '/test.txt', w_data, {flag: 'a'}, function (err) {
if(err) {
console.error(err);
} else {
console.log('寫入成功');
}
});
3、以追加方式寫檔案
// fs.appendFile(filename,data,[options],callback);
fs.appendFile(__dirname + '/test.txt', '使用fs.appendFile追加檔案内容', function () {
console.log('追加内容完成');
});
4、打開檔案
// 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);
});
5、讀檔案,讀取打開的檔案内容到緩沖區中;
//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());
});
}
});
}
});
6、寫檔案,将緩沖區内資料寫入使用fs.open打開的檔案
//fs.write(fd, buffer, offset, length, position, callback);
/**
* 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());
})
}
});
}
});
7、重新整理緩存區;
// 使用fs.write寫入檔案時,作業系統是将資料讀到記憶體,再把資料寫入到檔案中,當資料讀完時并不代表資料已經寫完,因為有一部分還可能在内在緩沖區内。
// 是以可以使用fs.fsync方法将記憶體中資料寫入檔案;--重新整理記憶體緩沖區;
//fs.fsync(fd, [callback])
/**
* fd, 使用fs.open打開成功後傳回的檔案描述符
* [callback(err, written, buffer)], 寫入操作執行完成後回調函數,written實際寫入位元組數,buffer被讀取的緩存區對象
*/
fs.open(__dirname + '/test.txt', 'a', function (err, fd) {
if(err)
throw err;
var buffer = new Buffer('我愛nodejs程式設計');
fs.write(fd, buffer, 0, 9, 0, function (err, written, buffer) {
console.log(written.toString());
fs.write(fd, buffer, 9, buffer.length - 9, null, function (err, written) {
console.log(written.toString());
fs.fsync(fd);
fs.close(fd);
})
});
});
8、建立目錄;
//使用fs.mkdir建立目錄
//fs.mkdir(path, [mode], callback);
/**
* path, 被建立目錄的完整路徑及目錄名;
* [mode], 目錄權限,預設0777
* [callback(err)], 建立完目錄回調函數,err錯誤對象
*/
fs.mkdir(__dirname + '/fsDir', function (err) {
if(err)
throw err;
console.log('建立目錄成功')
});
9、讀取目錄;
//使用fs.readdir讀取目錄,重點其回調函數中files對象
//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');
}
});
});
for (var i = 0; i < files.length; i++) {
//使用閉包無法保證讀取檔案的順序與數組中儲存的緻
(function () {
var filePath = path.normalize(__dirname + '/fsDir/' + files[i]);
fs.stat(filePath, function (err, stat) {
if(stat.isFile()) {
console.log(filePath + ' is: ' + 'file');
}
if(stat.isDirectory()) {
console.log(filePath + ' is: ' + 'dir');
}
});
})();
}
}
});
10、檢視檔案與目錄的資訊;
//fs.stat(path, callback);
//fs.lstat(path, callback); //檢視符号連結檔案
/**
* path, 要檢視目錄/檔案的完整路徑及名;
* [callback(err, stats)], 操作完成回調函數;err錯誤對象,stat fs.Stat一個對象執行個體,提供如:isFile, isDirectory,isBlockDevice等方法及size,ctime,mtime等屬性
*/
//執行個體,檢視fs.readdir
11、檢視檔案與目錄的是否存在
//fs.exists(path, callback);
/**
* path, 要檢視目錄/檔案的完整路徑及名;
* [callback(exists)], 操作完成回調函數;exists true存在,false表示不存在
*/
fs.exists(__dirname + '/te', function (exists) {
var retTxt = exists ? retTxt = '檔案存在' : '檔案不存在';
console.log(retTxt);
});
12、修改檔案通路時間與修改時間
//fs.utimes(path, atime, mtime, callback);
/**
* path, 要檢視目錄/檔案的完整路徑及名;
* atime, 新的通路時間
* ctime, 新的修改時間
* [callback(err)], 操作完成回調函數;err操作失敗對象
*/
fs.utimes(__dirname + '/test.txt', new Date(), new Date(), function (err) {
if(err) {
console.error(err);
return;
}
fs.stat(__dirname + '/test.txt', function (err, stat) {
console.log('通路時間: ' + stat.atime.toString() + '; \n修改時間:' + stat.mtime);
console.log(stat.mode);
})
});
13、修改檔案或目錄的操作權限
//fs.utimes(path, mode, callback);
/**
* path, 要檢視目錄/檔案的完整路徑及名;
* mode, 指定權限,如:0666 8進制,權限:所有使用者可讀、寫,
* [callback(err)], 操作完成回調函數;err操作失敗對象
*/
fs.chmod(__dirname + '/fsDir', 0666, function (err) {
if(err) {
console.error(err);
return;
}
console.log('修改權限成功')
});
14、移動/重命名檔案或目錄
//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('重命名成功')
});
15、删除空目錄
//fs.rmdir(path, callback);
/**
* path, 目錄的完整路徑及目錄名;
* [callback(err)], 操作完成回調函數;err操作失敗對象
*/
fs.rmdir(__dirname + '/test', function (err) {
fs.mkdir(__dirname + '/test', 0666, function (err) {
console.log('建立test目錄');
});
if(err) {
console.log('删除空目錄失敗,可能原因:1、目錄不存在,2、目錄不為空')
console.error(err);
return;
}
console.log('删除空目錄成功!');
});
16、監視檔案
//對檔案進行監視,并且在監視到檔案被修改時執行處理
//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,監視到檔案有修改');
});
17、取消監視檔案
//取消對檔案進行監視
//fs.unwatchFile(filename, [listener]);
/**
* filename, 完整路徑及檔案名;
* [listener], 要取消的監聽器事件,如果不指定,則取消所有監聽處理事件
*/
var listener = function (curr, prev) {
console.log('我是監視函數')
}
fs.unwatchFile(__dirname + '/test.txt', listener);
18、監視檔案或目錄
// 對檔案或目錄進行監視,并且在監視到修改時執行處理;
// fs.watch傳回一個fs.FSWatcher對象,擁有一個close方法,用于停止watch操作;
// 當fs.watch有檔案變化時,會觸發fs.FSWatcher對象的change(err, filename)事件,err錯誤對象,filename發生變化的檔案名
// fs.watch(filename, [options], [listener]);
/**
* 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);
19、檔案流
/*
* 流,在應用程式中表示一組有序的、有起點有終點的位元組資料的傳輸手段;
* 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 結束寫入,之後再寫入會報錯;
*/
20、建立讀取流
//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);
21、建立寫入流
//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('系統緩存區資料已經全部輸出。')
});
22、管道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('再見')
});
<4>node.js高階子產品-fs子產品
NodeJs隻能做的兩件事是什麼?
實際上,所有背景語言都能做的兩件事是: 檔案操作和網絡程式設計.
這其實是所有語言的根本。 計算機無外乎就是檔案和通信。Linux中,是把一切都當做檔案,如果了解了這一點那就無可厚非了.
是以,這裡,我想介紹一下NodeJS中一個重要的子產品--fs.
這裡我給大家放一個我的架構圖~

(為什麼不是http? 懶~)
let's start.
針對于fs,我們切實圍繞幾個問題來吧~
- fs是如何操作檔案的?
- drain和write到底是什麼關系?
- fs怎麼寫出向gulp那樣實時監聽檔案改變的插件?
關于fs的API,直接參考Nodejs官網. 同樣,放上fs的基本架構圖:
(圖有點大,大家另外開一個視窗看吧)
我們圍繞這些問題來展開,說明吧.
fs操作檔案的幾種方式
這裡,我們針對檔案最基本的兩種操作進行相關的解釋和說明吧--read&&write
讀寫檔案有哪幾種操作方式呢?
我們先從最簡便的開始吧~
先熟悉API: fs.createReadStream(path[, options]) path就是打開的檔案路徑,options有點複雜:
{
flags: 'r',
encoding: null,
fd: null,
mode: 0o666,
autoClose: true
}
實際上我們一般也用不到options,除非你是擷取已經打開後的檔案.具體描述詳見.官網.
ok~ 現在正式打開一個檔案:
const fs = require('fs');
const read = fs.createReadStream('sam.js',{encoding:'utf8'});
read.on('data',(str)=>{
console.log(str);
})
read.on('end',()=>{
console.log('have already opened');
})
實際上,我們就是利用fs繼承的readStream來進行操作的.
使用open打開檔案
同樣上:API:
fs.open(path, flags[, mode], callback)這個和上面的readStream不同,open打開檔案是一個持續狀态,相當于會将檔案寫入到記憶體當中. 而readStream隻是讀取檔案,當讀取完畢時則會自動關閉檔案--相當于fs.open+fs.close兩者的結合~ 其中flags和mode 就是設定打開檔案的權限,以及檔案的權限模式(rwx).
使用open來打開一個檔案
const fs = require('fs');
fs.open('sam.js','r',(err,fd)=>{
fs.fstat(fd,(err,stat)=>{
var len = stat.size; //檢測檔案長度
var buf = new Buffer(len);
fs.read(fd,buf,0,len,0,(err,bw,buf)=>{
console.log(buf.toString('utf8'));
fs.close(fd);
})
});
});
使用相關的read/readdir/readFile/readlink
read方法,使用來讀取已經打開後的檔案。 他不用用來進行打開檔案操作,這點很重要》 那還有其他方法,在讀的過程可以直接打開檔案嗎?
absolutely~
這裡就拿readFile和readdir舉例吧
API
fs.readFile(file[, options], callback): file就是檔案路徑,options可以為object也可以為string. 不過最常用的還是str. 我們直接看demo:
const fs = require('fs');
fs.readFile('sam.js','utf8',(err,data)=>{
console.log(`the content is ,${data}`);
})
另外一個readdir,顧名思義該API就是用來讀取檔案夾的.實際上,該API也沒有什麼卵用~
fs.readdir(path, callback):用來擷取檔案下所有的檔案(包括目錄),并且不會進行recursive.并且callback(err,files)中的files隻是以數組的形式放回該目錄下所有檔案的名字
show u code:
//用來檢查,上層目錄中那些是file,那些是dir
const fs = require('fs');
fs.readdir('..', (err,files)=>{
var path,stat;
files.map((val)=>{
path = `../${val}`;
stat= fs.statSync(path);
if(stat.isFile()){
console.log(`file includes ${val}`);
}else if(stat.isDirectory()){
console.log(`dir includes ${val}`);
}
})
})
nodejs 打開檔案的所有方式就是以上這幾種.接下來我們再來看一下,如果寫入檔案吧~
寫入檔案
同樣,先介紹最簡單的吧.
fs.createWriteStream(path[, options]): path就是檔案路徑.而options和上面的createWriteStream一樣比較複雜;
{
flags: 'w',
defaultEncoding: 'utf8',
fd: null,
mode: 0o666
}
實際上,我們隻需要寫好path就enough了.
直接看demo吧:
//用來寫入str的操作
const fs = require('fs');
const write = fs.createWriteStream('sam.js');
write.on('drain',()=>{
write.resume();
});
var writeData = function(){
var i = 1000;
while(i--){
if(!write.write('sam')){
write.pause();
}
}
}
writeData();
實際上,上面那段代碼是最常用的寫入檔案的寫法.drain是代表,寫入記憶體已經清空後,可以繼續寫入時觸發的事件.這就是第二個問題: drain和write到底是什麼關系? 這個問題,我們放到後面講解,這裡先繼續說一下如何寫入内容.
使用fs.write方法直接寫入内容:
fs.writeAPI其實就有兩個:
fs.write(fd, buffer, offset, length[, position], callback):這一種,是用來直接寫入Buffer資料内容的.
fs.write(fd, data[, position[, encoding]], callback):這一種,是用來寫入str資料内容的.
不過,fs.write()該方法,也是建立在已有檔案打開的基礎上的.
直接看一下demo:
//使用Buffer寫入
const fs = require('fs');
fs.open('sam.js','w+',(err,fd)=>{
var buf = new Buffer("sam",'utf8');
fs.write(fd,buf,0,buf.length,0,(err,bw,buf)=>{
fs.close(fd);
});
})
//直接使用string寫入:
const fs = require('fs');
fs.open('sam.js','w+',(err,fd)=>{
fs.write(fd,'sam','utf8',0,(err,bw,buf)=>{
fs.close(fd);
});
})
通常情況下,我們也不會用來寫入Buffer的. 是以,第二種方法就足夠了.
同理,能否直接寫入未打開的檔案呢?
當然是可以的,是以這裡介紹最後一種方法. 使用writeFile和appendFile來寫入資料.
fs.writeFile(file, data[, options], callback):直接寫入指定檔案. 寫入的内容會直接覆寫掉原始内容.
fs.appendFile(file, data[, options], callback):真正的用來append file
//檢測檔案是否存在,如果存在則增加内容,否則建立檔案并寫入内容.
const fs = require('fs');
var writeData = function() {
fs.access('sam.js', (noAccess) => {
if (noAccess) {
fs.writeFile('sam.js', 'sam', (err) => {
if (!err) console.log('writeFile success')
})
} else {
fs.appendFile('sam.js', 'sam', (err) => {
if (!err) console.log('appendFile success~');
});
}
})
}
writeData()
大緻梳理一下上面說的内容吧:
drain和stream.write的關聯
首先這兩個東西,是底層writeStream提供的. write這個方法不用解釋了吧~ 關鍵drain到底怎麼使用~ 這也是官網沒說清楚的地方:
If a stream.write(chunk) call returns false, then the 'drain' event will indicate when it is appropriate to begin writing more data to the stream.
實際上,我們判斷用沒用到drain事件的機制,是根據write方法的傳回值來進行判斷的. 官方也給出一個demo,用來測試drain事件的觸發.
const fs = require('fs');
const writer = fs.createWriteStream('sam.js');
writeOneMillionTimes(writer,'sam','utf8',()=>{});
沒錯,這樣确實會多次觸發drain事件.但是,他到底是什麼時候會觸發呢?
根據源碼的介紹,write方法在使用時,會内置一個Buffer用來寫入資料.我們可以了解該Buffer就是該次寫入的最大記憶體值~ 那到底是多少呢? 源碼:
var defaultHwm = this.objectMode ? 16 : 16 * 1024;
//即,預設為16KB
相當于,我們每次寫入都會有16KB的空間開着~ 如果寫入data已經填滿了16KB, 而我們還繼續寫入就有可能造成 memory leak~ 這就go die了。輕者卡一卡,重則當機都有可能. 那如果按照官網那種寫法的話,每次寫入一個大檔案,都要寫入老長老長的函數呢?
倫家~才不要~
實際上,我們直接提煉一下,使用stream.once('drain')事件來進行處理.
if(!stream.write(data))stream.once('drain',()=>{
stream.write(otherData);
})
或者當你使用readStream和writeStream用來讀寫檔案時~可以使用
//試一試讀取一個較大的檔案,你會發現drain事件也會觸發~ 是以我們需要使用pause和resume來暫停流的讀取,防止memory leak~
const fs = require('fs');
const read = fs.createReadStream('唱歌的孩子.mp3');
const write = fs.createWriteStream('get.mp3');
read.on('data',(chunk)=>{
if(!write.write(chunk))read.pause();
});
write.on('drain',()=>{
console.log('drain');
read.resume();
})
read.on('end',()=>{
console.log(`finish`);
})
如何寫出像gulp一樣監聽檔案變化的插件呢?
首先,我們看一下監聽插件的配置:
gulp.task('sync', function() {
var files = [
'app/**/*.html',
'app/styles/**/*.css',
'app/img/**/*.png',
'app/src/**/*.js'
];
browserSync.init(files, {
server: {
baseDir: './app'
}
});
});
首先,我們設定了files之後,就可以監聽檔案,并且開啟一個服務~
而實際上,就是使用Nodejs底層的fs.watch對檔案進行監聽.我們來使用fs.watch和fs.watchFile來實作檔案的監聽~
這裡,我們先從簡單的watchFile入手~
根據nitoyon的解釋,我們可以得出兩個結論
- fs.watch() uses native API
- fs.watchFile() periodically executes fs.stat()
是以,底層上來看,其實fs.watchFile是周期性執行fs.stat的,速度上來看,肯定會慢的. 不多說了,我們看一下demo:
const fs = require('fs');
fs.watchFile('sam.js', {
persistent:true,
interval:3000
}, (cur,prev)=>{
if(cur.mtime>prev.mtime){
console.log('change');
console.log(cur,prev);
}
})
這裡,主要想談及一下watchFile中的第二個參數,options中的interval. 這個東西有點傻逼~ 為什麼呢? 因為,他并不是在一定時間内,觸發watch,而是在第一次觸發後的interval時間内,不會觸發watch. 即,他會發生改變的積累~ 在interval時間内改變的内容,隻會在最後一次中呈現出來~ 而他的底層其實就是調用fs.stat來完成的.這裡,我們使用fs.stat來模仿一遍~
const fs = require('fs'),
Event = require('events').EventEmitter,
event = new Event();
//原始方法getCur
//原始屬性prev
var watchFile = function(file,interval,cb){
var pre,cur;
var getPrv = function(file){
var stat = fs.statSync(file);
return stat;
}
var getCur = function(file){
cur = getPrv(file);
console.log(cur,pre);
if(cur.mtime.toString()!==pre.mtime.toString()){
cb('change');
}
pre = cur; //改變初始狀态
}
var init = (function(){
pre = getPrv(file); //首先擷取pre
event.on('change',function(){
getCur(file);
});
setInterval(()=>{
event.emit('change');
},interval);
})()
}
watchFile('sam.js',2000,function(eventname){
console.log(eventname);
})
上述,完善了一下,在指定時間内,對檔案改動進行監聽,和fs.watchFile不同.
ok~ 這個out-of-date的監聽方式,我們大緻了解了. 接下來我們來看一下,如何使用v0.5.x版本退出的新API:fs.watch. 我們參考官網:
fs.watch should be used instead of fs.watchFile and fs.unwatchFile when possible.
為什麼呢?
不為什麼. 因為,fs.watch調用的是native API。而fs.watchFile是調用的是fs.stat. 比起來,時間肯定會慢一點.
那怎麼使用fs.watch監聽檔案呢?
先看一下API吧:
fs.watch(filename, options):其實和fs.watchFile沒差多少. 不多options裡面有一個參數不同:
{ persistent: true, recursive: false }
即,該API不僅可以監聽檔案,還可以監聽目錄.其中recursive表示遞歸,用來監聽目錄下的檔案。 不過NodeJS如是說:
The recursive option is only supported on OS X and Windows.
懂了吧. 不過基本上, 該API的覆寫率也足夠了.别告訴我,你用linxu寫代碼.
const fs = require('fs');
fs.watch('..',{recursive:true},function(event,filename){
console.log(`event is ${event} and filename is ${filename}`);
})
在MAC OX 11完美通過. 每當儲存一次,就會觸發一次。不過當你修改檔案名時,便會觸發兩次. 一次是,原檔案被修改,另一次是新檔案被建立.即.
event is rename and filename is app/sam.html
event is rename and filename is app/index.html
<5>node.js裡面的fs用法詳解
Node.js之fs用法詳解
2018-01-22
Node.js 内置的fs子產品就是檔案系統子產品,負責讀寫檔案。和所有其他JS子產品不同的是,fs子產品同時提供了異步和同步的方法。
- 檔案寫入
| |
運作上述代碼的時候,會發現該父級檔案夾下會自動生成一個11.txt檔案。
| |
因為是追加的内容,是以内容會自動在該檔案後面
上面說的方法都是異步操作,異步操作會傳回一個回調函數,在回調函數裡面執行結束語句,不然會出現錯誤
而所有的同步函數,都隻是在異步函數後面加上Sync
| |
- 檔案讀取
異步方法讀取檔案
| |
如果使用同步的方法,不需要在後面使用回調方法
| |
- 檔案修改
| |
- 檔案删除
| |
- 檔案複制(先讀取,在複制)
異步方法
| |
同步方法,相比異步少了很對回調
| |
- 檔案夾建立
| |
上述的權限就是在檔案簡介裡面權限
- 修改檔案夾權限
| |
- 修改檔案夾名字,與修改檔案是同一個函數
| |
- 判斷某個檔案件是否存在,如果不存在建立,exists函數,是唯一一個回調函數中不帶err的回調函數
| |
- 删除檔案夾(隻能删除空的檔案夾)
| |
- 讀取檔案夾裡面的資訊
| |
- 判斷一個位置問價是否是檔案或者是檔案件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
fs.stat("image",function (err,data) { if(err){ return console.log(err); }else { //判斷是否是檔案 if(data.isFile()){ //是檔案 console.log("yes"); }else{ //是檔案夾 console.log("no"); } } })
-
删除非空檔案夾
首先擷取到該檔案夾裡面所有的資訊,周遊裡面的資訊,判斷是檔案還是檔案夾,如果是檔案直接删除,如果是檔案,進入檔案,重複上述過程
| |