Fork me on GitHub
余鸢

文件系统IO

从文件异步读取

对所有文件操作使用文件系统模块:

1
const fs = require('fs');

Encoding

在这个例子中,从目录/ tmp中读取hello.txt。 此操作将在后台完成,并且在完成或失败时发生回调:

1
2
3
4
5
6
7
fs.readFile('/tmp/hello.txt', { encoding: 'utf8' }, (err, content) => {
// 如果发生错误,输出并返回
if(err) return console.error(err);
// 没有发生错误,内容是一个 string
console.log(content);
});

Without Encoding

从当前目录中读取二进制文件binary.txt,在后台异步读取。 请注意,我们不设置’encoding’选项 - 这样可以防止Node.js将内容解码为字符串:

1
2
3
4
5
6
7
8
fs.readFile('binary', (err, binaryContent) => {
// If an error occurred, output it and return
if(err) return console.error(err);
// No error occurred, content is a Buffer, output it in
// hexadecimal representation.
console.log(content.toString('hex'));
});

相对路径

请记住,在一般情况下,您的脚本可以使用任意当前工作目录运行。 要找到相对于当前脚本的文件,请使用_dirname_filename

1
2
3
fs.readFile(path.resolve(__dirname, 'someFile'), (err, binaryContent) => {
//Rest of Function
}

使用readdir或readdirSync列出目录内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const fs = require('fs');
// Read the contents of the directory /usr/local/bin asynchronously.
// The callback will be invoked once the operation has either completed
// or failed.
fs.readdir('/usr/local/bin', (err, files) => {
// On error, show it and return
if(err) return console.error(err);
// files is an array containing the names of all entries
// in the directory, excluding '.' (the directory itself)
// and '..' (the parent directory).
// Display directory entries
console.log(files.join(' '));
});

同步变量可用作readdirSync,它阻塞主线程,因此可以同时防止异步代码的执行。 大多数开发人员避免同步IO功能,以提高性能。

1
2
3
4
5
6
7
8
let files;
try {
files = fs.readdirSync('/var/tmp');
} catch(err) {
// An error occurred
console.error(err);
}

使用generator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const fs = require('fs');
// Iterate through all items obtained via
// 'yield' statements
// A callback is passed to the generator function because it is required by
// the 'readdir' method
function run(gen) {
var iter = gen((err, data) => {
if (err) { iter.throw(err); }
return iter.next(data);
});
iter.next();
}
const dirPath = '/usr/local/bin';
// Execute the generator function
run(function* (resume) {
// Emit the list of files in the directory from the generator
var contents = yield fs.readdir(dirPath, resume);
console.log(contents);
});

通过管道流复制文件

该程序使用可读流和可写流与流类提供的pipe()函数来复制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// require the file system module
var fs = require('fs');
/*
在名为'node.txt'的当前目录中创建可读流文件
     使用utf8编码
     读取16-kilobyte块中的数据
*/
var readable = fs.createReadStream(__dirname + '/node.txt', { encoding: 'utf8', highWaterMark: 16 * 1024 });
// create writable stream
var writable = fs.createWriteStream(__dirname + '/nodePipe.txt');
// 使用pipe将可读写复制
readable.pipe(writable);

同步读取文件

对于任何文件操作,您将需要文件系统模块:

1
const fs = require('fs');

Reading a String

fs.readFileSync的行为类似于fs.readFile,但是它在同步完成时不会回调,因此阻止主线程。 大多数node.js开发人员喜欢异步变量,这将在程序执行中几乎没有任何延迟。

如果指定了encoding选项,将返回一个字符串,否则将返回一个Buffer

1
2
3
4
5
6
7
8
// Read a string from another file synchronously
let content;
try {
content = fs.readFileSync('sync.txt', { encoding: 'utf8' });
} catch(err) {
// An error occurred
console.error(err);
}

检查文件或目录的权限

fs.access()确定路径是否存在,以及用户对该路径上的文件或目录的权限。 fs.access不返回结果,如果没有返回错误,则路径存在,并且用户具有所需的权限。

权限模式作为fs对象fs.constants上的属性可用

  • fs.constants.F_OK - 具有读/写/执行权限(如果没有提供模式,这是默认值)
  • fs.constants.R_OK - 具有读取权限
  • fs.constants.W_OK - 具有写权限
  • fs.constants.X_OK - 具有执行权限(与Windows上的fs.constants.F_OK相同)

异步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var fs = require('fs');
var path = '/path/to/check';
// checks execute permission
fs.access(path, fs.constants.X_OK, (err) => {
if (err) {
console.log("%s doesn't exist", path);
} else {
console.log('can execute %s', path);
}
});
// 检查我们是否有读/写权限
// 指定多个权限模式时
// 每个模式由管道分隔:`|`
fs.access(path, fs.constants.R_OK | fs.constants.W_OK, (err) => {
if (err) {
console.log("%s doesn't exist", path);
} else {
console.log('can read/write %s', path);
}
});

同步

fs.access也有一个同步版本的fs.accessSync。 使用fs.accessSync时,必须将其包含在try/catch块中。

1
2
3
4
5
6
7
8
// 检查写权限
try {
fs.accessSync(path, fs.constants.W_OK);
console.log('can write %s', path);
}
catch (err) {
console.log("%s doesn't exist", path);
}

检查文件或目录是否存在

异步

1
2
3
4
5
6
7
8
9
var fs = require('fs');
fs.stat('path/to/file', function(err) {
if (!err) {
console.log('file or directory exists');
}else if (err.code === 'ENOENT') {
console.log('file or directory does not exist');
}
});

同步

在这里,我们必须将函数调用包装在try/catch块中来处理错误。

1
2
3
4
5
6
7
8
9
10
var fs = require('fs');
try {
fs.statSync('path/to/file');
console.log('file or directory exists');
}catch (err) {
if (err.code === 'ENOENT') {
console.log('file or directory does not exist');
}
}

确定文本文件的行数

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const readline = require('readline');
const fs = require('fs');
var file = 'path.to.file';
var linesCount = 0;
var rl = readline.createInterface({
input: fs.createReadStream(file),
output: process.stdout,
terminal: false
});
rl.on('line', function (line) {
linesCount++; // 在每个换行符上,将+1添加到“linesCount”
});
rl.on('close', function () {
console.log(linesCount); // 当“close”事件被调用时打印结果
});

逐行读取文件

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
const readline = require('readline');
const fs = require('fs');
var file = 'path.to.file';
var rl = readline.createInterface({
input: fs.createReadStream(file),
output: process.stdout,
terminal: false
});
rl.on('line', function (line) {
console.log(line) // 在每个换行符上打印行的内容
});

创建或使用现有目录时避免竞争条件

由于Node的异步性质,首先要创建或使用目录:

  1. fs.stat()检查它的存在,然后

  2. 根据存在检查的结果创建或使用它,

如果在检查时间和创建时间之间创建文件夹,则可能导致竞争条件。 下面的方法将fs.mkdir()fs.mkdirSync()包含在错误捕获的包装器中,如果代码为EEXIST(已经存在),则异常通过。 如果错误是别的东西,像EPERM(pemission被拒绝),抛出或传递一个像本机功能那样的错误。

fs.mkdir()的异步版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var fs = require('fs');
function mkdir (dirPath, callback) {
fs.mkdir(dirPath, (err) => {
callback(err && err.code !== 'EEXIST' ? err : null);
});
}
mkdir('./existingDir', (err) => {
if (err)
return console.error(err.code);
// Do something with `./existingDir` here
});

同步版本fs.mkdirSync()

1
2
3
4
5
6
7
8
9
10
function mkdirSync (dirPath) {
try {
fs.mkdirSync(dirPath);
} catch(e) {
if ( e.code !== 'EEXIST' ) throw e;
}
}
mkdirSync('./existing-dir');
// Do something with `./existing-dir` now

更改文本文件的内容

例。 它将使用简单的RegExp replace(/email/gim, 'name')将文本email替换为文本文件index.txt中的name

1
2
3
4
5
6
7
8
9
10
11
12
var fs = require('fs');
fs.readFile('index.txt', 'utf-8', function(err, data) {
if (err) throw err;
var newValue = data.replace(/email/gim, 'name');
fs.writeFile('index.txt', newValue, 'utf-8', function(err, data) {
if (err) throw err;
console.log('Done!');
})
})

使用流克隆文件

该程序说明了如何使用可读写的流使用createReadStream()和由文件系统模块提供的createWriteStream()函数来复制文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Require the file System module
var fs = require('fs');
/*
Create readable stream to file in current directory (__dirname) named 'node.txt'
Use utf8 encoding
Read the data in 16-kilobyte chunks
*/
var readable = fs.createReadStream(__dirname + '/node.txt', { encoding: 'utf8', highWaterMark: 16 * 1024 });
// create writable stream
var writable = fs.createWriteStream(__dirname + '/nodeCopy.txt');
// Write each chunk of data to the writable stream
readable.on('data', function(chunk) {
writable.write(chunk);
});

使用unlink或unlinkSync删除文件

异步删除文件:

1
2
3
4
5
6
7
var fs = require('fs');
fs.unlink('/path/to/file.txt', function(err) {
if (err) throw err;
console.log('file deleted');
});

您也可以同步删除*:

1
2
3
4
var fs = require('fs');
fs.unlinkSync('/path/to/file.txt');
console.log('file deleted');

*避免同步方法,因为它们阻止整个进程,直到执行完成。

使用流将文件读入Buffer

在使用fs.readFile()方法从文件读取内容已经是异步的时候,有时我们想要在一个Stream中获取数据,而不是简单的回调。 这样我们可以将这些数据管理到其他位置,或者将其处理成最后一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const fs = require('fs');
// 使用数组存储文件数据块
let chunks = [];
// 使用这个变量来存储最终的数据
let fileBuffer;
// 将文件读入stream.Readable
let fileStream = fs.createReadStream('text.txt');
// An error occurred with the stream
fileStream.once('error', (err) => {
// Be sure to handle this properly!
console.error(err);
});
// 文件完成读取
fileStream.once('end', () => {
// 从数据块中创建最终数据Buffer;
fileBuffer = Buffer.concat(chunks);
// Of course, you can do anything else you need to here, like emit an event!
});
// Data is flushed from fileStream in chunks,
// this callback will be executed for each chunk
fileStream.on('data', (chunk) => {
chunks.push(chunk); // push data chunk to array
// We can perform actions on the partial data we have so far!
});

使用writeFile或writeFileSync写入文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var fs = require('fs');
// Save the string "Hello world!" in a file called "hello.txt" in
// the directory "/tmp" using the default encoding (utf8).
// This operation will be completed in background and the callback
// will be called when it is either done or failed.
fs.writeFile('/tmp/hello.txt', 'Hello world!', function(err) {
// If an error occurred, show it and return
if(err) return console.error(err);
// Successfully wrote to the file!
});
// Save binary data to a file called "binary.txt" in the current
// directory. Again, the operation will be completed in background.
var buffer = new Buffer([ 0x48, 0x65, 0x6c, 0x6c, 0x6f ]);
fs.writeFile('binary.txt', buffer, function(err) {
// If an error occurred, show it and return
if(err) return console.error(err);
// Successfully wrote binary contents to the file!
});

fs.writeFileSync的行为类似于fs.writeFile,但是它不会在同步完成时执行回调,因此阻止主线程。 大多数node.js开发人员喜欢异步变量,这将在程序执行中几乎没有任何延迟。

注意:在node.js中阻塞主线程是不好的做法。 同步功能只能在调试时使用或没有其他选项可用。

在Node.js中,资源密集型操作(例如I / O)异步执行,但具有同步对应(例如存在fs.readFile,其对应方是fs.readFileSync)。 由于Node是单线程的,所以在使用同步操作时应该小心,因为它们会阻塞整个进程。

如果进程被同步操作阻塞,那么整个执行周期(包括事件循环)将被停止。 这意味着其他异步代码(包括事件和事件处理程序)将不会运行,您的程序将继续等到单个阻止操作完成。

同步和异步操作都有适当的用途,但必须注意使用它们。