~ 文件系统 – 异步和底层操作

异步

异步形式始终以完成回调作为它最后一个参数。 传给完成回调的参数取决于具体方法,但第一个参数总是留给异常。 如果操作成功完成,则第一个参数会是 null 或 undefined。

var fs = require('fs');
fs.unlink('/tmp/hello', function(err){
  if (err) throw err;
  console.log('successfully deleted /tmp/hello');
});

当使用同步形式时,任何异常都会被立即抛出。 可以使用 try/catch 来处理异常,或让它们往上冒泡

//同步示例
var fs = require('fs');
fs.unlinkSync('/tmp/hello');
console.log('successfully deleted /tmp/hello');

异步方法不保证执行顺序。 所以下面的例子容易出错

fs.rename('/tmp/hello', '/tmp/world', function(err){
  if (err) throw err;
  console.log('renamed complete');
});
fs.stat('/tmp/world', function(err, stats){
  if (err) throw err;
  console.log('stats: ${JSON.stringify(stats)}');
});

fs.stat 可能在 fs.rename 之前执行。正确的方法是把回调链起来

fs.rename('/tmp/hello', '/tmp/world', function(err){
  if (err) throw err;
  fs.stat('/tmp/world', function(err, stats){
    if (err) throw err;
    console.log('stats: ${JSON.stringify(stats)}');
  });
});

推荐开发者使用这些函数的异步版本。同步版本会阻塞整个进程,直到它们完成(停止所有连接)。


底层操作

[ 打开文件 ] fs.open(path, flags[, mode], callback)

path <String> | <Buffer>
flags <String> | <Number>
mode <Integer> 设置文件模式(权限和 sticky 位),但只有当文件被创建时才有效。默认为 0666,可读写
callback <Function> 该回调有两个参数 (err错误, fd文件标识,与定时器标识类似)

flags可以是:

'r' - 以读取模式打开文件。如果文件不存在则发生异常。
'r+' - 以读写模式打开文件。如果文件不存在则发生异常。
'rs+' - 以同步读写模式打开文件。命令操作系统绕过本地文件系统缓存。
'w' - 以写入模式打开文件。文件会被创建(如果文件不存在)或截断(如果文件存在)。
'wx' - 类似 'w',但如果 path 存在,则失败。
'w+' - 以读写模式打开文件。文件会被创建(如果文件不存在)或截断(如果文件存在)。
'wx+' - 类似 'w+',但如果 path 存在,则失败。
'a' - 以追加模式打开文件。如果文件不存在,则会被创建。
'ax' - 类似于 'a',但如果 path 存在,则失败。
'a+' - 以读取和追加模式打开文件。如果文件不存在,则会被创建。
'ax+' - 类似于 'a+',但如果 path 存在,则失败。

[注意] 使用'rs+'模式不会使fs.open()进入同步阻塞调用。如果那是你想要的,则应该使用fs.openSync()

var fs = require('fs');
fs.open('a.txt','r',function(err,fs){
    console.log(err);//null
    console.log(fs);//3
})
var fs = require('fs');
fs.open('b.txt','r',function(err,fs){
/*
{ Error: ENOENT: no such file or directory, open 'D:\project\b.txt'
    at Error (native)
  errno: -4058,
  code: 'ENOENT',
  syscall: 'open',
  path: 'D:\\project\\b.txt' }
 */
    console.log(err);
    console.log(fs);//undefined
})

文件的回调函数中的第二个参数fd代表文件标识,与定时器标识类似,用于标识文件,且随着文件的打开顺序递增

var fs = require('fs');
fs.open('1.txt','r',function(err,fs){
    console.log(fs);//3
})
fs.open('2.txt','r',function(err,fs){
    console.log(fs);//4
})

[ 读取文件 ] fs.read(fd, buffer, offset, length, position, callback)

fd <Integer> 通过 fs.open() 方法返回的文件描述符
buffer <String> | <Buffer> 数据将被写入到buffer
offset <Integer> buffer中开始写入的偏移量
length <Integer> 指定要读取的字节数(整数)
position <Integer> 指定从文件中开始读取的位置(整数)。 如果position为null,则数据从当前文件位置开始读取
callback <Function> 回调有三个参数 (err, bytesRead, buffer)。err为错误信息,bytesRead表示读取的字节数,buffer为缓冲区对象

由于使用read()方法,会将文件内容读取buffer对象中,所以需要提前先准备一个buffer对象

var fs = require('fs');
fs.open('1.txt','r',function(err,fd){
    if(err){
        console.log('文件打开失败');
    }else{
        var bf = Buffer.alloc(5);
        fs.read(fd,bf,0,3,null,function(err,len,buffer){
            console.log(err);//null
            console.log(len);//3
            console.log(buffer);//<Buffer 61 61 61 00 00>
        })
    }
});

[ 写入文件 ]

  • fs.write(fd, buffer, offset, length[, position], callback)
fd <Integer>  文件标识
buffer <String> | <Buffer> 要将buffer中的数据写入到文件中
offset <Integer> buffer对象中要写入的数据的起始位置
length <Integer> length是一个整数,指定要写入的字节数
position <Integer> 指定从文件开始写入数据的位置的偏移量。 如果 typeof position !== 'number',则数据从当前位置写入
callback <Function> 回调有三个参数(err, written, buffer),其中written指定从buffer写入了多少字节

[注意]多次对同一文件使用fs.write且不等待回调,是不安全的。对于这种情况,强烈推荐使用 fs.createWriteStream

当我们要对打开的文件进行写操作的时候,打开文件的模式应该是读写模式

var fs = require('fs');
fs.open('1.txt','r+',function(err,fd){
    if(err){
        console.log('文件打开失败');
    }else{
        var bf = Buffer.from('test');
        fs.write(fd,bf,0,3,null,function(err,len,buffer){
            console.log(err);//null
            console.log(len);//3
            console.log(buffer);//<Buffer 74 65 73 74>
        })
    }
});
  • fs.write(fd, data[, position[, encoding]], callback)

该方法写入data到fd指定的文件。如果data不是一个Buffer实例,则该值将被强制转换为一个字符串。

不同于写入 buffer,该方法整个字符串必须被写入。不能指定子字符串,这是因为结果数据的字节偏移量可能与字符串的偏移量不同。

fd  <Integer> 文件标识
data <String> | <Buffer> 要将string或buffer中的数据写入到文件中
position <Integer> 指向从文件开始写入数据的位置的偏移量。 如果 typeof position !== 'number',则数据从当前位置写入
encoding <String> 期望的字符串编码
callback <Function> 回调有三个参数(err, written, str),其中written指定从str写入了多少字节
var fs = require('fs');
fs.open('1.txt','r+',function(err,fd){
    if(err){
        console.log('文件打开失败');
    }else{
        fs.write(fd,'12345',function(err,len,str){
            console.log(err);//null
            console.log(len);//5
            console.log(str);//<Buffer 74 65 73 74>
        })
    }
});

[ 关闭文件 ] fs.close(fd, callback)              // 一个文件被操作后,要及时将该文件关闭

fd - 通过 fs.open() 方法返回的文件描述符。
callback - 回调函数,没有参数。
var fs = require('fs');
fs.open('1.txt','r+',function(err,fd){
    if(err){
        console.log('文件打开失败');
    }else{
        fs.close(fd, function(err){
            if (err){
                console.log(err);
            } 
            console.log("文件关闭成功");
        });
    }
});