Nodejs fs (一)

前言

flie system 可以在服务端读取文件和数据 方法是同步+异步共存
同步方法易使用(刚开始可以用同步)
异步方法不会阻塞主线程(程序运行起来之后可以用异步 回调函数)

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

// 读取文件 文件不存在会报错
// 写入文件 文件不存在会创建文件
fs.readFile(path.resolve(__dirname,'./note.md'),function(err, data){
if(err) console.log(err);
fs.writeFile(path.resolve(__dirname,'./note1.md'), data, function(){

})
})

分析:先将note.md中的内容全部读取到内存中,再将内存写入note1中。如果文件非常大,这样就会很消耗内存,所以不适合大文件来使用,可能会导致内存的浪费 性能不高。
所以可以读取一些 写入一些

fs + Buffer

手动按照字节读取 fs.open fs.read fs.write fs.close

fs.open

1
2
3
4
5
6
7
8
9
// r 读取  
// w 写入
// r+ 在读的基础上可以写 但是文件不存在会报错
// w+ 在写的基础上可以读取 如果文件不存在会创建
let fs = require('fs');
let path = require('path');
fs.open(path.resolve(__dirname,'./note.md'), 'r', (err, fd)=>{ // fd:file descriptor 文件描述符
console.log(fd);
})
  • 现在希望每次读3个再写入

fs.open + fs.read + fs.close + Buffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let fs = require('fs');
let path = require('path');
let buffer = Buffer.alloc(3);
fs.open(path.resolve(__dirname,'./note.md'), 'r', (err, fd)=>{// fd类似于一个标识,代表读取的文件名和'r'这个操作 是number类型
// 读取文件 fd代表文件 buffer代表要把读取的内容放到哪个Buffer中
// 0,3 从buffer的第0个位置写入 写入3个
// 0 从文件的哪个位置读取
fs.read(fd,buffer,0,3,0, function(err, bytesRead){ // bytesRead真正读取到的个数
console.log(bytesRead, buffer);
fs.close(fd, ()=>{
console.log('close');
})
});
})
// 3 <Buffer e4 bd a0> close
  • 写入 在读的过程中写入

fs.open + fs.read + fs.write + Buffer

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
let fs = require('fs');
let path = require('path');
let buffer = Buffer.alloc(3);
fs.open(path.resolve(__dirname,'./note.md'), 'r', (err, rfd)=>{
// 读取文件 fd代表文件 buffer代表要把读取的内容放到哪个Buffer中
// 0,3 从buffer的第0个位置写入 写入3个
// 0 从文件的哪个位置读取
fs.open(path.resolve(__dirname,'./note1.md'), 'w', (err,wfd)=>{
// 读取的过程是将读取到的内容 写入到buffer中
// 写入文件的过程是将buffer中的内容读取出来写到文件中
fs.read(rfd,buffer,0,3,0, function(err, bytesRead){ // bytesRead真正读取到的个数
fs.write(wfd,buffer,0,3,0, function(err) {

})
// fs.write(wfd,buffer,0,3,3, function(err) {

// })
// fs.write(wfd,buffer,0,3,6, function(err) {

// })
});
})
})
// node.md 1234567890
// 现在将line15-20注释打开 可以看到note1.md里面一直追加存入123123123
  • writeFileSync 、appendFileSync
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let fs = require('fs');
let path = require('path');
fs.writeFileSync(path.resolve(__dirname,'note2.md'), '123');
fs.writeFileSync(path.resolve(__dirname,'note2.md'), '123');// 如果文件存在,会将文件中的内容情况 覆盖
fs.writeFileSync(path.resolve(__dirname,'note2.md'), '123');
// 123

// 如下 追加
let fs = require('fs');
let path = require('path');
fs.writeFileSync(path.resolve(__dirname,'note3.md'), '123');
fs.writeFileSync(path.resolve(__dirname,'note3.md'), '123',{flag:'a'});
fs.writeFileSync(path.resolve(__dirname,'note3.md'), '123',{flag:'a'});
// 123123123

