文章

Node.js文件操作

Node.js文件操作

fs 文件系统模块

fs 模块介绍

Node.js 内置的 fs 模块就是文件系统模块,负责读写文件。
和所有其它 JavaScript 模块不同的是,fs 模块同时提供了异步和同步的方法。
什么是异步方法?
因为 JavaScript 的单线程模型,执行 IO 操作时,JavaScript 代码无需等待,而是传入回调函数后,继续执行后续 JavaScript 代码。比如 jQuery 提供的 getJSON() 操作:

1
2
3
4
$.getJSON('http://example.com/ajax', function (data) {
    console.log('IO结果返回后执行...');
});
console.log('不等待IO结果直接执行后续代码...');

而同步的 IO 操作则需要等待函数返回:

1
2
// 根据网络耗时,函数将执行几十毫秒~几秒不等:
var data = getJSONSync('http://example.com/ajax');

同步操作的好处是代码简单,缺点是程序将等待 IO 操作,在等待时间内,无法响应其它任何事件。而异步读取不用等待 IO 操作,但代码较麻烦。

异步和同步的选择

由于 Node 环境执行的 JavaScript 代码是服务器端代码,所以,绝大部分需要在服务器运行期反复执行业务逻辑的代码,必须使用异步代码,否则,同步代码在执行时期,服务器将停止响应,因为 JavaScript 只有一个执行线程。
服务器启动时如果需要读取配置文件,或者结束时需要写入到状态文件时,可以使用同步代码,因为这些代码只在启动和结束时执行一次,不影响服务器正常运行时的异步执行。

打开文件 open

语法:

fs.open(path, flags[, mode], callback)

参数:

  • path - 文件的路径。
  • flags - 文件打开的行为。
  • mode - 设置文件模式 (权限),文件创建默认权限为 0666(可读,可写)。
  • callback - 回调函数,带有两个参数如:callback(err, fd)。

flags 参数可以是以下值:

Flag描述
r以读取模式打开文件。如果文件不存在抛出异常。
r+以读写模式打开文件。如果文件不存在抛出异常。
rs以同步的方式读取文件。
rs+以同步的方式读取和写入文件。
w以写入模式打开文件,如果文件不存在则创建。
wx类似 ‘w’,但是如果文件路径存在,则文件写入失败。
w+以读写模式打开文件,如果文件不存在则创建。
wx+类似 ‘w+’, 但是如果文件路径存在,则文件读写失败。
a以追加模式打开文件,如果文件不存在则创建。
ax类似 ‘a’, 但是如果文件路径存在,则文件追加失败。
a+以读取追加模式打开文件,如果文件不存在则创建。
ax+类似 ‘a+’, 但是如果文件路径存在,则文件读取追加失败。

示例:创建 file.js 文件,并打开 input.txt 文件进行读写

1
2
3
4
5
6
7
8
9
10
var fs = require("fs");

// 异步打开文件
console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
   if (err) {
       return console.error(err);
   }
  console.log("文件打开成功!");     
});

获取文件信息 stat

stat 同步

语法
以下为通过异步模式获取文件信息的语法格式:

fs.stat(path, callback)

参数
参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,带有两个参数如:(err, stats), stats 是 fs.Stats 对象。

fs.stat(path) 执行后,会将 stats 类的实例返回给其回调函数。可以通过 stats 类中的提供方法判断文件的相关属性。例如判断是否为文件:

1
2
3
4
5
var fs = require('fs');

fs.stat('/Users/hacket/code/demo/fs.js', function (err, stats) {
    console.log(stats.isFile());         //true
})

stats 类中的方法有:

方法描述
stats.isFile()如果是文件返回 true,否则返回 false。
stats.isDirectory()如果是目录返回 true,否则返回 false。
stats.isBlockDevice()如果是块设备返回 true,否则返回 false。
stats.isCharacterDevice()如果是字符设备返回 true,否则返回 false。
stats.isSymbolicLink()如果是软链接返回 true,否则返回 false。
stats.isFIFO()如果是 FIFO,返回 true,否则返回 false。FIFO 是 UNIX 中的一种特殊类型的命令管道。
stats.isSocket()如果是 Socket 返回 true,否则返回 false。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var fs = require("fs");

console.log("准备打开文件!");
fs.stat('input.txt', function (err, stats) {
   if (err) {
       return console.error(err);
   }
   console.log(stats);
   console.log("读取文件信息成功!");
   
   // 检测文件类型
   console.log("是否为文件(isFile) ? " + stats.isFile());
   console.log("是否为目录(isDirectory) ? " + stats.isDirectory());    
});

statSync 异步

