1. fs模組
- 在Node.js中,使用fs模組來實現所有有關檔案及目錄的建立、寫入及刪除操作。
- 在fs模組中,所有的方法都分為同步和非同步兩種實現。
- 具有
sync
字尾的方法為同步方法,不具有sync
字尾的方法為非同步方法。
2. 整體讀取檔案
2.1 非同步讀取
fs.readFile(path[, options], callback)
複製程式碼
- options
- encoding
- flag flag 預設 = 'r'
2.2 同步讀取
fs.readFileSync(path[, options])
複製程式碼
3. 寫入檔案
3.1 非同步寫入
fs.writeFile(file, data[, options], callback)
複製程式碼
- options
- encoding
- flag flag 預設 = 'w'
- mode 讀寫許可權,預設為0666
let fs = require('fs');
fs.writeFile('./1.txt',Date.now()+'\n',{flag:'a'},function(){
console.log('ok');
});
複製程式碼
3.2 同步寫入
fs.writeFileSync(file, data[, options])
複製程式碼
3.3 追加檔案
fs.appendFile(file, data[, options], callback)
fs.appendFile('./1.txt',Date.now()+'\n',function(){
console.log('ok');
})
複製程式碼
3.4 拷貝檔案
function copy(src,target){
fs.readFile(src,function(err,data){
fs.writeFile(target,data);
})
}
複製程式碼
4. 從指定位置處開始讀取檔案
4.1 開啟檔案
fs.open(filename,flags,[mode],callback);
- filename, 必選引數,檔名
- * flags, 操作標識,如"r",讀方式開啟
- [mode],許可權,如777,表示任何使用者讀寫可執行
- callback 開啟檔案後回撥函式,引數預設第一個err,第二個fd為一個整數,表示開啟檔案返回的檔案描述符,window中又稱檔案控制程式碼
fs.open('./1,txt','r',0600,function(err,fd){});
複製程式碼
4.2 讀取檔案
fs.read(fd, buffer, offset, length, position, callback((err, bytesRead, buffer)))
const fs=require('fs');
const path=require('path');
fs.open(path.join(__dirname,'1.txt'),'r',0o666,function (err,fd) {
console.log(err);
let buf = Buffer.alloc(6);
fs.read(fd,buf,0,6,3,function(err, bytesRead, buffer){
console.log(bytesRead);//6
console.log(buffer===buf);//true
console.log(buf.toString());
})
})
複製程式碼
4.3 寫入檔案
fs.write(fd, buffer[, offset[, length[, position]]], callback)
const fs=require('fs');
const path=require('path');
fs.open(path.join(__dirname,'1.txt'),'w',0o666,function (err,fd) {
console.log(err);
let buf=Buffer.from('你好成都');
fs.write(fd,buf,3,6,0,function(err, bytesWritten, buffer){
console.log(bytesWritten);//6
console.log(buffer===buf);//true
console.log(buf.toString());//你好成都
})
})
複製程式碼
4.4 同步磁碟快取
fs.fsync(fd,[callback]);
4.5 關閉檔案
fs.close(fd,[callback]);
let buf = Buffer.from('你好成都');
fs.open('./2.txt', 'w', function (err, fd) {
fs.write(fd, buf, 3, 6, 0, function (err, written, buffer) {
console.log(written);
fs.fsync(fd, function (err) {
fs.close(fd, function (err) {
console.log('寫入完畢!')
}
);
});
})
});
複製程式碼
4.6 拷貝檔案
let BUFFER_SIZE=1;
const path=require('path');
const fs=require('fs');
function copy(src,dest,callback) {
let buf=Buffer.alloc(BUFFER_SIZE);
fs.open(src,'r',(err,readFd)=>{
fs.open(dest,'w',(err,writeFd) => {
!function read() {
fs.read(readFd,buf,0,BUFFER_SIZE,null,(err,bytesRead) => {
bytesRead&&fs.write(writeFd,buf,0,bytesRead,read);
});
}()
})
});
}
copy(path.join(__dirname,'1.txt'),path.join(__dirname,'2.txt'),()=>console.log('ok'));
複製程式碼
5 目錄操作
5.1 建立目錄
fs.mkdir(path[, mode], callback)
要求父目錄必須存在
5.2 判斷一個檔案是否有許可權訪問
fs.access(path[, mode], callback)
fs.access('/etc/passwd', fs.constants.R_OK | fs.constants.W_OK, (err) => { console.log(err ? 'no access!' : 'can read/write'); }); 複製程式碼
5.3 讀取目錄下所有的檔案
fs.readdir(path[, options], callback)
5.4 檢視檔案目錄資訊
fs.stat(path, callback)
- stats.isFile()
- stats.isDirectory()
- atime(Access Time)上次被讀取的時間。
- ctime(State Change Time):屬性或內容上次被修改的時間。
- mtime(Modified time):檔案的內容上次被修改的時間。
5.5 移動檔案或目錄
fs.rename(oldPath, newPath, callback)
複製程式碼
5.6 刪除檔案
fs.unlink(path, callback)
複製程式碼
5.7 截斷檔案
fs.ftruncate(fd[, len], callback)
複製程式碼
const fd = fs.openSync('temp.txt', 'r+');
// 截斷檔案至前4個位元組
fs.ftruncate(fd, 4, (err) => {
console.log(fs.readFileSync('temp.txt', 'utf8'));
});
複製程式碼
5.8 監視檔案或目錄
fs.watchFile(filename[, options], listener)
複製程式碼
let fs = require('fs');
fs.watchFile('1.txt', (curr, prev) => {
//parse() 方法可解析一個日期時間字串,並返回 1970/1/1 午夜距離該日期時間的毫秒數。
if(Date.parse(prev.ctime)==0){
console.log('建立');
}else if(Date.parse(curr.ctime)==0){
console.log('刪除');
}else if(Date.parse(prev.ctime) != Date.parse(curr.ctime)){
console.log('修改');
}
});
複製程式碼
6 遞迴建立目錄
6.1 同步建立目錄
let fs=require('fs');
let path=require('path');
function makepSync(dir) {
let parts=dir.split(path.sep);
for (let i=1;i<=parts.length;i++){
let parent=parts.slice(0,i).join(path.sep);
try {
fs.accessSync(parent);
} catch (error) {
fs.mkdirSync(parent);
}
}
}
複製程式碼
6.2 非同步建立目錄
function makepAsync(dir,callback) {
let parts=dir.split(path.sep);
let i=1;
function next() {
if (i>parts.length)
return callback&&callback();
let parent=parts.slice(0,i++).join(path.sep);
fs.access(parent,err => {
if (err) {
fs.mkdir(parent,next);
} else {
next();
}
});
}
next();
}
複製程式碼
6.3 Async+Await建立目錄
async function mkdir(parent) {
return new Promise((resolve,reject) => {
fs.mkdir(parent,err => {
if (err) reject(err);
else resolve();
});
});
}
async function access(parent) {
return new Promise((resolve,reject) => {
fs.access(parent,err => {
if (err) reject(err);
else resolve();
});
});
}
async function makepPromise(dir,callback) {
let parts=dir.split(path.sep);
for (let i=1;i<=parts.length;i++){
let parent=parts.slice(0,i).join(path.sep);
try {
await access(parent);
}catch(err) {
await mkdir(parent);
}
}
}
複製程式碼
7. 遞迴刪除目錄
7.1 同步刪除目錄(深度優先)
let fs=require('fs');
let path=require('path')
function rmSync(dir) {
try {
let stat = fs.statSync(dir);
if (stat.isFile()) {
fs.unlinkSync(dir);
} else {
let files=fs.readdirSync(dir);
files
.map(file => path.join(dir,file))
.forEach(item=>rmSync(item));
fs.rmdirSync(dir);
}
} catch (e) {
console.log('刪除失敗!');
}
}
rmSync(path.join(__dirname,'a'));
複製程式碼
7.2 非同步刪除非空目錄(Promise版)
function rmPromise(dir) {
return new Promise((resolve,reject) => {
fs.stat(dir,(err,stat) => {
if (err) return reject(err);
if (stat.isDirectory()) {
fs.readdir(dir,(err,files) => {
let paths = files.map(file => path.join(dir,file));
let promises = paths.map(p=>rmPromise(p));
Promise.all(promises).then((() => fs.rmdir(dir,resolve)));
});
} else {
fs.unlink(dir,resolve);
}
});
});
}
rmPromise(path.join(__dirname,'a')).then(() => {
console.log('刪除成功');
})
複製程式碼
7.3 非同步序列刪除目錄(深度優先)
function rmAsyncSeries(dir,callback) {
setTimeout(() => {
fs.stat(dir,(err,stat) => {
if (err) return callback(err);
if (stat.isDirectory()) {
fs.readdir(dir,(err,files) => {
let paths = files.map(file => path.join(dir,file));
function next(index) {
if (index>=files.length) return fs.rmdir(dir,callback);
let current=paths[index];
rmAsyncSeries(current,()=>next(index+1));
}
next(0);
});
} else {
fs.unlink(dir,callback);
}
})
},1000);
}
console.time('cost');
rmAsyncSeries(path.join(__dirname,'a'),err => {
console.timeEnd('cost');
})
複製程式碼
7.4 非同步並行刪除目錄(深度優先)
function rmAsyncParallel(dir,callback) {
setTimeout(() => {
fs.stat(dir,(err,stat) => {
if (err) return callback(err);
if (stat.isDirectory()) {
fs.readdir(dir,(err,files) => {
let paths=files.map(file => path.join(dir,file));
if (paths.length>0) {
let i=0;
function done() {
if (++i == paths.length) {
fs.rmdir(dir,callback);
}
}
paths.forEach(p=>rmAsyncParallel(p,done));
} else {
fs.rmdir(dir,callback);
}
});
} else {
fs.unlink(dir,callback);
}
})
},1000);
}
console.time('cost');
rmAsyncParallel(path.join(__dirname,'a'),err => {
console.timeEnd('cost');
})
複製程式碼
7.5 同步刪除目錄(廣度優先)
function rmSync(dir){
let arr=[dir];
let index=0;
while (arr[index]) {
let current=arr[index++];
let stat=fs.statSync(current);
if (stat.isDirectory()) {
let dirs=fs.readdirSync(current);
arr=[...arr,...dirs.map(d => path.join(current,d))];
}
}
let item;
while (null != (item = arr.pop())) {
let stat = fs.statSync(item);
if (stat.isDirectory()) {
fs.rmdirSync(item);
} else {
fs.unlinkSync(item);
}
}
}
複製程式碼
7.6 非同步刪除目錄(廣度優先)
function rmdirWideAsync(dir,callback){
let dirs=[dir];
let index=0;
function rmdir() {
let current = dirs.pop();
if (current) {
fs.stat(current,(err,stat) => {
if (stat.isDirectory()) {
fs.rmdir(current,rmdir);
} else {
fs.unlink(current,rmdir);
}
});
}
}
!function next() {
let current=dirs[index++];
if (current) {
fs.stat(current,(err,stat) => {
if (err) callback(err);
if (stat.isDirectory()) {
fs.readdir(current,(err,files) => {
dirs=[...dirs,...files.map(item => path.join(current,item))];
next();
});
} else {
next();
}
});
} else {
rmdir();
}
}();
}
複製程式碼
8. 遍歷演算法
- 目錄是一個樹狀結構,在遍歷時一般使用深度優先+先序遍歷演算法
- 深度優先,意味著到達一個節點後,首先接著遍歷子節點而不是鄰居節點
- 先序遍歷,意味著首次到達了某節點就算遍歷完成,而不是最後一次返回某節點才算數
- 因此使用這種遍歷方式時,下邊這棵樹的遍歷順序是A > B > D > E > C > F。
A / \ B C / \ \ D E F 複製程式碼
8.1 同步深度優先+先序遍歷
function deepSync(dir){ console.log(dir); fs.readdirSync(dir).forEach(file=>{ let child = path.join(dir,file); let stat = fs.statSync(child); if(stat.isDirectory()){ deepSync(child); }else{ console.log(child); } }); } 複製程式碼
8.2 非同步深度優先+先序遍歷
function deep(dir,callback) { console.log(dir); fs.readdir(dir,(err,files)=>{ !function next(index){ if(index == files.length){ return callback(); } let child = path.join(dir,files[index]); fs.stat(child,(err,stat)=>{ if(stat.isDirectory()){ deep(child,()=>next(index+1)); }else{ console.log(child); next(index+1); } }) }(0) }) } 複製程式碼
8.3 同步廣度優先+先序遍歷
function wideSync(dir){ let dirs = [dir]; while(dirs.length>0){ let current = dirs.shift(); console.log(current); let stat = fs.statSync(current); if(stat.isDirectory()){ let files = fs.readdirSync(current); files.forEach(item=>{ dirs.push(path.join(current,item)); }); } } } 複製程式碼
8.4 非同步廣度優先+先序遍歷
// 非同步廣度遍歷
function wide(dir, cb) {
console.log(dir);
cb && cb()
fs.readdir(dir, (err, files) => {
!function next(i){
if(i>= files.length) return;
let child = path.join(dir,files[i]);
fs.stat(child,(err,stat)=>{
if(stat.isDirectory()){
wide(child, () => next(i+1));
} else {
console.log(child);
next(i+1);
}
})
}(0);
})
}
wide(path.join(__dirname,'a'));
複製程式碼
8. path模組
path是node中專門處理路徑的一個核心模組
- path.join 將多個引數值字串結合為一個路徑字串
- path.basename 獲取一個路徑中的檔名
- path.extname 獲取一個路徑中的副檔名
- path.sep 作業系統提定的檔案分隔符
- path.delimiter 屬性值為系統指定的環境變數路徑分隔符
- path.normalize 將非標準的路徑字串轉化為標準路徑字串 特點:
- 可以解析 . 和 ..
- 多個槓可以轉換成一個槓
- 在windows下反槓會轉化成正槓
- 如結尾以槓結尾的,則保留斜槓
- resolve
- 以應用程式根目錄為起點
- 如果引數是普通字串,則意思是當前目錄的下級目錄
- 如果引數是.. 回到上一級目錄
- 如果是/開頭表示一個絕對的根路徑
var path = require('path');
var fs = require('fs');
/**
* normalize 將非標準化的路徑轉化成標準化的路徑
* 1.解析. 和 ..
* 2.多個斜槓會轉成一個斜槓
* 3.window下的斜槓會轉成正斜槓
* 4.如果以斜槓會保留
**/
console.log(path.normalize('./a////b//..\\c//e//..//'));
// \a\c\
//多個引數字串合併成一個路徑 字串
console.log(path.join(__dirname,'a','b'));
/**
* resolve
* 以就用程式為根目錄,做為起點,根據引數解析出一個絕對路徑
* 1.以應用程式為根起點
* 2... .
* 3. 普通 字串代表子目錄
* 4. /代表絕地路徑根目錄
*/
console.log(path.resolve());//空代表當前的目錄 路徑
console.log(path.resolve('a','/c'));// /a/b
// d:\c
//可以獲取兩個路徑之間的相對關係
console.log(path.relative(__dirname,'/a'));
// a
//返回指定路徑的所在目錄
console.log(path.dirname(__filename)); // 9.path
console.log(path.dirname('./1.path.js'));// 9.path
//basename 獲取路徑中的檔名
console.log(path.basename(__filename));
console.log(path.basename(__filename,'.js'));
console.log(path.extname(__filename));
console.log(path.sep);//檔案分隔符 window \ linux /
console.log(path.win32.sep);
console.log(path.posix.sep);
console.log(path.delimiter);//路徑 分隔符 window ; linux :
複製程式碼
9. flags
符號 | 含義 |
---|---|
r | 讀檔案,檔案不存在報錯 |
r+ | 讀取並寫入,檔案不存在報錯 |
rs | 同步讀取檔案並忽略快取 |
w | 寫入檔案,不存在則建立,存在則清空 |
wx | 排它寫入檔案 |
w+ | 讀取並寫入檔案,不存在則建立,存在則清空 |
wx+ | 和w+類似,排他方式開啟 |
a | 追加寫入 |
ax | 與a類似,排他方式寫入 |
a+ | 讀取並追加寫入,不存在則建立 |
ax+ | 作用與a+類似,但是以排他方式開啟檔案 |
10. 助記
- r 讀取
- w 寫入
- s 同步
- + 增加相反操作
- x 排他方式
- r+ w+的區別?
- 當檔案不存在時,r+不會建立,而會導致呼叫失敗,但w+會建立。
- 如果檔案存在,r+不會自動清空檔案,但w+會自動把已有檔案的內容清空。