• buffer缓冲区,stream 数据流
  • buffer存储中间变量,目的是为了方便CPU执行数据存取操作的时候,可以有一个中间存储区域
  • 而流操作,类似于水流一样,可以通过管道操作数据,还可以对数据进行分段

    Buffer、Stream与FS有什么关系呢

    FS是内置核心模块,提供文件系统操作的API(文件目录的创建删除,查询,读取,写入,操作系统的二进制数据)

    FS模块结构

  • FS基本操作类

  • FS常用API

fs.png

用户对于文件所具备的操作权限

file权限.png

Nodejs中flag表示对文件操作方式

  • r:可读
  • w:可写
  • s:同步
  • +:表示执行相反操作
  • x:排它操作
  • a:追加操作

fd就是操作系统分配给被打开文件的标识(从3开始)

FS介绍总结

  • fs是Nodejs中内置核心模块
  • 代码层面上fs分为基本操作类和常用API
  • 文件操作。权限位、标识符、操作符

    文件读写与拷贝操作(以下是异步)

  • 文件操作API(异步,同步请看文档)

  • 错误优先,如果是成功的,err会返回null

    readFile:从指定文件中读取数据

    ```javascript // 1. fs.readFile(path,字符编码,callback) const fs = require(‘fs’)

// 路径也可以使用path.resolve来补全 fs.readFile(‘/etc/passwd’,’utf8’,(err, data) => { if (err) throw err; console.log(data); });

// node官网: fs.readFile() 函数缓冲整个文件。 为了最小化内存成本,在可能的情况下优先通过 fs.createReadStream() 进行流式传输。

  1. <a name="dTRS0"></a>
  2. #### writeFile:向指定文件中写入数据
  3. ```javascript
  4. const fs = require('fs')
  5. // 2. fs.writeFile(file, data,'字符编码', callback)
  6. // 当 file 是文件名时,将数据异步地写入文件,如果文件已存在则替换该文件。
  7. fs.writeFile('data.txt','hello,杰哥',{
  8. mode:438, // 可读可写,不可执行
  9. flag: 'r+', // r+ 不会清空,直接写入, w+会清空重新写入
  10. encoding: 'utf-8'
  11. },(err,data) => {
  12. if(!err){
  13. fs.readFile('data.txt','utf-8',(err,data) => {
  14. if(!err){
  15. console.log(data);
  16. }
  17. })
  18. }
  19. })
  20. /*
  21. 在同一个文件上多次使用 fs.writeFile() 而不等待回调是不安全的。 对于这种情况,建议使用 fs.createWriteStream()。
  22. 与 fs.readFile 类似,fs.writeFile 是一个便捷的方法,其在内部执行多次 write 调用以写入传给它的缓冲区。 对于性能敏感的代码,则考虑使用 fs.createWriteStream()。
  23. 可以使用 <AbortSignal> 取消 fs.writeFile()。 取消是"尽力而为"的,并且可能仍会写入一些数据。
  24. 中止正在进行的请求不会中止单个操作系统请求,而是中止内部缓冲的 fs.writeFile 执行。
  25. */

appendFile:追加的方式向指定文件中写入数据

const fs = require('fs')
// fs.appendFile(path, data[, options], callback)
// 异步地将数据追加到文件,如果该文件尚不存在,则创建该文件。 data 可以是字符串或 <Buffer>。

fs.appendFile('message.txt', 'data to append',(err) => {
  if (err) throw err;
  console.log('The "data to append" was appended to file!');
});

// 官网案例
// 通过任何其他 fs 操作对当前正在使用的任何文件描述符 (fd) 调用 fs.close(),则可能会导致未定义的行为。
// fs.close() 关闭文件
function closeFd(fd) {
  fs.close(fd, (err) => {
    if (err) throw err;
  });
}

fs.open('message.txt', 'a', (err, fd) => {
  if (err) throw err;

  try {
    fs.appendFile(fd, 'data to append', 'utf8', (err) => {
      closeFd(fd);
      if (err) throw err;
    });
  } catch (err) {
    closeFd(fd);
    throw err;
  }
});

