“一切皆是文件”是 Unix/Linux 的基本哲学之一, 不仅普通的文件、目录、字符设备、块设备、套接字等在 Unix/Linux 中都是以文件被对待, 也就是说这些资源的操作对象均为 fd (文件描述符), 都可以通过同一套 system call 来读写. 在 linux 中你可以通过 ulimit 来对 fd 资源进行一定程度的管理限制.

stdio

stdio (standard input output) 标准的输入输出流, 即输入流 (stdin), 输出流 (stdout), 错误流 (stderr) 三者.
在Node.js中分别对应process.stdin(Readable), process.stdout(Writable)及process.stderr(Writable)三个stream

以C的伪代码来看输出函数的实现:

  1. int printf(FILE *stream, 要打印的内容)
  2. {
  3. // ...
  4. // 1. 申请一个临时内存空间
  5. char *s = malloc(4096);
  6. // 2. 处理好要打印的的内容, 其值存储在 s 中
  7. // ...
  8. // 3. 将 s 上的内容写入到 stream 中
  9. fwrite(s, stream);
  10. // 4. 释放临时空间
  11. free(s);
  12. // ...
  13. }

其中第三步中的stream就是指的stdout(标准输出流)

实际上在 shell 上运行一个应用程序的时候, shell 做的第一个操作是 fork 当前 shell 的进程 (所以, 如果你通过 ps 去查看你从 shell 上启动的进程, 其父进程 pid 就是当前 shell 的 pid), 在这个过程中也把 shell 的 stdio 继承给了你当前的应用进程, 所以你在当前进程里面将数据写入到 stdout, 也就是写入到了 shell 的 stdout, 即在当前 shell 上显示了。 输入也是同理, 当前进程继承了 shell 的 stdin, 所以当你从 stdin 中读取数据时, 其实就获取到你在 shell 上输入的数据. (PS: shell 可以是 windows 下的 cmd, powershell, 也可以是 linux 下 bash 或者 zsh 等)

File System

fs是file syetem的缩写,该模块提供本地文件的读写能力,基本上是POSIX文件操作命令的简单包装。但是,这个模块几乎对所有操作提供异步和同步两种操作方式,供开发者选择,可以通过fs.open()去获取一个文件描述符.

  1. // mode-设置文件权限, 文件创建默认权限为066(可读, 可写)
  2. fs.open(path, flags[, mode], callback)
  3. // example
  4. const fs = require('fs')
  5. console.log('准备打开文件')
  6. fs.open('fs/input.txt', 'r+', function(err ,fd) {
  7. if(err) return console.error(err)
  8. console.log('文件打开成功')
  9. })

Node.js的文件系统中的方法有同步和异步的版本。例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。
比起同步方法,异步性能更好,速度更快没有阻塞。

读写数据流

fs模块中读写数据流分别使用的是:fs.createReadStream、fs.createWriteStream两个方法.

createReadStream

createReadStream方法往往用于打开大型的文本文件,创建一个读取操作的数据流。所谓大型文本文件,指的是文本文件的体积很大,读取操作的缓存装不下,只能分成几次发送,每次发送会触发一个data事件,发送结束会触发end事件.

  1. const fs = require('fs')
  2. function readLines(input, func) {
  3. let remaining = '';
  4. input.on('data', function(data) {
  5. remaining += data;
  6. var index = remaining.indexOf('\n');
  7. var last = 0;
  8. while (index > -1) {
  9. var line = remaining.substring(last, index);
  10. last = index + 1;
  11. func(line);
  12. index = remaining.indexOf('\n', last);
  13. }
  14. remaining = remaining.substring(last);
  15. });
  16. input.on('end', function() {
  17. if (remaining.length > 0) {
  18. func(remaining);
  19. }
  20. })
  21. }
  22. function func(data) {
  23. console.log('Line: ' + data)
  24. }
  25. let input = fs.createReadStream('lines.txt');
  26. readLines(input, func);

createWriteStream

createWriteStream方法创建一个写入数据流对象,该对象的write方法用于写入数据,end方法用于结束写入操作

  1. var out = fs.createWriteStream(fileName, {
  2. encoding: 'utf8'
  3. });
  4. out.write(str);
  5. out.end();

Readline

readline模块提供一个用于从Readable的stream中一次读取一行的接口

  1. const readline = require('readline')
  2. const fs = require('fs')
  3. const r1 = readline.createInterface({
  4. input: fs.createReadStream('sample.txt')
  5. })
  6. rl.on('line', (line) => {
  7. console.log(`Line from file: ${line}`);
  8. });

实现上, realine 在读取 TTY 的数据时, 是通过input.on(‘keypress’, onkeypress) 时发现用户按下了回车键来判断是新的 line 的, 而读取一般的 stream 时, 则是通过缓存数据然后用正则 .test 来判断是否为 new line 的.