读文件

read

fs.read(fd, buffer, offset, length, position, callback)

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • buffer - 数据写入的缓冲区。
  • offset - 缓冲区写入的写入偏移量。
  • length - 要从文件中读取的字节数。
  • position - 文件读取的起始位置,如果 position 的值为 null,则会从当前文件指针的位置读取。
  • callback - 回调函数,有三个参数 err, bytesRead, buffer,err 为错误信息, bytesRead 表示读取的字节数,buffer 为缓冲区对象。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var fs = require("fs");
var buf = new Buffer.alloc(1024);

console.log("准备打开已存在的文件!");
fs.open('sample.txt', 'r+', function (err, fd) {
    if (err) {
        return console.error(err);
    }
    console.log("文件打开成功!");
    console.log("准备读取文件:");
    fs.read(fd, buf, 0, buf.length, 0, function (err, bytes) {
        if (err) {
            console.log(err);
        }
        console.log(bytes + "  字节被读取");

        // 仅输出读取的字节
        if (bytes > 0) {
            console.log(buf.slice(0, bytes).toString());
        }
    });
});

readFile 异步读文件

readFile()

  • fd: 读取文件存放的路径
  • buffer: 读取文件时采用的编码格式
  • 参数 3:回调函数,拿到失败和成功的结果

示例 1:读取文本

1
2
3
4
5
6
7
8
9
10
11
12
'use strict';

var fs = require('fs');

// 异步读取文件
fs.readFile('sample.txt', 'utf-8', function (err, data) {
    if (err) {
        console.log(err);
    } else {
        console.log(data); // Hello, Node.js
    }
});

异步读取时,传入的回调函数接收两个参数:

  • err:当正常读取时,err 参数为 null,data 参数为读取到的 String
  • data:当读取发生错误时,err 参数代表一个错误对象,data 为 undefined

这也是 Node.js 标准的回调函数:第一个参数代表错误信息,第二个参数代表结果。后面我们还会经常编写这种回调函数。

sample.txt 文件必须在当前目录下,且文件编码为 utf-8

由于 err 是否为 null 就是判断是否出错的标志,所以通常的判断逻辑总是:

1
2
3
4
5
if (err) {
    // 出错了
} else {
    // 正常
}

示例 2:非文本读取,读取 png

1
2
3
4
5
6
7
8
9
10
11
12
'use strict';

var fs = require('fs');

fs.readFile('sample.png', function (err, data) {
    if (err) {
        console.log(err);
    } else {
        console.log(data);
        console.log(data.length + ' bytes');
    }
});

当读取二进制文件时,不传入文件编码时,回调函数的 data 参数将返回一个 Buffer 对象。在 Node.js 中,Buffer 对象就是一个包含零个或任意个字节的数组(注意和 Array 不同)。
Buffer 对象可以和 String 作转换,例如,把一个 Buffer 对象转换成 String:

1
2
3
// Buffer -> String
var text = data.toString('utf-8');
console.log(text);

或者把一个 String 转换成 Buffer:

1
2
3
// String -> Buffer
var buf = Buffer.from(text, 'utf-8');
console.log(buf);

readFileSync 同步读文件

除了标准的异步读取模式外,fs 也提供相应的同步读取函数。同步读取的函数和异步函数相比,多了一个 Sync 后缀,即 readFileSync,并且不接收回调函数,函数直接返回结果。

1
2
3
4
5
6
'use strict';

var fs = require('fs');

var data = fs.readFileSync('sample.txt', 'utf-8');
console.log(data);

原异步调用的回调函数的 data 被函数直接返回,函数名需要改为 readFileSync,其它参数不变。
如果同步读取文件发生错误,则需要用 try…catch 捕获该错误:

1
2
3
4
5
6
try {
    var data = fs.readFileSync('sample.txt', 'utf-8');
    console.log(data);
} catch (err) {
    // 出错了
}

写文件

writeFile() 异步写文件

fs.writeFile(file, data[, options], callback)

  • file:必选,写入的文件名或文件描述符
  • data:必选,要写入文件的数据,可以是 String(字符串) 或 Buffer(缓冲) 对象。
  • options:可选,该参数是一个对象,包含 {encoding, mode, flag}。默认编码为 utf8, 模式为 0666 , flag 为 ‘w’;如果是 String,默认以 utf-8 写入
  • callback:必选, 回调函数,回调函数只包含错误信息参数 (err),在写入失败时返回。

writeFile 直接打开文件默认是 w 模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'use strict';

var fs = require("fs");

