入门nodeJS文件系统

文件操作

fs概述

在 NodeJS 中,所有与文件操作都是通过 fs 核心模块来实现的,包括文件目录的创建、删除、查询以及文件的读取和写入,在 fs 模块中,所有的方法都分为同步和异步两种实现,具有 sync 后缀的方法为同步方法,不具有 sync 后缀的方法为异步方法;

文件描述符fd

操作系统会为每个打开的文件分配一个名为文件描述符的数值标识,文件操作使用这些文件描述符来识别与追踪每个特定的文件,Window 系统使用了一个不同但概念类似的机制来追踪资源,为方便用户,NodeJS 抽象了不同操作系统间的差异,为所有打开的文件分配了数值的文件描述符。

在 NodeJS 中,每操作一个文件,文件描述符是递增的,文件描述符一般从 3 开始,因为前面有 012 三个比较特殊的描述符,分别代表 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, 有四个参数

异步写入方法 writeFilewriteFileSync 的前三个参数相同,最后一个参数为回调函数,函数内有一个参数 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,有四个参数

异步追加写入方法 appendFileappendFileSync 的前三个参数相同,最后一个参数为回调函数,函数内有一个参数 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,两个参数

异步拷贝写入方法 copyFilecopyFileSync 前两个参数相同,最后一个参数为回调函数,在拷贝完成后执行。

例:

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
支持三种传参方式

第一个参数为字符串,第二个参数为字符编码,如 ASCIIUTF-8Base64 等等。
传入一个数组,数组的每一项会以十六进制存储为 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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容