// 或者
let fs = require('fs');
let path = require('path');
// 写入时 没有会创建 有会追加
fs.appendFileSync(path.resolve(__dirname,'note4.md'), '123');
fs.appendFileSync(path.resolve(__dirname,'note4.md'), '123');
fs.appendFileSync(path.resolve(__dirname,'note4.md'), '123');
// 123123123

两个耦合性比较高的代码 怎么拆分, 发布订阅 => 流

  • 读取一点写一点 不会导致淹没可用内存
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
const fs = require('fs');
const path = require('path');
// 读取一点写一点 不会导致淹没可用内存
const SIZE = 5;
let buffer = Buffer.alloc(SIZE);
fs.open(path.resolve(__dirname,'note.md'), 'r', function (err, rfd) {
if(err) return console.log(err);

fs.open(path.resolve(__dirname,'copy.md'), 'w', function (err, wfd) {
if(err) return console.log(err);
let readOffset = 0;
let writeOffset = 0;
// co
function next() {
fs.read(rfd,buffer,0,SIZE,readOffset, function (err, bytesRead) {
if(bytesRead === 0){
fs.close(rfd, ()=>{});
fs.close(wfd, ()=>{});
return;
}
fs.write(wfd,buffer,0,bytesRead,writeOffset,function(){
readOffset += bytesRead;
writeOffset += bytesRead;
next();
});
})
}
next();
})
})
// copy.md 1234567890

fs操作文件

文件的操作

  • fs.readFile 、fs.existSync 、fs.access
  • fs.writeFile 、fs.copyFile()
  • fs.rename 、fs.unlink

文件夹的操作

  • 创建目录 mkdir
  • 删除目录 rmdir
  • 读取目录 readdir

fs.stat 提供了关于文件的信息(文件状态)

  • 删除文件 重命名
1
2
3
4
5
6
7
8
9
10
const fs = require('fs');
const path = require('path');
// 重命名
fs.rename(path.resolve(__dirname, 'copy.md'), path.resolve(__dirname, 'c.md'),function (err) {
console.log(err)
})
// 删除
fs.unlink(path.resolve(__dirname, 'c.md'), function (err) {
console.log(err)
})

创建目录

创建目录 必须是存在的文件夹才能向下创建

  • mkdirSync 、mkdir创建文件夹
1
2
3
4
5
6
7
8
9
10
11
// 现有a/文件夹
const fs = require('fs');
const path = require('path');
fs.mkdirSync('a/b');
// 直接a/b/c/d 这里只有a/b文件夹,不能直接创建到d

const fs = require('fs');
const path = require('path');
fs.mkdir('a/b/c/d', function (err) {
if(err) console.log(err); // 这样就会报错 只能先创建 a/b/c
});
  • 现在希望实现 mkdir(‘a/b/c/d’)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const fs = require('fs');
function mkdir(paths, callback){
paths = paths.split('/');
let index = 1;
// 这里用异步的方式创建 创建完a之后再创建b再创建c、d 异步迭代的模式
function next(){
if(index === paths.length + 1) return callback();
let dirPath = paths.slice(0, index++).join('/');
fs.access(dirPath, function (err) {
if(err){
// 不存在
fs.mkdir(dirPath,next);
}else{ // 存在创建下一层
next();
}
})
}
next();
}
mkdir('a/b/c/d', function () {
console.log('创建完成')
})

删除目录

删除目录 包含子目录不能删除

1
2
3
4
5
6
7
8
9
10
// 删除目录
const fs = require('fs');
fs.rmdir('a', function(err){
console.log(err); // 报错 a目录下有子目录
})

const fs = require('fs');
fs.readdir('a', function(err, dirs){
console.log(dirs); // 返回的是数组 ['b']
})
  • 删除a文件夹下的文件夹或文件 这里只有一层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const fs = require('fs');
const path = require('path');
fs.readdir('a',function(err,dirs){
console.log(dirs)
dirs = dirs.map(dir=>path.join('a',dir));
console.log(dirs);
dirs.forEach(dir=>{
// 判断当前路径的状态
fs.stat(dir,function(err,statObj){
if(statObj.isFile()){
fs.unlink(dir,function(){});
}else{
fs.rmdir(dir,()=>{})
}
});
})
});

