Node.js|Node.js 操作本地文件及深入了解 fs 内置模块
目录
- 前言
- 一、目录操作
- 创建目录
- 目录重命名
- 读取目录
- 删除目录
- 二、文件操作
- 创建文件
- 追加文件内容
- 读取文件内容
- 删除文件
- 三、 读取文件/目录信息
- 四、同步方法
- Sync同步方法
- 捕捉错误
- 删除不为空目录的案例
- 五、Promise方法
- 六、大文件操作
- stream流介绍
- 读取数据
- 写入数据
- 管道(文件复制)
- 七、补充
- 结语
前言
node.js
作为服务端应用,肯定少不了对本地文件的操作,像创建一个目录、创建一个文件、读取文件内容等都是我们开发中经常需要用到的功能这篇文章我们将深入学习
node
的内置模块:fs
文件操作模块,并使用它来操作本地文件,让我们开始吧!一、目录操作
创建目录
语法:
fs.mkdir(path[, options], callback)
参数:
path
- 文件路径options
配置对象,属性有:recursive
是否以递归的方式创建目录(创建嵌套目录),默认为false
mode
设置目录权限,默认为 0777( Windows 不支持)
callback
回调函数,参数如下:err
错误信息path
仅在options
配置recursive
为true
时出现,表示所创建的顶层目录的绝对路径
const fs = require("fs"); // 创建文件夹fs.mkdir("./blog", (err) => {if (err) {console.log(err); } else {console.log("创建blog目录成功!"); }});
文章图片
我们无法直接创建嵌套的目录:
文章图片
想要创建嵌套目录,需要配置
options
对象:// 创建嵌套文件夹fs.mkdir("./blog/one", { recursive: true }, (err, path) => {if (err) {console.log(err); } else {console.log("创建blog目录成功!"); console.log("path参数出现", path); }});
文章图片
目录重命名
语法:
fs.rename(oldPath, newPath, callback)
参数:
- oldPath 老的名字
- newPath 新的名字
- callback 回调函数,参数如下:
- err 错误信息
// 文件夹重命名fs.rename("./blog", "./newBlog", (err) => {if (err) {console.log(err); } else {console.log("重命名成功!"); }});
文章图片
读取目录
语法:
fs.readdir(path[, options], callback)
参数:
path
目录路径options
可以是指定编码格式的字符串,也可以是具有以下属性的对象encoding
:指定编码格式,默认值: ‘utf-8
’withFileTypes
files
数组是否包含
对象,默认值:false
callback
回调函数err
:错误信息files
:目录里的内容,数组格式
// 读取目录信息fs.readdir("./newBlog", (err, data) => {if (err) {console.log("err", err); } else {// 数组结构:包含目录下的所有文件名console.log("data", data); }});
文章图片
删除目录
语法:
fs.rmdir(path[, options], callback)
参数:
path
- 文件路径callback
回调函数,参数如下:err
错误信息
文章图片
注意: 在目录里面有内容时,是无法直接删除该目录的,需要提前将目录下的所有内容给删掉,删除文件会在下面讲到:
文章图片
二、文件操作
创建文件
使用
fs.writeFile
方法创建一个文件并写入内容,如果该文件本来就存在,则会替换文件原本的内容:// 创建文件并写入内容// 需要提前有blog目录fs.writeFile("./blog/one.txt", "hello", (err) => {if (err) {console.log(err); } else {console.log("文件创建成功!"); }});
创建文件:
文章图片
创建的内容:
文章图片
注意: 使用
writeFile
时,如要原本没有需要创建的这个文件,则writeFile
会新建这个文件并向其中写入指定内容,但如果原本有这个文件,则writeFile
会将原本的这个文件内容替换成我们指定的内容我们可以在循环中批量写入文件:
// 批量写入文件for (let i = 0; i < 10; i++) {fs.writeFile(`./blog/${i}.txt`, `blog-${i}`, (err) => {if (err) {console.log(err); } else {}}); }
文章图片
追加文件内容
使用
fs.appendFile
方法向一个文件内追加内容:// 给文件追加内容fs.appendFile("./blog/one.txt", "\nworld", (err) => {if (err) {console.log(err); } else {console.log("内容追加成功!"); }});
上面的代码将在blog
目录下的one.txt
中另起一行追加world
的内容,\n
表示换行,同时也支持其它的转义符号
文章图片
读取文件内容
使用
fs.readFile
方法读取文件内容:// 读取文件内容fs.readFile("./blog/one.txt", (err, data) => {if (err) {console.log(err); } else {console.log(data); }});
文章图片
默认读取的内容是
nodejs
的Buffer
数组格式,我们可以在获取数据时通过toString
将其转化成字符串:文章图片
也可以直接在读取文件时指定读取的编码格式:
文章图片
删除文件
使用
fs.unlink
方法删除文件:// 删除文件; fs.unlink("./blog/one.txt", (err) => {if (err) {console.log(err); } else {console.log("删除成功!"); }});
文章图片
三、 读取文件/目录信息 使用
fs.stat
可以用来获取指定路径的内容的详细信息,包括文件大小、创建时间等:// 读取文件/目录信息fs.stat("./blog/one.txt", (err, stats) => {if (err) {console.log(err); } else {console.log("stats", stats); }});
文章图片
回调函数中的
stats
参数会接收到文件的详细信息对象,其中各个属性表示的含义如下:Stats {// 包含文件的设备的数值型标识dev: 641331036,// 描述文件类型和模式的位域mode: 33206,// 文件的硬链接数量nlink: 1,// 文件拥有者的数值型用户标识uid: 0,// 拥有文件的群组的数值型群组标识gid: 0,// 如果文件表示设备,则为数字设备标识符rdev: 0,// i/o 操作的文件系统块大小blksize: 4096,// 文件的文件系统特定的“inode”号ino: 281474979034994,// 文件的字节大小size: 12,// 分配给文件的块的数量blocks: 0,// 指示上次访问此文件的时间戳atimeMs: 1661836850563.044,// 指示该文件最后一次修改的时间戳mtimeMs: 1661836850557.057,// 指示文件状态最后一次更改的时间戳ctimeMs: 1661836850557.057,// 指示此文件创建时间的时间戳birthtimeMs: 1661836842506.9233,// 表示文件最后一次被访问的时间atime: 2022-08-30T05:20:50.563Z,// 表示文件最后一次被修改的时间mtime: 2022-08-30T05:20:50.557Z,// 表示文件状态最后一次被改变的时间ctime: 2022-08-30T05:20:50.557Z,// 表示文件的创建时间birthtime: 2022-08-30T05:20:42.507Z}
stats
还有如下的常用方法:stats.isDirectory()
判断该内容是否是一个目录stats.isFile()
判断该文件是否是一个常规文件
四、同步方法 上面三节所讲的所有
fs
的方法都是异步的,如下图所示:文章图片
由于
Node
环境执行的JavaScript
代码是服务器端代码,所以绝大部分需要在服务器运行期反复执行业务逻辑的代码必须使用异步代码否则,同步代码在执行时期,服务器将停止响应,因为
JavaScript
只有一个执行线程Sync同步方法
但
node
也为我们提供了一些fs
的同步方法,我们只需在对应的方法名后加上Sync
即可使用它的同步方法:fs.mkdirSync("./blog");
注意:
fs
的同步方法没有callback
(回调函数)参数,需要获取的内容(如readdir
方法回调函数参数中的files
参数数据)会通过函数return
的形式返回出去大家可以自己动手试一下:在上面我们遇到过的更多fs
的方法名加上Sync
后缀使其变成同步方法。这里就不一一举例了
fs
同步的api
可见:nodejs官方文档文章图片
捕捉错误
需要注意的是:如果我们使用同步写法,一定要做好错误收集与处理!以防止服务端因同步方法报错而导致宕机:
文章图片
删除不为空目录的案例
在目录操作中,我们了解到在目录内容不为空时,我们是无法直接使用
rmdir
删除该目录的那么我们就需要先使用
unlink
将目录内的文件删除掉,这里我们将使用同步方法实现一个通用函数,来实现删除任何指定的目录,不管它内容为不为空:const fs = require("fs"); function rmdirPlus(path) {try {// 读取目录内容const dirData = https://www.it610.com/article/fs.readdirSync(path); // 遍历内容dirData.forEach((item) => {// 获取内容信息const stats = fs.statSync(`${path}/${item}`); if (stats.isDirectory()) {// 如果该内容为目录,则进行递归rmdirPlus(`${path}/${item}`); } else {// 如果该内容不为目录,直接删除fs.unlinkSync(`${path}/${item}`); }}); // 删除目录fs.rmdirSync(path); } catch (error) {console.log("执行错误!", error); }}
文章图片
在服务端,如果没有必要就尽量不要使用同步代码!非必要不使用
服务器启动时如果需要读取配置文件,或者结束时需要写入到状态文件时,可以使用同步代码,因为这些代码只在启动和结束时执行一次,不影响服务器正常运行时的异步执行
五、Promise方法 内置模块
fs
的所有异步方法都可以改写成promise
的写法,我们只需在引入fs
模块时指定promises
后缀:const fs = require("fs").promises;
之后使用
fs
的方法就可以直接使用promise
的写法了:const fs = require("fs").promisesfs.readFile('./blog/one.txt', 'utf-8').then(data => {console.log(data)})
让我们使用
promise
的写法改写一下上边删除不为空目录的案例:const fs = require("fs").promises; async function rmdirPlus(path) {try {// 读取目录内容const dirData = https://www.it610.com/article/await fs.readdir(path); await Promise.all(dirData.map(async (item) => {// 获取内容信息const stats = await fs.stat(`${path}/${item}`); if (stats.isDirectory()) {// 如果该内容为目录,则进行递归await rmdirPlus(`${path}/${item}`); } else {// 如果该内容不为目录,直接return出去return fs.unlink(`${path}/${item}`); }})); // 删除目录await fs.rmdir(path); } catch (error) {console.log("执行错误!", error); }}
这里巧妙的使用了
map
方法和Promise.all
方法,并通过async
和await
来实现我们的需求如果你需要同步使用
fs
,推荐使用async
和await
来代替上面提到的Sync
同步方法!因为await
必须用在async
函数中,async
函数调用不会造成阻塞,它内部所有的await
阻塞都被封装在一个Promise
对象中异步执行
六、大文件操作 前面我们是通过
readFile
方法来读取文件内容,通过writeFile
和appendFile
来写入文件内容,这些方法对文件数据的操作都是一次性操作,即一次性将数据读出或一次性将数据写入在文件数据内容比较大时,这些方法的效率就会变得很慢,那有没有什么效率高的方式呢?这就需要引入
fs
模块的stream
流了stream流介绍
stream
是Node.js
提供的一个仅在服务区端可用的模块,目的是支持“流”这种数据结构什么是流? 流是一种抽象的数据结构
想象水流,当在水管中流动时,就可以从某个地方(例如自来水厂)源源不断地到达另一个地方(比如你家的洗手池)我们也可以把数据看成是数据流,比如你敲键盘的时候,就可以把每个字符依次连起来,看成字符流。这个流是从键盘输入到应用程序,实际上它还对应着一个名字:标准输入流(
stdin
)如果应用程序把字符一个一个输出到显示器上,这也可以看成是一个流,这个流也有名字:标准输出流(
stdout
)。流的特点是数据是有序的,而且必须依次读取,或者依次写入,不能像Array
那样随机定位有些流用来读取数据,比如从文件读取数据时,可以打开一个文件流,然后从文件流中不断地读取数据。有些流用来写入数据,比如向文件写入数据时,只需要把数据不断地往文件流中写进去就可以了
读取数据
在
Node.js
中,读取流(Readable
流) 也是一个对象,我们只需要响应流的事件就可以了:data
事件表示流的数据已经可以读取了end
事件表示这个流已经到末尾了,没有数据可以读取了error
事件表示出错了
const fs = require("fs"); // 创建一个可读的流const rs = fs.createReadStream("./1.txt", "utf-8"); // 监听data事件,数据会一点一点的进行读取rs.on("data", function (chunk) {console.log("on", chunk); }); // 监听end事件,数据读取完毕后触发rs.on("end", function () {console.log("end"); }); // 监听error事件,出错时触发rs.on("error", function (err) {console.log("error", err); });
要注意,
data
事件可能会有多次,每次传递的chunk
是流的一部分数据写入数据
要以流的形式写入文件,只需要使用 写入流(
Writable
流) 不断调用write()
方法,最后以end()
结束:const fs = require("fs"); // 创建一个可写的流const ws = fs.createWriteStream("./2.txt", "utf-8"); // 写入内容ws.write("11111111111111111111"); ws.write("22222222222222222222"); ws.write("33333333333333333333"); ws.write("44444444444444444444"); ws.end();
管道(文件复制)
我们需要将一个大文件的内容复制到另一个大文件里,这就需要同时使用读取流和写入流,但我们自己使用两者结合时,可能会无法控制某一方的速率,导致两方速率不同步,使得最后复制过去的数据不完整
而
node
贴心的为我们提供了一个pipe
管道(Readable
流的pipe()
方法),用来联通读取流和写入流,并自动控制两者的速率让我们用pipe
就像可以把两个水管串成一个更长的水管一样,两个流也可以串起来。一个Readable
流和一个Writable
流串起来后,所有的数据自动从Readable
流进入Writable
流,这种操作叫pipe
。
pipe()
把一个文件流和另一个文件流串起来,这样源文件的所有数据就自动写入到目标文件里了,所以,这实际上是一个复制文件的程序:const fs = require("fs"); const readStream = fs.createReadStream("./1.txt"); const writeStream = fs.createWriteStream("./2.txt"); // 将1.txt内的数据复制到2.txt中// 若开始时没有2.txt文件,则会自动创建// 若开始时有2.txt文件,则会使用1.txt的内容替换掉其中的内容readStream.pipe(writeStream);
?? 注意:
我们使用
readStream.pipe(writeStream)
时,数据是从左到右,从可读流到可写流传递,既将readStream
的数据传递到writeStream
中小技巧:pipe
管道可以链式调用,这在下一节我们讲到gzip
时会用到
七、补充 判断路径是否存在:
fs
中exists
方法能够判断某一路径是否存在,但exists
异步的方法官方已经不建议使用,建议使用的是加Sync
的同步方法:const fs = require("fs"); // exists方法:判断路径是否存在,异步的方法官方已经不建议使用,建议使用的是加Sync的同步方法console.log(fs.existsSync("./stream流")); // true
文章图片
结语 本篇文章详细讲解了
node.js
的内置模块fs
的常用方法,并介绍了fs
的同步方法与Promise方法,需要注意的是,在node
开发中应尽量减少同步的写法,从而避免因同步阻塞代码执行导致服务器宕机???????到此这篇关于Node.js 操作本地文件及深入了解 fs 内置模块的文章就介绍到这了,更多相关Node.js fs 内置模块内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
推荐阅读
- Dart|Dart 2.18 正式发布
- Hybrid|Hybrid app本地开发如何调用JSBridge
- 批量操作数据、自定义分页器
- 会计老婆大人的前端小跟班儿——node|会计老婆大人的前端小跟班儿——node 操作 Excel 表格
- 麒麟操作系统|麒麟操作系统 (kylinos) 从入门到精通 - 研发环境- 第三十四篇 Docker安装与配置
- Android安全|【原创工具】ADBGUI - GUI版ADB操作工具
- 数据库基本操作|SQL Server 添加字段 修改字段 删除字段 语句
- 明日大盘走势预测及操作策略
- 微服务系列之网关(二)|微服务系列之网关(二) konga配置操作
- 第1.7节|第1.7节 字符串操作