node fs學習筆記

江小魚發表於2018-06-17

基礎用法

在 node 中,使用 fs 模組來實現所有有關檔案以及目錄的建立,寫入和刪除操作。在 fs 模組中,所有的方法都分為同步和非同步兩種,==具有 sync 字尾的是同步方法==,不具有 sync 字尾的方法是非同步方法。

  • 常用 api
fs.readFile
fs.writeFile
fs.copyFile
fs.unlink

fs.read
fs.write

fs.open
fs.sync
fs.close

fs.mkdir
fs.rmdir
fs.rname
fs.readdir

複製程式碼

文件地址:http://nodejs.cn/api/fs.html

命令列中輸入 ls -l 可以檢視檔案以及資料夾的許可權。

image

對檔案常見操作許可權是 666 即讀寫操作。如果想更改許可權 chmod -R 777 * 把所有檔案更改為最高許可權。助記:二爺一直死讀書(2寫1執4讀)

flag 引數含義

符號 含義
r 讀檔案,檔案不存在報錯
r+ 讀取並寫入,檔案不存在報錯
rs 同步讀取檔案並忽略快取
w 寫入檔案,不存在則建立,存在則清空
wx 排它寫入檔案
w+ 讀取並寫入檔案,不存在則建立,存在則清空
wx+ 和w+類似,排他方式開啟
a 追加寫入
ax 與a類似,排他方式寫入
a+ 讀取並追加寫入,不存在則建立
ax+ 作用與a+類似,但是以排他方式開啟檔案

助記:

  • r 讀取
  • w 寫入
  • s 同步
  • + 增加相反操作
  • x 排他方式
  • r+ w+的區別?
    • 當檔案不存在時,r+不會建立,而會導致呼叫失敗,但w+會建立。
    • 如果檔案存在,r+不會自動清空檔案,但w+會自動把已有檔案的內容清空。

檔案操作

  • fs.readFile 讀取檔案
fs.readFile(__dirname + '/a.text',{ encoding: 'utf8', flag: 'r' }, function(err, data) {
  if (err) {
    console.log(err);
    return;
  }
  console.log(data);
});
複製程式碼
  • fs.writeFile 寫入檔案
// 檔案裡存的永遠是二進位制
fs.writeFile(__dirname + '/a.text', Buffer.from('1233333'), { flag: 'w', mode: 0o666 }, function(error, data) {
  console.log(error);
});

複製程式碼
  • fs.copyFile 拷貝檔案
fs.copyFile(__dirname + '/buffer.js', __dirname + '/a.text', function() {
  console.log('成功');
})

複製程式碼

注意: 如果要實現拷貝大檔案,以上操作都會造成記憶體過大情況

  • fs.open && fs.read

fd 檔案描述符由3開始。0 標準輸入 1 標準輸出 2 錯誤輸出

fs.open(__dirname + '/a.text', 'r', function(err, fd ) {
  let BUFFER_SIZE = 3;
  // 把檔案的內容讀取到記憶體中。
  let buffer = Buffer.alloc(BUFFER_SIZE);
  // fd 描述符 
  // buffer 讀取到哪個buffer上 
  // offset buffer的偏移量
  // byteRead 實際讀到的個數
  fs.read(fd, buffer, 0, BUFFER_SIZE, 0, function(err, byteRead) {
    console.log(buffer);
  });
});
複製程式碼

每次讀3個迴圈讀直到讀完為止,讀取完畢後將其關閉。

fs.open(__dirname + '/a.text', 'r', function(err, fd ) {
  let BUFFER_SIZE = 3;
  // 把檔案的內容讀取到記憶體中。
  let buffer = Buffer.alloc(BUFFER_SIZE);
  let index = 0;
  // fd 描述符 
  // buffer 讀取到哪個buffer上 
  // offset buffer的偏移量
  // byteRead 實際讀到的個數
  function next() {
    fs.read(fd, buffer, 0, BUFFER_SIZE, index, function(err, byteRead) {
      index += byteRead;
      console.log(buffer.slice(0, byteRead).toString());
      if (byteRead === BUFFER_SIZE) {
        next();
      } else {
        fs.close(fd, () => {
          console.log('關閉');
        })
      }
    });
  }
  next();
});

複製程式碼

邊讀取邊寫入,1.text 拷貝到 5.text中

// 1.text 拷貝到 5.text中
function copy(source, target) {
  let BUFFER_SIZE = 3;
  let index = 0;
  let buffer = Buffer.alloc(BUFFER_SIZE);
  // 讀取檔案
  fs.open(source, 'r', function(error, rfd) {
    if (error) {
      console.log(error);
      return;
    }
    // 寫入檔案
    fs.open(target, 'w', 0o666, function(err, wfd) {
      function next() {
        fs.read(rfd, buffer, 0, BUFFER_SIZE, index, function(er, bytesRead) {
          // 要寫入的檔案描述符、寫入的buffer、buffer的偏移量、buffer寫入的個數、檔案的位置、callback 
          fs.write(wfd, buffer, 0, bytesRead, index, function(e, byteWritten) {
            console.log(byteWritten);
            index += bytesRead;
            // 如果還有要寫入的內容就繼續讀取
            if (byteWritten) {
              next();
            } else {
              // 關閉檔案
              fs.close(rfd, () => {});
              // 把記憶體中的內容強制寫完後在關閉檔案。(寫入的操作是非同步操作)
              fs.fsync(function() {
                fs.close(wfd, () => {});
              });
            }
          })
        });
      }
      next();
    });
  })
}
copy('1.text', '5.text');

複製程式碼

資料夾操作

建立目錄(同步和非同步),非同步不會阻塞主執行緒

fs.mkdirSync('a');
// 不能跨級建立 如 a/b/c
複製程式碼
  • fs.mkdirSync && fs.mkdir

