“一切皆是文件”是 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的伪代码来看输出函数的实现:
int printf(FILE *stream, 要打印的内容)
{
// ...
// 1. 申请一个临时内存空间
char *s = malloc(4096);
// 2. 处理好要打印的的内容, 其值存储在 s 中
// ...
// 3. 将 s 上的内容写入到 stream 中
fwrite(s, stream);
// 4. 释放临时空间
free(s);
// ...
}
其中第三步中的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()去获取一个文件描述符.
// mode-设置文件权限, 文件创建默认权限为066(可读, 可写)
fs.open(path, flags[, mode], callback)
// example
const fs = require('fs')
console.log('准备打开文件')
fs.open('fs/input.txt', 'r+', function(err ,fd) {
if(err) return console.error(err)
console.log('文件打开成功')
})
Node.js的文件系统中的方法有同步和异步的版本。例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。
比起同步方法,异步性能更好,速度更快没有阻塞。
读写数据流
fs模块中读写数据流分别使用的是:fs.createReadStream、fs.createWriteStream两个方法.
createReadStream
createReadStream方法往往用于打开大型的文本文件,创建一个读取操作的数据流。所谓大型文本文件,指的是文本文件的体积很大,读取操作的缓存装不下,只能分成几次发送,每次发送会触发一个data事件,发送结束会触发end事件.
const fs = require('fs')
function readLines(input, func) {
let 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)
}
let input = fs.createReadStream('lines.txt');
readLines(input, func);
createWriteStream
createWriteStream方法创建一个写入数据流对象,该对象的write方法用于写入数据,end方法用于结束写入操作
var out = fs.createWriteStream(fileName, {
encoding: 'utf8'
});
out.write(str);
out.end();
Readline
readline模块提供一个用于从Readable的stream中一次读取一行的接口
const readline = require('readline')
const fs = require('fs')
const r1 = readline.createInterface({
input: fs.createReadStream('sample.txt')
})
rl.on('line', (line) => {
console.log(`Line from file: ${line}`);
});
实现上, realine 在读取 TTY 的数据时, 是通过input.on(‘keypress’, onkeypress) 时发现用户按下了回车键来判断是新的 line 的, 而读取一般的 stream 时, 则是通过缓存数据然后用正则 .test 来判断是否为 new line 的.