console.log("准备写入文件");
fs.writeFile('input.txt', '我是通 过fs.writeFile 写入文件的内容',  function(err) {
   if (err) {
       return console.error(err);
   }
   console.log("数据写入成功!");
   console.log("--------我是分割线-------------")
   console.log("读取写入的数据!");
   fs.readFile('input.txt', function (err, data) {
      if (err) {
         return console.error(err);
      }
      console.log("异步读取文件数据: " + data.toString());
   });
});

writeFileSync 同步写文件

1
2
3
4
5
6
'use strict';

var fs = require('fs');

var data = 'Hello, Node.js';
fs.writeFileSync('output.txt', data);

关闭文件 close

语法:
fs.close(fd, callback)
参数:

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • callback - 回调函数,没有参数。

示例:

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
var fs = require("fs");
var buf = new Buffer.alloc(1024);

console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
   if (err) {
       return console.error(err);
   }
   console.log("文件打开成功!");
   console.log("准备读取文件!");
   fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
      if (err){
         console.log(err);
      }

      // 仅输出读取的字节
      if(bytes > 0){
         console.log(buf.slice(0, bytes).toString());
      }

      // 关闭文件
      fs.close(fd, function(err){
         if (err){
            console.log(err);
         } 
         console.log("文件关闭成功");
      });
   });
});

截取文件 ftruncate

语法:
以下为异步模式下截取文件的语法格式:
fs.ftruncate(fd, len, callback)
参数:
参数使用说明如下:

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • len - 文件内容截取的长度。
  • callback - 回调函数,没有参数。

示例:

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
32
33
34
35
36
37
38
var fs = require("fs");
var buf = new Buffer.alloc(1024);

console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
   if (err) {
       return console.error(err);
   }
   console.log("文件打开成功!");
   console.log("截取10字节内的文件内容,超出部分将被去除。");
   
   // 截取文件
   fs.ftruncate(fd, 10, function(err){
      if (err){
         console.log(err);
      } 
      console.log("文件截取成功。");
      console.log("读取相同的文件"); 
      fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
         if (err){
            console.log(err);
         }

         // 仅输出读取的字节
         if(bytes > 0){
            console.log(buf.slice(0, bytes).toString());
         }

         // 关闭文件
         fs.close(fd, function(err){
            if (err){
               console.log(err);
            } 
            console.log("文件关闭成功!");
         });
      });
   });
});

语法:
fs.unlink(path, callback)
参数:
参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,没有参数。

示例:

1
2
3
4
5
6
7
8
9
var fs = require("fs");

console.log("准备删除文件!");
fs.unlink('input.txt', function(err) {
   if (err) {
       return console.error(err);
   }
   console.log("文件删除成功!");
});

文件夹操作

在 Node.js 中可以通过如下 API 对文件夹进行创建、读取、删除等操作

  • mkdir / mkdirSync 创建文件夹
  • readdir / readdirSync 读取文件夹
  • rmdir / rmdirSync 删除文件夹

创建目录 mkdir/mkdirSync

mkdir 异步

语法:
fs.mkdir(path[, options], callback)
参数:

  • path - 文件路径。
  • options 参数可以是:
    • recursive - 是否以递归的方式创建目录,默认为 false。
    • mode - 设置目录权限,默认为 0777。
  • callback - 回调函数,没有参数。

示例:

1
2
3
4
5
6
7
8
9
var fs = require("fs");
// tmp 目录必须存在
console.log("创建目录 /tmp/test/");
fs.mkdir("/tmp/test/",function(err){
   if (err) {
       return console.error(err);
   }
   console.log("目录创建成功。");
});

可以添加 recursive: true 参数,不管创建的目录 /tmp 和 /tmp/a 是否存在:

1
2
3
fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
  if (err) throw err;
});

mkdirSync 同步

fs.mkdirSync(path, [options])

读取目录 readdir

语法:
fs.readdir(path, callback)
参数:

  • path - 文件路径。
  • callback - 回调函数,回调函数带有两个参数 err, files,err 为错误信息,files 为 目录下的文件数组列表。

示例:

1
2
3
4
5
6
7
8
9
10
11
var fs = require("fs");

console.log("查看 /tmp 目录");
fs.readdir("/tmp/",function(err, files){
   if (err) {
       return console.error(err);
   }
   files.forEach( function (file){
       console.log( file );
   });
});

删除目录 rmdir

语法:
fs.rmdir(path, callback)
参数:

  • path - 文件路径。
  • callback - 回调函数,没有参数。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var fs = require("fs");