同步方法實現建立資料夾 /a/b/c/d/e/f

function makep(p) { // a/b/c
  let dirs = p.split('/');
  console.log(dirs);
  for(let i = 0; i < dirs.length; i++) {
     let p = dirs.slice(0, i+1).join('/');
    // 判斷當前檔案是否存在
    try {
      fs.accessSync(p)
    } catch(e) {
      fs.mkdirSync(p);
    }
  }
}
makep('a/b/c/d/e/f');
複製程式碼

非同步方法實現建立資料夾 /a/b/c/d/e/f

function makep(dir, callback) {
  let dirs = dir.split('/');
  let index = 1;
  function next(index) {
    // 當索引溢位時候停止遞迴
    if(index - 1 === dirs.length) {
      return callback();
    }
    let p = dirs.slice(0, index).join('/');
    fs.access(p, (err)=>{
      if(!err) {
        // 沒錯誤表示存在當前目錄則建立下一個目錄
        next(index + 1)
      } else {
        // 如果沒有這個檔案,報錯,則建立這個檔案,建立完後建立下一個檔案
        fs.mkdir(p, (err) => {
          if (err) return console.log(err); 
          next(index + 1);
        });
      }
    })
    
  }
  next(index);
}
makep('a/b/c/d/e/f', () => {
  // 建立完成回撥
  console.log('ok');
});
複製程式碼
  • fs.rmdirSync

刪除檔案首先保證檔案為空。跟建立檔案相反。

遍歷的順序: 先序、中序、後序

  1. 先序:先遍歷 b 再 c 再 d
  2. 中序:先遍歷 c 再 b 再 d
  3. 後序:先遍歷 c 再 d 在 b

主要看 b 的位置。遍歷也包括深度和廣度

  • fs.unlink 刪除檔案

只有一層目錄情況

// 讀取目錄 一級
let dirs = fs.readdirSync('c'); // [ 'a', 'a.js' ]
// 對映路徑[ 'c/a', 'c/a.js' ]
dirs = dirs.map(item => {
  return path.join('c', item);
});
dirs.forEach(p => {
  // 判斷檔案的狀態 stat 上有兩個方法 isDirectory isFile
  let stat = fs.statSync(p);
  if (stat.isDirectory()) { // 是否是資料夾
    fs.rmdirSync(p);
  } else {
    // 刪除檔案
    fs.unlinkSync(p);
  }
});

複製程式碼

刪除多層目錄

  • 遞迴同步深度刪除
// 深度刪除由內到外  遞迴深度刪除
function removeDirSync(dir) {
  // 刪除的時候允許 刪除的不一定是目錄
  let stat = fs.statSync(dir);
  if (stat.isDirectory()) { // 資料夾 讀取檔案中內容
    // 讀取目錄下檔案
    let dirs = fs.readdirSync(dir);
    // 拼接目錄
    dirs = dirs.map(d => path.join(dir, d)); // [c/a, c/b]
    console.log(dirs);
    dirs.forEach(d => { // 依次刪除子目錄
      // 遞迴呼叫刪除目錄裡檔案
      removeDirSync(d);
    });
    // 刪除自己
    fs.rmdirSync(dir);
  } else { // 檔案 刪除跑路
    fs.unlinkSync(dir);
  }
}

removeDirSync('c');
複製程式碼
  • 遞迴非同步深度刪除 - promise
function removeDir(dir) {
  return new Promise((resolve, reject) => {
    // 判斷檔案型別
    fs.stat(dir, (err, stat) => {
      if (stat.isDirectory()) {
        // 讀取當前目錄下的內容
        fs.readdir(dir, (err, dirs) => {
          // 拼接路徑 [c/b, c/d]
          dirs = dirs.map(d => path.join(dir, d));
          // promise 化
          dirs =  dirs.map(p => {
            return removeDir(p); // 【promise, promise】
          });
          Promise.all(dirs).then(() => {
            console.log(dir, 111);
            // 刪除自己
            fs.rmdir(dir, resolve);
          });
        });
      } else {
        // 如果是檔案刪除後,呼叫 promise 成功
        fs.unlink(dir, resolve);
      }
    });
  });
}
removeDir('b').then(data=>{
  console.log('ok');
});
複製程式碼
  • 普通回撥深度刪除
// 普通回撥刪除
function rmdir(dir, callback) {
  fs.stat(dir, (err, stat) => {
    if (stat.isDirectory()) {
      // 目錄
      fs.readdir(dir, (err, dirs) => {
        // 只要涉及到非同步遞迴 使用next
        function next(index) {
          if ((dirs.length === 0) || index === dirs.length) {
            return fs.rmdir(dir, callback); // 目錄下沒內容刪除自己
          }

          let p = path.join(dir, dirs[index]); // a/b
          rmdir(p, ()=>next(index+1));// 刪除 a/b 後刪除 a/c
        }
        next(0);
      });
    } else {
      // 檔案
      fs.unlink(dir, callback);
    }
  })
}

rmdir('a', function() {
  console.log('ok');
});
複製程式碼
  • 廣度刪除

先準備一個陣列。改變指標移動,將目錄讀出放到陣列中。字典刪除

同步方式:由後向前刪除

function preWide(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))];
    }
  }
  console.log(arr); // [ 'a', 'a/a.js', 'a/b', 'a/c', 'a/b/b.js', 'a/b/d', 'a/c/c.js' ]
  for (let i = arr.length - 1; i >=0 ; i--) {
    let p = arr[i];
    let stat = fs.statSync(p);
    if(stat.isDirectory()) {
      fs.rmdirSync(p);
    } else {
      fs.unlinkSync(p);
    }
  }
}

preWide('a');
複製程式碼

相關文章