递归删除目录

其实就是树的遍历

  • 先序深度遍历

深度 先序 串联

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
const fs = require('fs');
const path = require('path');
function preSeriesDeep(dir, callback){
// 有儿子就删除儿子
// 儿子删除完毕后删除自己 考虑的时候只考虑第一层和第二层,后面的都是一样的
fs.stat(dir, function(err, statObj){
if(statObj.isFile()){ // 是文件就删除
fs.unlink(dir, callback);
}else{ // 是文件夹
fs.readdir(dir, function(err,dirs){
// dirs 是读到的儿子 // [ '1.js', 'b', 'e' ]
dirs = dirs.map( item=> path.join(dir, item));
let index = 0;
function next(){
if(index === dirs.length) return fs.rmdir(dir, callback);
let dirPath = dirs[index++];
// 删除当前第一个儿子 成功后删除第二个儿子
preSeriesDeep(dirPath, next);
}
next(); // 取出第一个删掉

})
}
})
}
preSeriesDeep('a',function(){
console.log("删除成功");
});

分析:如图,现在删除的顺序是c、d删除,再删b再删e,其实删cdb和e没有必然关系,这样就会导致性能上的劣势。所以可以cdb自己删自己的,e包括它下面的自己删自己的,同一层的全部删除完毕之后 告诉上一层,如这里的a,然后再删除a 上面可以理解为串联的删。

并发的删除

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
const fs = require('fs');
const path = require('path');
function preparallelDeep(dir, callback){
fs.stat(dir, function(err, statObj){
if(statObj.isFile()){ // 是文件就删除
fs.unlink(dir, callback);
}else{
fs.readdir(dir, function(err,dirs){
dirs = dirs.map( item=> path.join(dir, item));
// 如果没有儿子节点,直接将自己删除即可
if(dirs.length === 0) return fs.rmdir(dir, callback);
let index = 0;
function done(){
// index ++;
// if(index === dirs.length){
// return fs.rmdir(dir, callback);
// }
// 简写
if(++index === dirs.length) return fs.rmdir(dir, callback);
}
dirs.forEach( dir=>{
preparallelDeep(dir, done)
})
})
}
})
}
preparallelDeep('a',function(){
console.log("删除成功");
});

上面的代码还是有点复杂 只要是异步的其实都可以用Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const fs = require('fs');
const path = require('path');
function preDeep(dir){
return new Promise( (resolve, reject) =>{
fs.stat(dir,function(err, statObj){
if(statObj.isFile()){
fs.unlink(dir,resolve); // 这行注意下 自己想的时候没想到这样放,在下一行resolve() 了
}else{
fs.readdir(dir, function(err, dirs) {
dirs = dirs.map( item => preDeep(path.join(dir, item)));
Promise.all(dirs).then(()=>{
fs.rmdir(dir,resolve); // 这行也是
})
})
}
})
})
}

preDeep('a').then(()=>{
console.log('删除成功');
})

async + await 后序都会这样写 重要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const {unlink, readdir, stat, rmdir} = require('fs').promises;
const path = require('path');
async function preDeep(dir){
let statObj = await stat(dir);
if(statObj.isFile()){
await unlink(dir);
}else{
let dirs = await readdir(dir); // generator await是它的语法糖
dirs = dirs.map( item => preDeep(path.join(dir,item)));
await Promise.all(dirs);
await rmdir(dir);
}
}
preDeep('a').then(()=>{
console.log('删除成功');
}).catch(err=>{
console.log("捕获错误", err)
})
  • 广度遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const fs = require('fs');
const path = require('path');
function wide(dir){
let arr = [dir];
let index = 0;
let current = null;
while (current = arr[index++]) {
let dirs = fs.readdirSync(current);
dirs = dirs.map( item => path.join(current, item));
arr = [...arr, ...dirs];
}
// 循环arr删除即可
console.log(arr);
}
wide('a');
// [ 'a', 'a/b', 'a/e', 'a/b/c', 'a/b/c/d' ]