// 执行前创建一个空的 /tmp/test 目录
console.log("准备删除目录 /tmp/test");
fs.rmdir("/tmp/test",function(err){
   if (err) {
       return console.error(err);
   }
   console.log("读取 /tmp 目录");
   fs.readdir("/tmp/",function(err, files){
      if (err) {
          return console.error(err);
      }
      files.forEach( function (file){
          console.log( file );
      });
   });
});

注意

fs 相对路径目录问题

原因:fs 模块读取文件的相对路径是以启动 xxx.js 的位置为基准的,而不是以 xxx.js 文件的位置
示例:

1
2
3
4
5
6
7
8
9
10
11
12
# /js/foo/a.txt
hello World
# /js/foo/index.js
const fs = require('fs');

fs.readFile('./a.txt',(err,data) => {
  if (err) {
    console.log('error');
  } else {
    console.log(data.toString());
  }
})

在/js 目录下执行 node foo/index.js,会出现文件找不到的情况,原因:/js/foo/index.js 文件中读文件是写的相对路径也即:./a.txt,而这个相对路径实际上是相对于执行 node 命令所处的路径,也即以上的执行 node 时,进行文件操作时查找的路径是:js/a.txt 显然/js 目录下没有该文件,也就查找失败。

解决 1:使用绝对路径
解决 2:使用 __dirname 拼接,使用 __dirname+'/a.txt' 来相对当前路径进行定位。
全局变量 __dirname 保存着当前文件所在目录的绝对路径

1
2
3
4
5
6
7
8
9
const fs = require('fs');

fs.readFile(__dirname + '/a.txt', 'utf-8', (err, data) => {
    if (err) {
        console.log('error');
    } else {
        console.log(data.toString());
    }
})

解决 3:使用 path+__dirname 拼接,由于 __dirname 不受 node 命令所属路径影响,同时又可以动态的获取当前文件的绝对路径,因此可以是个不错的选择:

1
2
3
4
5
6
7
8
9
10
11
const fs = require('fs');
const path = require('path');

// 采用path.join()对于拼接的路径自动进行修复,避免不必要的失误操作造成的文件访问不到的问题
fs.readFile(path.join(__dirname + './a.txt'),(err,data) => {
  if (err) {
    console.log('error');
  } else {
    console.log(data.toString());
  }
})

__dirname 是什么呢?
在每个模块中,除了 requireexports 等模块相关 API 之外,还有两个特殊的成员:

  • __dirname 获取当前文件所处目录(绝对路径)
  • _filename 获取当前文件所处目录,包括当前文件(绝对路径)
  • __dirname__filename 是不受执行 node 命令所属路径影响的

require() 中的路径问题

模块中 require 中所写的路径跟文件操作的路径是没有关系的,其路径是相对于文件模块的,也即相对于当前文件模块(文件)所处目录的相对路径。

1
2
3
4
5
# /js/other.js
require('./foo/index.js');

# /js/foo/index.js
console.log('1');

查找 ./foo/index.js 就是相对于/js 目录

Node.js fs 模块相同的方法列表

fs 完整见:https://nodejs.org/api/fs.html

