- buffer缓冲区,stream 数据流
- buffer存储中间变量,目的是为了方便CPU执行数据存取操作的时候,可以有一个中间存储区域
而流操作,类似于水流一样,可以通过管道操作数据,还可以对数据进行分段
Buffer、Stream与FS有什么关系呢
FS是内置核心模块,提供文件系统操作的API(文件目录的创建删除,查询,读取,写入,操作系统的二进制数据)
FS模块结构
FS基本操作类
- FS常用API
用户对于文件所具备的操作权限
Nodejs中flag表示对文件操作方式
- r:可读
- w:可写
- s:同步
- +:表示执行相反操作
- x:排它操作
- a:追加操作
FS介绍总结
- fs是Nodejs中内置核心模块
- 代码层面上fs分为基本操作类和常用API
-
文件读写与拷贝操作(以下是异步)
文件操作API(异步,同步请看文档)
- 错误优先,如果是成功的,err会返回null
readFile:从指定文件中读取数据
```javascript // 1. fs.readFile(path,字符编码,callback) const fs = require(‘fs’)
// 路径也可以使用path.resolve来补全 fs.readFile(‘/etc/passwd’,’utf8’,(err, data) => { if (err) throw err; console.log(data); });
// node官网: fs.readFile() 函数缓冲整个文件。 为了最小化内存成本,在可能的情况下优先通过 fs.createReadStream() 进行流式传输。
<a name="dTRS0"></a>
#### writeFile:向指定文件中写入数据
```javascript
const fs = require('fs')
// 2. fs.writeFile(file, data,'字符编码', callback)
// 当 file 是文件名时,将数据异步地写入文件,如果文件已存在则替换该文件。
fs.writeFile('data.txt','hello,杰哥',{
mode:438, // 可读可写,不可执行
flag: 'r+', // r+ 不会清空,直接写入, w+会清空重新写入
encoding: 'utf-8'
},(err,data) => {
if(!err){
fs.readFile('data.txt','utf-8',(err,data) => {
if(!err){
console.log(data);
}
})
}
})
/*
在同一个文件上多次使用 fs.writeFile() 而不等待回调是不安全的。 对于这种情况,建议使用 fs.createWriteStream()。
与 fs.readFile 类似,fs.writeFile 是一个便捷的方法,其在内部执行多次 write 调用以写入传给它的缓冲区。 对于性能敏感的代码,则考虑使用 fs.createWriteStream()。
可以使用 <AbortSignal> 取消 fs.writeFile()。 取消是"尽力而为"的,并且可能仍会写入一些数据。
中止正在进行的请求不会中止单个操作系统请求,而是中止内部缓冲的 fs.writeFile 执行。
*/
appendFile:追加的方式向指定文件中写入数据
const fs = require('fs')
// fs.appendFile(path, data[, options], callback)
// 异步地将数据追加到文件,如果该文件尚不存在,则创建该文件。 data 可以是字符串或 <Buffer>。
fs.appendFile('message.txt', 'data to append',(err) => {
if (err) throw err;
console.log('The "data to append" was appended to file!');
});
// 官网案例
// 通过任何其他 fs 操作对当前正在使用的任何文件描述符 (fd) 调用 fs.close(),则可能会导致未定义的行为。
// fs.close() 关闭文件
function closeFd(fd) {
fs.close(fd, (err) => {
if (err) throw err;
});
}
fs.open('message.txt', 'a', (err, fd) => {
if (err) throw err;
try {
fs.appendFile(fd, 'data to append', 'utf8', (err) => {
closeFd(fd);
if (err) throw err;
});
} catch (err) {
closeFd(fd);
throw err;
}
});
copyFile:将某个文件中的数据拷贝至另外一个文件
- src
| | 要复制的源文件名 - dest
| | 复制操作的目标文件名 - mode
复制操作的修饰符。 默认值: 0。 - callback
```javascript const fs = require(‘fs’) function callback(err) { if (err) throw err; console.log(‘source.txt was copied to destination.txt’); }
// 默认情况下将创建或覆盖 destination.txt。 fs.copyFile(‘source.txt’, ‘destination.txt’, callback);
// 通过使用 COPYFILE_EXCL,如果 destination.txt 存在,则该操作将失败。 fs.copyFile(‘source.txt’, ‘destination.txt’, constants.COPYFILE_EXCL, callback);
mode 是可选的整数,用于指定复制操作的行为。 可以创建由两个或多个值的按位或组成的掩码(例如 fs.constants.COPYFILE_EXCL | fs.constants.COPYFILE_FICLONE)。
fs.constants.COPYFILE_EXCL: 如果 dest 已经存在,则复制操作将失败。 fs.constants.COPYFILE_FICLONE: 复制操作将尝试创建写时复制引用链接。 如果平台不支持写时复制,则使用后备复制机制。 fs.constants.COPYFILE_FICLONE_FORCE: 复制操作将尝试创建写时复制引用链接。 如果平台不支持写时复制,则该操作将失败。
<a name="KVJmL"></a>
#### watchFile:对指定文件进行监控
- filename [<string>](http://url.nodejs.cn/9Tw2bK) | [<Buffer>](http://nodejs.cn/api/buffer.html#class-buffer) | [<URL>](http://nodejs.cn/api/url.html#the-whatwg-url-api)
- listener [<Function>](http://url.nodejs.cn/ceTQa6) 可选,先前使用 fs.watchFile() 附加的监听器。
停止监视 filename 的变化。 如果指定了 listener,则仅删除该特定监听器。 否则,所有监听器都将被删除,从而有效地停止监视 filename。<br />使用未被监视的文件名调用 fs.unwatchFile() 是空操作,而不是错误。<br />使用 [fs.watch()](http://nodejs.cn/api/fs.html#fswatchfilename-options-listener) 比 fs.watchFile() 和 fs.unwatchFile() 更高效。 应尽可能使用 fs.watch() 而不是 fs.watchFile() 和 fs.unwatchFile()。
```javascript
const fs = require('fs')
fs.watchFile('data.txt',{interval:20},(curr,prev) => {
// curr是修改之后的文件,prev是修改之前的
if(curr.mtime !== prev.mtime){
console.log('文件被修改了');
fs.unwatchFile('data.txt') // 取消监控
}
})
打开关闭
const fs = require('fs')
const path = require('path')
// open
fs.open(path.resolve('data.txt'),'r',(err,fd) => {
console.log(fd); // 3
})
// close
fs.open('data.txt','r',(err,fd) => {
// fd可以追踪当前的文件资源
fs.close(fd, err => {
console.log('关闭成功');
})
})
md转html文件实现
/*
01 读取md和css内容
02 将上述读取出来的内容替换占位符,生成一个最终需要的HTML字符串
03 将上述html字符写入到指定html文件中
04 监听md文档内容的变化,然后更新html内容
05 使用browser-sybcKa来实时显示html内容
*/
const fs = require('fs')
const path = require('path')
const marked = require('marked')
const browserSync = require('browser-sync')
let mdPath = path.join(__dirname, process.argv[2])
let cssPath = path.resolve('github.css')
let htmlPath = mdPath.replace(path.extname(mdPath), '.html')
fs.readFile(mdPath,'utf-8',(err,data) => {
let htmlStr = marked(data)
fs.readFile(cssPath, 'utf-8',(err,data) => {
let retHtml = temp.replace('{{content}}',htmlStr).replace('{{style}}',data)
// 将上述内容写入到指定的html文件,用于在浏览器中展示
fs.writeFile(htmlPath, retHtml,(err) => {
console.log('html生成成功');
})
})
})
const temp = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
*{
margin: 0;
}
</style>
<body>
<div class="markdown-body">
{{content}}
</div>
</body>
</html>
`
大文件读写操作
- 要实现大文件边度边写
const fs = require('fs')
// read : 所谓的读操作就是将数据从磁盘文件中写入到buffer中
let buf = Buffer.alloc(10)
// write read
/*
fd用于定位当前被打开的文件
buf 用于表示当前缓冲区
offset 表示当前从buf的哪个位置开始执行写入
length 表示当前写入的长度
position 表示当前从文件的哪个位置开始读取
*/
// 把磁盘里面的东西拿出来放入暂存区
fs.open('data.txt','r', (err,rfd) => {
console.log(rfd); // 3
// rfd表示要执行的文件是谁
fs.read(rfd, buf, 0,3,0,(err,readBytes,data) => {
// readBytes实际读了都是个字节
console.log(readBytes);// 3
console.log(data);
console.log(data.toString());
})
})
// write 将缓冲区间里的内容写入到磁盘文件中
// 先读再写
// 把暂存区内容拿出来,放入文件中
buf = Buffer.from('1234567890')
fs.open('data.txt','w',(err,wfd) => {
// wfd 表示要操作的文件,buf存放buf的缓冲区
fs.write(wfd, buf,0,3,0,(err,written,buffer) => {
console.log(written); // 当时写入的字节数 3个字节
console.log(buffer);
console.log(buffer.toString());
fs.close(wfd) // 关闭
})
})
文件拷贝自定义实现
- 对大体积文件,进行拷贝
- 每一次open都是对系统资源的消耗 ```javascript const fs = require(“fs”);
/**
- 01 打开A文件,利用read将数据保存到buffer暂存起来
- 02 打开B文件,利用write将buffer中写数据写入到b文件中 /
let buf = Buffer.alloc(10);
数据的拷贝(不完全) // 01从打开的文件中读取数据 fs.open(“a.txt”, “r”, (err, rfd) => { // 03打开b文件,用于执行数据写入操作 fs.open(“b.txt”, “w”, (err, wfd) => { // 02从打开的文件中读取数据 fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => { // 04将buffer的数据写入到b.txt中 fs.write(wfd, buf, 0, 10, 0, (err, written) => { console.log(“写入成功”); }); }); }); });
// 全部拷贝 const BUFFER_SIZE = buf.length; let readOffset = 0;
fs.open(“a.txt”, “r”, (err, rfd) => { fs.open(“b.txt”, “w”, (err, wfd) => { function next() { fs.read(rfd, buf, 0, BUFFER_SIZE, readOffset, (err, readBytes) => { if (!readBytes) { // 如果条件成立,说明内容已经读取完毕 fs.close(rfd, () => {}); fs.close(wfd, () => {}); console.log(“拷贝完成”); return; } readOffset += readBytes; fs.write(wfd, buf, 0, readBytes, (err, written) => { next(); }); }); } next(); }); });
<a name="G2G8T"></a>
### FS之目录操作API
<a name="T5H93"></a>
#### 目录操作API
- access:判断文件或目录是否具有操作权限
- stat:获取目录及文件信息
- mkdir:创建目录
- rmdir:删除目录
- readdir:读取目录中内容
- unlink:删除指定文件
```javascript
const fs = require("fs");
// 1. access
fs.access("a.txt", (err) => {
// 如果有错误,证明对他没有操作权限,或者路径信息是错误的
if (err) {
console.log(err);
} else {
console.log("有操作权限");
}
});
// 2. stat
fs.stat("a.txt", (err, statObj) => {
console.log(statObj.size); // 文件大小
console.log(statObj.isFile()); // 判断是否是文件
console.log(statObj.isDirectory()); // 判断是否是目录
});
// // 3. mkdir 保证前面目录存在
// 想要实现递归的创建,{recursive: true}
fs.mkdir("a/a/a", { recursive: true }, (err) => {
if (!err) {
console.log("创建成功");
} else {
console.log(err);
}
});
// rmdir 删除,默认情况只能删除空目录
// 递归删除 { recursive: true }
fs.rmdir("a", { recursive: true }, (err) => {
if (!err) {
console.log("删除成功");
} else {
console.log(err);
}
});
// readdir 读取目录, 只能看当前目录下
fs.readdir('a',(err,files) => {
console.log(files);
})
// 文件的删除
fs.unlink('a/a.txt',err => {
if(!err){
console.log('删除成功');
}
})
同步创建目录实现(对熟练度的加深)
const fs = require("fs");
const path = require("path");
/*
01 将来调用时需要接收类似于a/b/c,这样的路径,它们之间是采用 / 去行连接
02 利用 / 分割符将路径进行拆分,将每一项放入一个数组中进行管理['a','b','c']
03 对上述的数组进行遍历,我们需要拿到每一项,与前一项进行拼接
04 判断一个当前对拼接之后的路径是否具有可操作的权限,如果有证明存在,否则的话就需要执行创建
*/
function makeDirSync(dirpath) {
// path.sep可以拿到当前操作系统对路径的分割符
let items = dirpath.split(path.sep);
// console.log(items);
// console.log(path.sep);// \
for (let i = 1; i <= items.length; i++) {
// 每次都要判断是否有操作权限
let dir = items.slice(0, i).join(path.sep)
// 如果没有权限,会报错
try {
fs.accessSync(dir)
} catch (error) {
fs.mkdirSync(dir)
}
}
}
makeDirSync("a\\v\\c");
异步创建目录实现(并不是为了替代调原生的方法)
const fs = require("fs");
const path = require("path");
function mkDir(dirPath, cb) {
let parts = dirPath.split("/");
let index = 1;
// 定义函数,完成创建和往后移的操作
function next() {
// 递归出口
if (index > parts.length) return cb && cb();
let current = parts.slice(0, index++).join("/");
// 是否有权限
fs.access(current, (err) => {
if (err) {
// 有错误就创建一个
fs.mkdir(current, next);
} else {
// 不需要创建当层
next();
}
});
}
next()
}
mkDir('a/b/c',() => {
console.log('创建成功');
})
// 将access与mkdir处理成async...风格
const access = promisify(fs.access);
const mkdir = promisify(fs.mkdir);
async function myMkdir(dirPath, cb) {
let parts = dirPath.split("/");
for (let index = 1; index <= parts.length; index++) {
let current = parts.slice(0, index).join("/");
// 因为采用promise风格,捕获要使用
try {
await access(current);
} catch (error) {
// 不可读写就创建一个
mkdir(mkdir);
}
}
cb && cb();
}
myMkdir("a/v/c", () => {
console.log("创建成功");
});
递归删除目录(练习)
const fs = require("fs");
const path = require("path");
/**
* 需求自定义函数,接收一个路径,然后执行删除操作
* 01 判断当前传入的路径是否为一个文件,直接删除当前文件即可
* 02 如果当前传入的是一个目录,我们需要继续读取目录中的内容。然后再执行删除操作
* 03 将删除行为定义成一个函数,然后通过递归的方式进行复用
* 04 将当前的名称拼接成在删除时,可以使用的路径
*
*/
function myRmdir(dirPath, cb) {
// 判断当前dirPath的类型
fs.stat(dirPath, (err, statObj) => {
if (statObj.isDirectory()) {
// 读取目录---继续读取
fs.readdir(dirPath, (err, files) => {
// console.log(files); [ 'aaa.js', 'c' ]
let dirs = files.map((item) => {
return path.join(dirPath, item);
});
console.log(dirs);
let index = 0;
function next() {
// 找一个递归的出口
// 当我们发现数组中的每一项都被遍历完了,结束遍历,当前最外层的目录删除
if (index === dirs.length) return fs.rmdir(dirPath, cb);
// index每次都得往后移
let current = dirs[index++];
myRmdir(current, next);
}
next()
});
} else {
//文件---直接删除
fs.unlink(dirPath, cb);
}
});
}
myRmdir("a", () => {
console.log("删除成功");
});