copyFile:将某个文件中的数据拷贝至另外一个文件

  • src | | 要复制的源文件名
  • dest | | 复制操作的目标文件名
  • mode 复制操作的修饰符。 默认值: 0。
  • callback ```javascript const fs = require(‘fs’) function callback(err) { if (err) throw err; console.log(‘source.txt was copied to destination.txt’); }

// 默认情况下将创建或覆盖 destination.txt。 fs.copyFile(‘source.txt’, ‘destination.txt’, callback);

// 通过使用 COPYFILE_EXCL,如果 destination.txt 存在,则该操作将失败。 fs.copyFile(‘source.txt’, ‘destination.txt’, constants.COPYFILE_EXCL, callback);

mode 是可选的整数,用于指定复制操作的行为。 可以创建由两个或多个值的按位或组成的掩码(例如 fs.constants.COPYFILE_EXCL | fs.constants.COPYFILE_FICLONE)。

fs.constants.COPYFILE_EXCL: 如果 dest 已经存在,则复制操作将失败。 fs.constants.COPYFILE_FICLONE: 复制操作将尝试创建写时复制引用链接。 如果平台不支持写时复制,则使用后备复制机制。 fs.constants.COPYFILE_FICLONE_FORCE: 复制操作将尝试创建写时复制引用链接。 如果平台不支持写时复制,则该操作将失败。

<a name="KVJmL"></a>
#### watchFile:对指定文件进行监控

- filename [<string>](http://url.nodejs.cn/9Tw2bK) | [<Buffer>](http://nodejs.cn/api/buffer.html#class-buffer) | [<URL>](http://nodejs.cn/api/url.html#the-whatwg-url-api)
- listener [<Function>](http://url.nodejs.cn/ceTQa6) 可选,先前使用 fs.watchFile() 附加的监听器。

停止监视 filename 的变化。 如果指定了 listener,则仅删除该特定监听器。 否则,所有监听器都将被删除,从而有效地停止监视 filename。<br />使用未被监视的文件名调用 fs.unwatchFile() 是空操作,而不是错误。<br />使用 [fs.watch()](http://nodejs.cn/api/fs.html#fswatchfilename-options-listener) 比 fs.watchFile() 和 fs.unwatchFile() 更高效。 应尽可能使用 fs.watch() 而不是 fs.watchFile() 和 fs.unwatchFile()。
```javascript
const fs = require('fs')
fs.watchFile('data.txt',{interval:20},(curr,prev) => {
    // curr是修改之后的文件,prev是修改之前的
    if(curr.mtime !== prev.mtime){
        console.log('文件被修改了');
        fs.unwatchFile('data.txt') // 取消监控
    }
})

打开关闭

const fs = require('fs')
const path = require('path')

// open
fs.open(path.resolve('data.txt'),'r',(err,fd) => {
  console.log(fd); // 3 
})

// close
fs.open('data.txt','r',(err,fd) => {
    // fd可以追踪当前的文件资源
    fs.close(fd, err => {
        console.log('关闭成功');
    })
})

md转html文件实现

/*
01 读取md和css内容
02 将上述读取出来的内容替换占位符,生成一个最终需要的HTML字符串
03 将上述html字符写入到指定html文件中
04 监听md文档内容的变化,然后更新html内容
05 使用browser-sybcKa来实时显示html内容
*/
const fs = require('fs')
const path = require('path')
const marked = require('marked')
const browserSync = require('browser-sync')

let mdPath = path.join(__dirname, process.argv[2])
let cssPath = path.resolve('github.css')
let htmlPath = mdPath.replace(path.extname(mdPath), '.html')

fs.readFile(mdPath,'utf-8',(err,data) => {
  let htmlStr = marked(data)
  fs.readFile(cssPath, 'utf-8',(err,data) => {
      let retHtml = temp.replace('{{content}}',htmlStr).replace('{{style}}',data)
      // 将上述内容写入到指定的html文件,用于在浏览器中展示
      fs.writeFile(htmlPath, retHtml,(err) => {
          console.log('html生成成功');
      })
  })
})

const temp =  `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
*{
  margin: 0;
}
</style>
<body>
   <div class="markdown-body">
     {{content}}
   </div> 
</body>
</html>
`