序号方法 & 描述
1fs.rename(oldPath, newPath, callback)
异步 rename().回调函数没有参数,但可能抛出异常。
2fs.ftruncate(fd, len, callback)
异步 ftruncate().回调函数没有参数,但可能抛出异常。
3fs.ftruncateSync(fd, len)
同步 ftruncate()
4fs.truncate(path, len, callback)
异步 truncate().回调函数没有参数,但可能抛出异常。
5fs.truncateSync(path, len)
同步 truncate()
6fs.chown(path, uid, gid, callback)
异步 chown().回调函数没有参数,但可能抛出异常。
7fs.chownSync(path, uid, gid)
同步 chown()
8fs.fchown(fd, uid, gid, callback)
异步 fchown().回调函数没有参数,但可能抛出异常。
9fs.fchownSync(fd, uid, gid)
同步 fchown()
10fs.lchown(path, uid, gid, callback)
异步 lchown().回调函数没有参数,但可能抛出异常。
11fs.lchownSync(path, uid, gid)
同步 lchown()
12fs.chmod(path, mode, callback)
异步 chmod().回调函数没有参数,但可能抛出异常。
13fs.chmodSync(path, mode)
同步 chmod().
14fs.fchmod(fd, mode, callback)
异步 fchmod().回调函数没有参数,但可能抛出异常。
15fs.fchmodSync(fd, mode)
同步 fchmod().
16fs.lchmod(path, mode, callback)
异步 lchmod().回调函数没有参数,但可能抛出异常。Only available on Mac OS X.
17fs.lchmodSync(path, mode)
同步 lchmod().
18fs.stat(path, callback)
异步 stat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
19fs.lstat(path, callback)
异步 lstat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
20fs.fstat(fd, callback)
异步 fstat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
21fs.statSync(path)
同步 stat(). 返回 fs.Stats 的实例。
22fs.lstatSync(path)
同步 lstat(). 返回 fs.Stats 的实例。
23fs.fstatSync(fd)
同步 fstat(). 返回 fs.Stats 的实例。
24fs.link(srcpath, dstpath, callback)
异步 link().回调函数没有参数,但可能抛出异常。
25fs.linkSync(srcpath, dstpath)
同步 link().
26fs.symlink(srcpath, dstpath[, type], callback)
异步 symlink().回调函数没有参数,但可能抛出异常。 type 参数可以设置为 ‘dir’, ‘file’, 或 ‘junction’ (默认为 ‘file’) 。
27fs.symlinkSync(srcpath, dstpath[, type])
同步 symlink().
28fs.readlink(path, callback)
异步 readlink(). 回调函数有两个参数 err, linkString。
29fs.realpath(path[, cache], callback)
异步 realpath(). 回调函数有两个参数 err, resolvedPath。
30fs.realpathSync(path[, cache])
同步 realpath()。返回绝对路径。
31fs.unlink(path, callback)
异步 unlink().回调函数没有参数,但可能抛出异常。
32fs.unlinkSync(path)
同步 unlink().
33fs.rmdir(path, callback)
异步 rmdir().回调函数没有参数,但可能抛出异常。
34fs.rmdirSync(path)
同步 rmdir().
35fs.mkdir(path[, mode], callback)
S 异步 mkdir(2).回调函数没有参数,但可能抛出异常。 访问权限默认为 0777。
36fs.mkdirSync(path[, mode])
同步 mkdir().
37fs.readdir(path, callback)
异步 readdir(3). 读取目录的内容。
38fs.readdirSync(path)
同步 readdir().返回文件数组列表。
39fs.close(fd, callback)
异步 close().回调函数没有参数,但可能抛出异常。
40fs.closeSync(fd)
同步 close().
41fs.open(path, flags[, mode], callback)
异步打开文件。
42fs.openSync(path, flags[, mode])
同步 version of fs.open().
43fs.utimes(path, atime, mtime, callback)
44fs.utimesSync(path, atime, mtime)
修改文件时间戳,文件通过指定的文件路径。
45fs.futimes(fd, atime, mtime, callback)
46fs.futimesSync(fd, atime, mtime)
修改文件时间戳,通过文件描述符指定。
47fs.fsync(fd, callback)
异步 fsync.回调函数没有参数,但可能抛出异常。
48fs.fsyncSync(fd)
同步 fsync.
49fs.write(fd, buffer, offset, length[, position], callback)
将缓冲区内容写入到通过文件描述符指定的文件。
50fs.write(fd, data[, position[, encoding]], callback)
通过文件描述符 fd 写入文件内容。
51fs.writeSync(fd, buffer, offset, length[, position])
同步版的 fs.write()。
52fs.writeSync(fd, data[, position[, encoding]])
同步版的 fs.write().
53fs.read(fd, buffer, offset, length, position, callback)
通过文件描述符 fd 读取文件内容。
54fs.readSync(fd, buffer, offset, length, position)
同步版的 fs.read.
55fs.readFile(filename[, options], callback)
异步读取文件内容。
56fs.readFileSync(filename[, options])
57fs.writeFile(filename, data[, options], callback)
异步写入文件内容。
58fs.writeFileSync(filename, data[, options])
同步版的 fs.writeFile。
59fs.appendFile(filename, data[, options], callback)
异步追加文件内容。
60fs.appendFileSync(filename, data[, options])
The 同步 version of fs.appendFile.
61fs.watchFile(filename[, options], listener)
查看文件的修改。
62fs.unwatchFile(filename[, listener])
停止查看 filename 的修改。
63fs.watch(filename[, options][, listener])
查看 filename 的修改,filename 可以是文件或目录。返回 fs.FSWatcher 对象。
64fs.exists(path, callback)
检测给定的路径是否存在。
65fs.existsSync(path)
同步版的 fs.exists.
66fs.access(path[, mode], callback)
测试指定路径用户权限。
67fs.accessSync(path[, mode])
同步版的 fs.access。
68fs.createReadStream(path[, options])
返回 ReadStream 对象。
69fs.createWriteStream(path[, options])
返回 WriteStream 对象。
70fs.symlink(srcpath, dstpath[, type], callback)
异步 symlink().回调函数没有参数,但可能抛出异常。

Ref

本文由作者按照 CC BY 4.0 进行授权