文件操作
fs概述
在 NodeJS 中,所有与文件操作都是通过 fs
核心模块来实现的,包括文件目录的创建、删除、查询以及文件的读取和写入,在 fs
模块中,所有的方法都分为同步和异步两种实现,具有 sync
后缀的方法为同步方法,不具有 sync
后缀的方法为异步方法;
文件描述符fd
操作系统会为每个打开的文件分配一个名为文件描述符的数值标识,文件操作使用这些文件描述符来识别与追踪每个特定的文件,Window 系统使用了一个不同但概念类似的机制来追踪资源,为方便用户,NodeJS 抽象了不同操作系统间的差异,为所有打开的文件分配了数值的文件描述符。
在 NodeJS 中,每操作一个文件,文件描述符是递增的,文件描述符一般从 3
开始,因为前面有 0
、1
、2
三个比较特殊的描述符,分别代表 process.stdin
(标准输入)、process.stdout
(标准输出)和 process.stderr
(错误输出)
标识符flag
NodeJS 中,标识位代表着对文件的操作方式,如可读、可写、即可读又可写等等,在下面来表示文件操作的标识位和其对应的含义
r 读取文件,如果文件不存在则抛出异常。
r+ 读取并写入文件,如果文件不存在则抛出异常。
rs 读取并写入文件,指示操作系统绕开本地文件系统缓存。
w 写入文件,文件不存在会被创建,存在则清空后写入。
wx 写入文件,排它方式打开。
w+ 读取并写入文件,文件不存在则创建文件,存在则清空后写入。
wx+ 和 w+
类似,排他方式打开。
a 追加写入,文件不存在则创建文件。
ax 与 a
类似,排他方式打开。
a+ 读取并追加写入,不存在则创建。
ax+ 与 a+
类似,排他方式打开。
文件读取
同步读取文件readFileSync,有两个参数
第一个参数为读取文件的路径或文件描述符;
第二个参数为 options,默认值为 null,其中有 encoding(编码,默认为 null)和 flag(标识位,默认为 r),也可直接传入 encoding;
返回值为文件的内容,如果没有 encoding
,返回的文件内容为 Buffer,如果有按照传入的编码解析。
例:
const fs = require("fs");
let resultB = fs.readFileSync("./a.txt");
let resultD = fs.readFileSync("./a.txt", "utf8");
// 当读取的值为Buffer的时候,可以调用其toString()的方法,也可以将其转为正常的数据格式
// console.log(resultB.toString());
console.log(resultB); // <Buffer 68 65 6c 6c 6f 20 46 53>
console.log(resultD); // Hello FS
异步读取readFile, 有三个参数
异步读取方法与readFileSync
的前两个参数相同,最后一个参数为回调函数,函数内有两个参数 err
(错误)和 data
(数据),该方法没有返回值,回调函数在读取文件成功后执行,第二个参数也可以不传,通过toString()方法解析返回的数据
例:
const fs = require("fs");
fs.readFile("a.txt", "utf8", (err, data) => {
console.log(err); // null
console.log(data); // Hello FS
});
文件写入
同步写入writeFileSync,有三个参数
第一个参数为写入文件的路径或文件描述符;
第二个参数为写入的数据,类型为 String 或 Buffer;
第三个参数为 options
,默认值为 null
,其中有 encoding
(编码,默认为 utf8
)、 flag
(标识位,默认为 w
)和 mode
(权限位,默认为 0o666
),也可直接传入 encoding
例:
const fs = require("fs");
fs.writeFileSync("./txt.txt", "Hello FS 你好");
let result = fs.readFileSync("./txt.txt", "utf8");
console.log(result); // Hello FS 你好
异步写入writeFile, 有四个参数
异步写入方法 writeFile
与 writeFileSync
的前三个参数相同,最后一个参数为回调函数,函数内有一个参数 err
(错误),回调函数在文件写入数据成功后执行
例:
const fs = require("fs");
fs.writeFile("./txt.txt", "Hello FS 你好", err => {
if (!err) {
fs.readFile("./txt.txt", "utf8", (err, data) => {
console.log(data); // Hello FS 你好
});
}
});
文件追加写入
同步追加写入appendFileSync有三个参数
第一个参数为写入文件的路径或文件描述符;
第二个参数为写入的数据,类型为 String 或 Buffer;
第三个参数为 options
,默认值为 null
,其中有 encoding
(编码,默认为 utf8
)、 flag
(标识位,默认为 a
)和 mode
(权限位,默认为 0o666
),也可直接传入 encoding
例:
const fs = require("fs");
fs.appendFileSync("./txt.txt", "我是一名程序员");
let result = fs.readFileSync("./txt.txt", "utf8");
console.log(result); // Hello FS 你好我是一名程序员
异步追加写入appendFile,有四个参数
异步追加写入方法 appendFile
与 appendFileSync
的前三个参数相同,最后一个参数为回调函数,函数内有一个参数 err
(错误),回调函数在文件追加写入数据成功后执行
例:
const fs = require("fs");
fs.appendFile("./txt.txt", "2333", err => {
if (!err) {
fs.readFile("./txt.txt", "utf8", (err, data) => {
console.log(data); // Hello FS 你好我是一名程序员,2333
});
}
});
文件拷贝写入
同步拷贝写入copyFileSync,两个参数
同步拷贝写入方法 copyFileSync
有两个参数,第一个参数为被拷贝的源文件路径,第二个参数为拷贝到的目标文件路径,如果目标文件不存在,则会创建并拷贝
例:
const fs = require("fs");
fs.copyFileSync("./txt.txt", "a.txt");
let result = fs.readFileSync("a.txt", "utf8");
console.log(result); // Hello FS 你好我是一名程序员,2333
异步写入拷贝copyFile,两个参数
异步拷贝写入方法 copyFile
和 copyFileSync
前两个参数相同,最后一个参数为回调函数,在拷贝完成后执行。
例:
const fs = require("fs");
fs.copyFile("./txt.txt", "a.txt", () => {
fs.readFile("a.txt", "utf8", (err, data) => {
console.log(data); // Hello FS 你好我是一名程序员,2333
});
});
stream(流)
在Node.js中,流(stream)就是一系列从A点到B点移动的数据。完整点的说,就是当你有一个很大的数据需要传输、搬运时,你不需要等待所有数据都传输完成才开始下一步工作。实际上,巨型数据会被分割成小块(chunks)进行传输。所以,buffer的原始定义中所说的(“streams of binary data… in the context of… file system”)意思就是说二进制数据在文件系统中的传输。比如,将a.txt的文字存储到b.txt中
Buffer(缓冲区)介绍
JavaScript 语言没有用于读取或操作二进制数据流的机制。 Buffer
类是作为 Node.js API 的一部分引入的,用于在 TCP(面向连接的、可靠的、基于字节流的传输层通信协议) 流、文件系统操作、以及其他上下文中与八位字节流进行交互或者可以理解为处理二进制数据流;
Buffer.alloc
创建的缓冲区是被初始化过的,所有的项都用00填充
例:
let buf1 = Buffer.alloc(6);
//创建长度为6的缓冲区
console.log(buf1);
// <Buffer 00 00 00 00 00 00>
Buffer.allocUnsafe
创建的 Buffer 并没有经过初始化,在内存中只要有闲置的 Buffer 就直接 “抓过来” 使用
例:
let buf2 = Buffer.allocUnsafe(6);
//创建长度为6的缓冲区
console.log(buf2);
// <Buffer 00 e7 8f a0 00 00>
Buffer.allocUnsafe 创建 Buffer 使得内存的分配非常快,但已分配的内存段可能包含潜在的敏感数据,有明显性能优势的同时又是不安全的,所以使用需格外小心
Buffer.from
支持三种传参方式
第一个参数为字符串,第二个参数为字符编码,如 ASCII
、UTF-8
、Base64
等等。
传入一个数组,数组的每一项会以十六进制存储为 Buffer 的每一项。
传入一个 Buffer,会将 Buffer 的每一项作为新返回 Buffer 的每一项。
例:
// 传入字符串和字符编码
let buf = Buffer.from("hello", "utf8");
console.log(buf); // <Buffer 68 65 6c 6c 6f>
//传入数组
// 数组成员为十进制数
let buf = Buffer.from([1, 2, 3]);
console.log(buf); // <Buffer 01 02 03>
// 数组成员为十六进制数
let buf = Buffer.from([0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd]);
console.log(buf); // <Buffer e4 bd a0 e5 a5 bd>
console.log(buf.toString("utf8")); // 你好
在 NodeJS 中不支持 GB2312
编码,默认支持 UTF-8
,在 GB2312
中,一个汉字占两个字节,而在 UTF-8
中,一个汉字占三个字节,所以上面 “你好” 的 Buffer 为 6
个十六进制数组成。
例:
// 数组成员为字符串类型的数字
let buf = Buffer.from(["1", "2", "3"]);
console.log(buf); // <Buffer 01 02 03>
传入的数组成员可以是任何进制的数值,当成员为字符串的时候,如果值是数字会被自动识别成数值类型,如果值不是数字或成员为是其他非数值类型的数据,该成员会被初始化为 00
。
创建的 Buffer 可以通过 toString
方法直接指定编码进行转换,默认编码为 UTF-8
例:
// 传入一个 Buffer
let buf1 = Buffer.from("hello", "utf8");
let buf2 = Buffer.from(buf1);
console.log(buf1); // <Buffer 68 65 6c 6c 6f>
console.log(buf2); // <Buffer 68 65 6c 6c 6f>
console.log(buf1 === buf2); // true
console.log(buf1[0] === buf2[0]); // false
当传入的参数为一个 Buffer 的时候,会创建一个新的 Buffer 并复制上面的每一个成员。
Buffer 为引用类型,一个 Buffer 复制了另一个 Buffer 的成员,当其中一个 Buffer 复制的成员有更改,另一个 Buffer 对应的成员会跟着改变,因为指向同一个引用,类似于 “二维数组”。
例:
// Buffer 类比二维数组
let arr1 = [1, 2, [3]];
let arr2 = arr1.slice();
arr2[2][0] = 5;
console.log(arr1); // [1, 2, [5]]
打开文件,open,四个参数
path:文件的路径;
flag:标识位;
mode:权限位,默认 0o666
;
callback:回调函数,有两个参数 err
(错误)和 fd
(文件描述符),打开文件后执行
例:
const fs = require("fs");
fs.open("./txt.txt", "r", (err, fd) => {
console.log(fd);
});
关闭文件close
close
方法有两个参数,第一个参数为关闭文件的文件描述符 fd
,第二参数为回调函数,回调函数有一个参数 err
(错误),关闭文件后执行
例:
const fs = require("fs");
fs.open("./txt.txt", "r", (err, fd) => {
fs.close(fd, err => {
console.log("关闭成功");
});
});
读取文件read,有六个参数
read
方法与 readFile
不同,一般针对于文件太大,无法一次性读取全部内容到缓存中或文件大小未知的情况,都是多次读取到 Buffer 中
fd:文件描述符,需要先使用 open 打开;
buffer:要将内容读取到的 Buffer;
offset:整数,向 Buffer 写入的初始位置;
length:整数,读取文件的长度;
position:整数,读取文件初始位置;
callback:回调函数,有三个参数 err
(错误),bytesRead
(实际读取的字节数),buffer
(被写入的缓存区对象),读取执行完成后执行。
例:
const fs = require("fs");
let buf = Buffer.alloc(6);
// 打开文件
fs.open("./txt.txt", "r", (err, fd) => {
// 读取文件
fs.read(fd, buf, 0, 3, 0, (err, bytesRead, buffer) => {
console.log(bytesRead);
console.log(buffer);
// 继续读取
fs.read(fd, buf, 3, 3, 3, (err, bytesRead, buffer) => {
console.log(bytesRead);
console.log(buffer);
console.log(buffer.toString());
});
});
});
写入文件write,六个参数
fd:文件描述符,需要先使用 open
打开;
buffer:存储将要写入文件数据的 Buffer;
offset:整数,从 Buffer 读取数据的初始位置;
length:整数,读取 Buffer 数据的字节数;
position:整数,写入文件初始位置;
callback:回调函数,有三个参数 err
(错误),bytesWritten
(实际写入的字节数),buffer
(被读取的缓存区对象),写入完成后执行
例:
// 选择范围写入
const fs = require("fs");
let buf = Buffer.from("你还好吗");
// 打开文件
fs.open("./txt.txt", "r+", (err, fd) => {
// 读取 buf 向文件写入数据
fs.write(fd, buf, 3, 6, 3, (err, bytesWritten, buffer) => {
// 同步磁盘缓存
fs.fsync(fd, err => {
// 关闭文件
fs.close(fd, err => {
console.log("关闭文件");
});
});
});
});
// 这里为了看是否写入成功可直接使用 readFile 方法
fs.readFile("./txt.txt", "utf8", (err, data) => {
console.log(data);
});
获取文件目录stats
文件目录的 Stats
对象存储着关于这个文件或文件夹的一些重要信息,如创建时间、最后一次访问的时间、最后一次修改的时间、文章所占字节和判断文件类型的多个方法等等
同步获取stats方法statSync
statSync
方法参数为一个目录的路径,返回值为当前目录路径的 Stats
对象,现在通过 Stats
对象获取 a
目录下的 b
目录下的 c.txt
文件的字节大小,文件内容为 “你好”
例:
const fs = require("fs");
let statObj = fs.statSync("a/b/c.txt");
console.log(statObj.size); // 6
异步获取stats方法stat
stat
方法的第一个参数为目录的路径,最后一个参数为回调函数,回调函数有两个参数 err
(错误)和 Stats
对象,在读取 Stats
后执行,同样实现上面的读取文件字节数的例子。
例:
// 异步获取 Stats 对象
const fs = require("fs");
fs.stat("a/b/c.txt", (err, statObj) => {
console.log(statObj.size); // 6
});
创建文件目录
同步创建文件目录mkdirSync
mkdirSync
方法参数为一个目录的路径,没有返回值,在创建目录的过程中,必须保证传入的路径前面的文件目录都存在,否则会抛出异常。
例:
// 同步创建文件目录
const fs = require("fs");
// 假设已经有了 a 文件夹和 a 下的 b 文件夹
fs.mkdirSync("a/b/c");
异步创建文件目录mkdir
mkdir
方法的第一个参数为目录的路径,最后一个参数为回调函数,回调函数有一个参数 err
(错误),在执行创建操作后执行,同样需要路径前部分的文件夹都存在。
例:
// 异步创建文件目录
const fs = require("fs");
// 假设已经有了 a 文件夹和 a 下的 b 文件夹
fs.mkdir("a/b/c", err => {
if (!err) console.log("创建成功");
});
删除文件目录
同步删除文件目录rmdirSync
rmdirSync
方法参数为一个目录的路径,没有返回值,在创建目录的过程中,必须保证传入的路径前面的文件目录都存在,否则会抛出异常。
例:
// 同步删除文件目录
const fs = require("fs");
fs.rmdirSync("./ab");
异步删除文件目录rmdir
rmdir方法的第一个参数为目录的路径,最后一个参数为回调函数,回调函数有一个参数 err
(错误),在执行创建操作后执行,同样需要路径前部分的文件夹都存在。
例:
// 同步删除文件目录
const fs = require("fs");
fs.rmdir("./ab", err => {
if (!err) console.log("删除成功");
});
读取文件目录
同步读取文件目录readdirSync,有两个参数
第一个参数为目录的路径,传入的路径前部分的目录必须存在,否则会报错;
第二个参数为 options
,其中有 encoding
(编码,默认值为 utf8
),也可直接传入 encoding
;
返回值为一个存储文件目录中成员名称的数组。
例:
// 同步读取目录
const fs = require("fs");
let data = fs.readdirSync("a/b");
console.log(data); // [ 'c', 'index.js' ]
异步读取文件目录readdir,有两个参数
readdir
方法的前两个参数与 readdirSync
相同,第三个参数为一个回调函数,回调函数有两个参数 err
(错误)和 data
(存储文件目录中成员名称的数组),在读取文件目录后执行
例:
// 异步读取目录
const fs = require("fs");
fs.readdir("a/b", (err, data) => {
if (!err) console.log(data);
});
// [ 'c', 'index.js' ]
删除文件操作
同步删除文件操作unlinkSync
unlinkSync
的参数为要删除文件的路径,现在存在 a
目录和 a
目录下的 index.js
文件,删除 index.js
文件。
例:
// 同步删除文件
const fs = require("fs");
fs.unlinkSync("a/inde.js");
异步删除文件操作unlink
unlink
方法的第一个参数与 unlinkSync
相同,最后一个参数为回调函数,函数中存在一个参数 err
(错误),在删除文件操作后执行。
例:
// 异步删除文件
const fs = require("fs");
fs.unlink("a/index.js", err => {
if (!err) console.log("删除成功");
});
// 删除成功
检测文件或目录
同步检测existsSync
例:
const fs = require("fs");
let result = fs.existsSync('./a.js');
// true
异步检测exists
例:
const fs = require("fs");
let result = fs.existsSync('./a.js', function(flag) {
console.log(flag);
});
// true
__dirname
获取包含当前文件所在文件夹的一个绝对路径
例:
// 当前文件index.js
console.log(__dirname);
// /Users/shenjianbo/codes/practise/nodejs/20190624/foo/index.js.aa