大文件读写操作

  • 要实现大文件边度边写

大文件写入7.png

const fs = require('fs')
// read : 所谓的读操作就是将数据从磁盘文件中写入到buffer中
let buf = Buffer.alloc(10)

// write read
/*
    fd用于定位当前被打开的文件
    buf 用于表示当前缓冲区
    offset 表示当前从buf的哪个位置开始执行写入
    length 表示当前写入的长度
    position 表示当前从文件的哪个位置开始读取
*/
// 把磁盘里面的东西拿出来放入暂存区
fs.open('data.txt','r', (err,rfd) => {
  console.log(rfd); // 3
  // rfd表示要执行的文件是谁
  fs.read(rfd, buf, 0,3,0,(err,readBytes,data) => {
      // readBytes实际读了都是个字节
      console.log(readBytes);// 3
      console.log(data);
      console.log(data.toString());
  })
})

// write 将缓冲区间里的内容写入到磁盘文件中
// 先读再写

// 把暂存区内容拿出来,放入文件中
buf = Buffer.from('1234567890')
fs.open('data.txt','w',(err,wfd) => {
    // wfd 表示要操作的文件,buf存放buf的缓冲区
    fs.write(wfd, buf,0,3,0,(err,written,buffer) => {
      console.log(written); // 当时写入的字节数  3个字节
      console.log(buffer);
      console.log(buffer.toString());
      fs.close(wfd) // 关闭
    })
})

文件拷贝自定义实现

  • 对大体积文件,进行拷贝
  • 每一次open都是对系统资源的消耗 ```javascript const fs = require(“fs”);

/**

  • 01 打开A文件,利用read将数据保存到buffer暂存起来
  • 02 打开B文件,利用write将buffer中写数据写入到b文件中 /

let buf = Buffer.alloc(10);

数据的拷贝(不完全) // 01从打开的文件中读取数据 fs.open(“a.txt”, “r”, (err, rfd) => { // 03打开b文件,用于执行数据写入操作 fs.open(“b.txt”, “w”, (err, wfd) => { // 02从打开的文件中读取数据 fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => { // 04将buffer的数据写入到b.txt中 fs.write(wfd, buf, 0, 10, 0, (err, written) => { console.log(“写入成功”); }); }); }); });

// 全部拷贝 const BUFFER_SIZE = buf.length; let readOffset = 0;

fs.open(“a.txt”, “r”, (err, rfd) => { fs.open(“b.txt”, “w”, (err, wfd) => { function next() { fs.read(rfd, buf, 0, BUFFER_SIZE, readOffset, (err, readBytes) => { if (!readBytes) { // 如果条件成立,说明内容已经读取完毕 fs.close(rfd, () => {}); fs.close(wfd, () => {}); console.log(“拷贝完成”); return; } readOffset += readBytes; fs.write(wfd, buf, 0, readBytes, (err, written) => { next(); }); }); } next(); }); });

<a name="G2G8T"></a>
### FS之目录操作API
<a name="T5H93"></a>
#### 目录操作API

- access:判断文件或目录是否具有操作权限
- stat:获取目录及文件信息
- mkdir:创建目录
- rmdir:删除目录
- readdir:读取目录中内容
- unlink:删除指定文件
```javascript
const fs = require("fs");

// 1. access
fs.access("a.txt", (err) => {
  // 如果有错误,证明对他没有操作权限,或者路径信息是错误的
  if (err) {
    console.log(err);
  } else {
    console.log("有操作权限");
  }
});

// 2. stat
fs.stat("a.txt", (err, statObj) => {
  console.log(statObj.size); // 文件大小
  console.log(statObj.isFile()); // 判断是否是文件
  console.log(statObj.isDirectory()); // 判断是否是目录
});

// // 3. mkdir 保证前面目录存在
// 想要实现递归的创建,{recursive: true}
fs.mkdir("a/a/a", { recursive: true }, (err) => {
  if (!err) {
    console.log("创建成功");
  } else {
    console.log(err);
  }
});

// rmdir  删除,默认情况只能删除空目录
// 递归删除 { recursive: true }
fs.rmdir("a", { recursive: true }, (err) => {
  if (!err) {
    console.log("删除成功");
  } else {
    console.log(err);
  }
});

// readdir 读取目录, 只能看当前目录下
fs.readdir('a',(err,files) => {
    console.log(files);
})

// 文件的删除
fs.unlink('a/a.txt',err => {
    if(!err){
        console.log('删除成功');
    }
})

同步创建目录实现(对熟练度的加深)

const fs = require("fs");
const path = require("path");

/*
  01 将来调用时需要接收类似于a/b/c,这样的路径,它们之间是采用 / 去行连接
  02 利用 / 分割符将路径进行拆分,将每一项放入一个数组中进行管理['a','b','c']
  03 对上述的数组进行遍历,我们需要拿到每一项,与前一项进行拼接
  04 判断一个当前对拼接之后的路径是否具有可操作的权限,如果有证明存在,否则的话就需要执行创建
*/
function makeDirSync(dirpath) {
  // path.sep可以拿到当前操作系统对路径的分割符
  let items = dirpath.split(path.sep);
  //     console.log(items);
  //     console.log(path.sep);// \

  for (let i = 1; i <= items.length; i++) {
      // 每次都要判断是否有操作权限
      let dir = items.slice(0, i).join(path.sep)
      // 如果没有权限,会报错
      try {
          fs.accessSync(dir)
      } catch (error) {
          fs.mkdirSync(dir)
      }
  }

}

makeDirSync("a\\v\\c");

异步创建目录实现(并不是为了替代调原生的方法)

const fs = require("fs");
const path = require("path");

function mkDir(dirPath, cb) {
  let parts = dirPath.split("/");
  let index = 1;
  // 定义函数,完成创建和往后移的操作
  function next() {
    // 递归出口
    if (index > parts.length) return cb && cb();
    let current = parts.slice(0, index++).join("/");
    // 是否有权限
    fs.access(current, (err) => {
      if (err) {
        // 有错误就创建一个
        fs.mkdir(current, next);
      } else {
        // 不需要创建当层
        next();
      }
    });
  }
  next()
}

mkDir('a/b/c',() => {
    console.log('创建成功');
})



// 将access与mkdir处理成async...风格
const access = promisify(fs.access);
const mkdir = promisify(fs.mkdir);

async function myMkdir(dirPath, cb) {
  let parts = dirPath.split("/");
  for (let index = 1; index <= parts.length; index++) {
    let current = parts.slice(0, index).join("/");
    // 因为采用promise风格,捕获要使用
    try {
      await access(current);
    } catch (error) {
      // 不可读写就创建一个
      mkdir(mkdir);
    }
  }
  cb && cb();
}

myMkdir("a/v/c", () => {
  console.log("创建成功");
});

递归删除目录(练习)

const fs = require("fs");
const path = require("path");
/**
 * 需求自定义函数,接收一个路径,然后执行删除操作
 * 01 判断当前传入的路径是否为一个文件,直接删除当前文件即可
 * 02 如果当前传入的是一个目录,我们需要继续读取目录中的内容。然后再执行删除操作
 * 03 将删除行为定义成一个函数,然后通过递归的方式进行复用
 * 04 将当前的名称拼接成在删除时,可以使用的路径
 *
 */

function myRmdir(dirPath, cb) {
  // 判断当前dirPath的类型
  fs.stat(dirPath, (err, statObj) => {
    if (statObj.isDirectory()) {
      // 读取目录---继续读取
      fs.readdir(dirPath, (err, files) => {
        //   console.log(files);  [ 'aaa.js', 'c' ]
        let dirs = files.map((item) => {
          return path.join(dirPath, item);
        });
        console.log(dirs);
        let index = 0;
        function next() {
          // 找一个递归的出口
          // 当我们发现数组中的每一项都被遍历完了,结束遍历,当前最外层的目录删除
          if (index === dirs.length) return fs.rmdir(dirPath, cb);

          // index每次都得往后移
          let current = dirs[index++];

          myRmdir(current, next);
        }
        next()
      });
    } else {
      //文件---直接删除
      fs.unlink(dirPath, cb);
    }
  });
}

myRmdir("a", () => {
  console.log("删除成功");
});