node 之fs 操作文件 ? 缓存Buffer ?

5,319 阅读11分钟

在node我们如果操作文件,就需要读取buffer.通过fs读取

缓存 Buffer 你知道多少

文件操作,流的操作都要用到buffer,

一个字节最大多少? 假设8个1用10进制表示 265,他们分别有一下几个特点:

2进制 8进制 10进制 16进制
0b开头 0o开头 278 0x开头
  1. 关于进制的转换 16 进制 转换为 10 进制
  2. 关于10进制 小于127的我们认为是单字节,大于127的我们认为是汉字和双字节
  • 将10进制转换为任意进制 (278).tostring(16) => 116

比如 unicode编码都对应着一个utf8规则

十六进制 utf8编码方式
0000 0000-0000 007f 0xxxxxxx
0000 0080-0000 07ff 110xxxxx 10xxxxxx
0000 0800-0000 ffff 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0000 ffff 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

如果是汉字,是3个字节,那就是第三行。对于英语字母,是两个字节,和 ASCLL是相同的,我们区分他们用的就是编码前面的标识。

比如我们要转换一个 16进制编码,代码如下

function transfer(r){
    let code = [1110,10,10];  //汉字三个字节,拼接字符串
    code[2] += (r).toString(2).slice(-6);
    code[1] += (r).toString(2).slice(-12,-6); 
    code[0] += ((r).toString(2).slice(0,-12)).padStart(4,0);
    code = code.map((item)=>parseInt(item,2)) ; //将二进制转换回十进制
   return Buffer.from(code).toString()  //转换成buffer在转换成汉字
}

node要操作文件都是二进制,声明一段内存,有两个方法:buffer.from,buffer.alloc;

buffer将二进制转换为16进制展示,非分配内存的大小不能更改,比如v8 1.7g(64) 0.9g(32),内存是属于非引用空间,但是属于引用类型,非常像二维数组,buffer.alloc 会输出 <Buffer 00 00 00 00 00 00>,安全,速度慢,通过长度创建,buffer.from通过十进制的数组或者字符串,node不支持gb2312 格式,但是当我们爬虫一个这种类型的网站的时候,可以用一个包(icon-lite),使用之前需要安装,使用方法,请参考www.npmjs.com/package/ico…

let fs = require('fs');
let path = require('path');
let iconvlite = require('iconv-lite'); //这个包需要转化的是buffer
let r = fs.readFileSync(path.resolve(__dirname,'a.txt'));
let result = iconvlite.decode(r,'gbk')//可以转换成任意格式
console.log(result)

由于buffer实际上是二进制,那么他就可以用toString进行转换

let buffer = Buffer.from('走开')
console.log(buffer.slice(0,2).toString())
console.log(buffer.slice(2,6).toString())

=> �
�开

上面这种情况如何解决呢?也是一个内置方法

let buffer = Buffer.from('走开')
let a = buffer.slice(0,2);
let b = buffer.slice(2,6);
let { StringDecoder } = require('string_decoder');
let sd = new StringDecoder();
console.log(sd.write(a)) //此时不是正常汉字则保留在sd的内部
console.log(sd.write(b)) //下次输出会把上次的结果一同输出


=> 走开

缓存输出,把不能拼成汉字的先缓存,在输出.

buffer的其他方法buffer.copy,buffer.concat,具体实现


let buffer1 = Buffer.alloc(6);
let buffer2 = Buffer.from("走开");
Buffer.prototype.mycopy = function(target,targetStart,sourceStart,sourceEnd){
   for( let i = 0;i< sourceEnd - sourceStart;i++){
       target[i + sourceStart] = this[sourceStart + i]
   }
}
buffer2.mycopy(buffer1,1,3,6);//目标buffer, 目标开始的拷贝位置,源的开始和结束位置
console.log(buffer1.toString());
buffer2.copy(buffer1,1,3,6);
console.log(buffer1.toString());

接收请求时会采用concat方法进行拼接

let buffer1 = Buffer.from("走");
let buffer2 = Buffer.from("开");
let buffer3 = Buffer.from("啊");
Buffer.concat([buffer1,buffer2,buffer3]);//目标buffer, 目标开始的拷贝位置,源的开始和结束位置
console.log(Buffer.concat([buffer1,buffer2,buffer3]).toString());

Buffer.myconcat = function(list,len){
   
   //计算要生成的buffer长度,将list每一项求和
   if(typeof len === 'undefined'){
       len = list.reduce((current,next,index) => {
           return current + next.length
       },0)
   }
   let newBuffer = Buffer.alloc(len);
   let index = 0;
   list.forEach(buffer => {
       buffer.copy(newBuffer,index)   
       index += buffer.length;     
   });
   return newBuffer.slice(0,index)
}

Buffer.myconcat([buffer1,buffer2,buffer3]);//目标buffer, 目标开始的拷贝位置,源的开始和结束位置
console.log(Buffer.myconcat([buffer1,buffer2,buffer3]).toString());

类似还有indexof,下面我们在原型上扩展一个split方法


let buffer1 = Buffer.from("走**走**走");

Buffer.prototype.split = function(sep){
   let index = 0;
   let len = Buffer.from(sep).length;//查找buffer的长度
   let i = 0;
   let arr = []
   while( -1 != (i = this.indexOf(sep,index))){
       let a = this.slice(index,i);
       index = i + len;
       arr.push(a)
   }
   arr.push(this.slice(index))
   return arr.map(item => item.toString()));
}

buffer1.split('**'); 

fs方法有哪些

  • readFile/ writeFile /copyFile
  • read/write/open/sync/colse
  • fs.mkdir /rmdir/rname/readfir
  • ...

fs模块, 在nodejs中,使用fs模块来实现所有有关文件及目录创建,写入删除操作,所有方法氛围同步异步两种实现,带sync为同步,反之异步,在此之前我们先知道权限的问题

linux权限

文件类型与权限 链接占用的节点 文件所有者 文件所有者的用户组 文件大小 文件创建的事件 最近修改时间 文件名称
-rw-r--r-- 1 root root 34298 04-02 00:23 install.log

以上就是我们的权限 - 代表当前是文件,d代表目录,r代表读w代表写,`````

权限项 执行 执行 执行
字符表示 r w x r w x r w x
数字表示 4 2 1 4 2 1 4 2 1
权限分配 文件所有者 文件所有组 其他用户

他们三个组成权限位:二爷一直死读书

//文件中存的永远是二进制
let fs = require('fs');
fs.readFile('1.txt',{encoding:'utf8',flag:'r'},function(err,data){
    if(err) return console.log(err); 
    console.log(data);
})
//写
fs.writeFile('a.txt',Buffer.from('123'),{flag:'',mode:0o444},function(err,data){
    if(err) return console.log(err); 
    console.log("写入成功");
})
//copy
fs.copyFile('a.txt','3.txt',function(err,data){
    console.log("拷贝成功");
})

flag

  • w+ 读取并写入,如果存在则清空文件内容,不存在则创建内容
  • r+ 读取并写入,文件不存在则报错
  • r 文件不存在则报错
  • w 文件不存在则创建,存在则清空

fs文件操作

fs文件操作 - read


let fs = require('fs');
//fd文件描述符,符号从3开始, 0 标准输入,1标准输出 2代表错误输出
fs.open('1.txt','r',function(err,fd){
    //把文件中的内容读取到buffer中,offsetbuffer是buffer的偏移量
    let BFFER_SIZE = 3;
    let buffer = Buffer.alloc(3);//读取到哪个buffer上
    //将fd的内容读到buffer里,从第0开始读,读BFFER_SIZE个,从文件的第0 个位置开始读,成功以后回调,byteRead实际读到的个数
    // fs.read(fd,buffer,0,BFFER_SIZE,0,function(err,byteRead){
    //     fs.read(fd,buffer,0,BFFER_SIZE,0,function(err,byteRead){
    //         fs.read(fd,buffer,0,BFFER_SIZE,0,function(err,byteRead){
        
    //         })
    //     })
    // })
    //等同于
    let index = 0;
    function next(){
        fs.read(fd,buffer,0,BFFER_SIZE,index,function(err,byteRead){
            index += byteRead;
            if(byteRead == BFFER_SIZE){
                next()
            }else{
                fs.close(fd,()=>{
                    console.log('close')
                })
            }
            console.log(buffer.slice(0,byteRead).toString())
        })
    };
    next();
})

fs文件操作 - copy

let fs = require('fs');
function copy(source,target){
    let index = 0;
    let buffer = Buffer.alloc(3);
    let BUFFER_SIZE = 3;
    fs.open(source,'r',function(err,rfd){//开启读取文件描述符
        if(err) return console.log(err);
        fs.open(target,'w',0o666,function(err,wfd){ // 开启写入描述符
            function next(){
                fs.read(rfd,buffer,0,BUFFER_SIZE,index,function(err,byteRead){
                    fs.write(wfd,buffer,0,byteRead,index,function(err,byteWritten){
                        index += byteRead;
                        if(byteWritten){//如果有写入内容,就继续读取
                            next();
                        }else{
                            fs.close(rfd,()=>{});
                            //吧内存中的内容强制写入在关闭文件,以为写入是异步操作
                            fs.fsync(function(){
                                fs.close(wfd,()=>{});
                            })
                        }
                    })
                })
            }
            next();
        })
    })
}
copy('5.txt','6.txt')

如果你报错binding.fsync(fd, req); ^

TypeError: fd must be a file descriptorfs.write这个方法在执行的时候会检测该文件是否已经存在了,如果已经存在便会报这个错误,解决办法就是先删掉本地该文件,然后再执行就成功了,如下所示。 详情请参考https://blog.csdn.net/u012453843/article/details/60958779 当然以上这么麻烦,于是我们有流,流提供了copy的功能,pipe 有关流清参考文章

目录相关,树的遍历 fs.mkdir /rmdir/rname/readfir

fs主要做fileSystem,我们可以通过他监听文件的变化,判断文件的状态,文件的读写还有目录的操作等等

  • 创建目录
let fs = require('fs');
//创建目录,同步方法和异步方法,如果只读一次建议使用同步,同步编写比较容易
//异步不会阻塞主线程,性能高一些

//fs.mkdirSync('a/b') //只能创建目录,不能创建js文件,不能跨级创建,必须保证父级存在
function makep(dir,callback){
    let dirs = dir.split('/');
    let index = 1;
    function next(index){
        //当索引溢出时 就不要递归了
        if(index === dir.length + 1 ) return callback;
        let p = dirs.slice(0,index).join('/');
        fs.access(p,(err) => {
            if(!err){
                next(index+1);
            }else{
                //如果没有这个文件就会走到err中,创建这个文件,创建完毕后创建下一个文件
                fs.mkdirp(p,(err) => {
                    if(err) return console.log(err);
                    next(index + 1)
                })
            }
        })
    }
    next(index)
}
makep('a/b/c/d')
  • 删除目录

当我们删除文件夹的时候fs.rmdirSync('a');有时候会报错误directory not empty,也就是说删除文件夹必须保证a目录是空的,此时我们需要对文件夹进行遍历, 遍历分先序,中序,后序,(删除目录一般是先序)或者深度和广度 ;

先写一个同步一层的

let fs = require('fs');
let path = require('path');

//只能读儿子目录
let dirs = fs.readdirSync('c');
dirs = dirs.map(item => path.join('c',item))
dirs.forEach(p =>{
    let stat = fs.statSync(p);
    console.log('atat' + stat.isDirectory())
    if(stat.isDirectory()){//是否是文件夹
        fs.rmdirSync(p)//删除目录
    }else{
        fs.unlinkSync(p) //删除文件
    }
})
fs.rmdirSync('c')//删除自己

多层删除,先序深度

let fs = require('fs');
let path = require('path');

//同步删除文件夹
function removeDirSync(dir){
//允许人家删除的不一定是目录
    let stat = fs.statSync(dir);
    if(stat.isDirectory()){//是否是文件夹
        let dirs = fs.readdirSync(dir);
        dirs = dirs.map(item => path.join(dir,item))
        dirs.forEach(d =>{
            removeDirSync(d)
        })
        fs.rmdirSync(dir)//最后删除自己
    }else{
        fs.unlinkSync(dir) //删除文件
    }
}
removeDirSync('c')

异步删除文件夹

let fs = require('fs');
let path = require('path');

//异步删除文件夹 promise
function removeDir(dir){
    return new Promise((resolve,reject) => {
        fs.stat(dir,(err,stat)=> {//第二个参数返回的是之前let stat 
            if(stat.isDirectory()){//是否是文件夹
                let dirs = fs.readdir(dir,(err,dirs)=>{
                    dirs = dirs.map(item => path.join(dir,item));
                    dirs = dirs.map(p => removeDir(p)); //保证返回的是promise
                    //当两个方法同时完成的时候
                    Promise.all(dirs).then(()=>{
                        fs.rmdir(dir,resolve) //删除自己
                    }) 
                });
                
            }else{
                fs.unlink(dir, resolve) //删除文件
            }
        })
    })
}
removeDir('c').then(data =>{
    console.log('成功')
})

//异步删除文件夹

let fs = require('fs');
let path = require('path');

//异步删除文件夹 promise
function rmdir(dir,callback){
    fs.stat(dir,(err,stat)=> {//第二个参数返回的是之前let stat 
        if(stat.isDirectory()){//是否是文件夹
            let dirs = fs.readdir(dir,(err,dirs)=>{
                //只要涉及到异步递归就用next
                function next(index){
                    if(dirs.length === 0 ||(index ==  dirs.length)) {
                        return fs.rmdir(dir,callback)
                    }
                    let p = path.join(dir,dirs[index]);
                    rmdir(p,() => next(index+1));
                }
                next(0);
            });
        }else{
            fs.unlink(dir,callback) //删除文件
        }
    })
}
rmdir('a',() =>{
    console.log('delecte ok')
})

广度删除

let fs = require('fs');
let path = require('path');

//异步删除文件夹 promise
function preWide(dir){
    let arr = [dir];
    let index = 0;
    while(arr[index]){
        let current = arr[index++];
        let stat = fs.statSync(current);
        if(stat.isDirectory()){
            let dirs = fs.readdirSync(current);
            arr = [...arr,...dirs.map(d => path.join(current,d))]
        }
        
    }
    for(var i = arr.length-1;i >= 0;i--){
        let p = arr[i];
        let stat = fs.statSync(p);
        if(stat.isDirectory()){
            fs.rmdirSync(p)
        }else{
            fs.unlinkSync(p)
        }
    }
}

preWide("a");

广度异步删除 promise 如果有人有更好的方法的话,麻烦提供代码,谢谢,这里是我多次修改的全部代码

let fs = require('fs');
let path = require('path');

let arr = [];
let index = 0;
function preWideDir(dir){
    if(arr.length === 0){arr[0] = dir}
    let current = dir;
    return new Promise((resolve,reject) => {
        stat = fs.stat(current,(err,stat) =>{ //判断当前目录状态 异步操作
            if(stat.isDirectory()){ //如果是文件夹
                let dirs = fs.readdir(current,(err,dirs)=>{  //读取文件夹的内容
                    dirs = dirs.map(d => path.join(current,d));
                    arr = [...arr,...dirs];
                    if(index < arr.length-1){
                        index++;
                        preWideDir(arr[index])
                    }else{
                        for(var i = arr.length-1;i >= 0;i--){
                            let p = arr[i];
                            let stat = fs.stat(p,(err,stat) =>{
                                if(stat.isDirectory()){
                                    fs.rmdir(p,resolve)
                                }else{
                                    fs.unlink(p, resolve)
                                }
                            });
                        }
                    }
                })
            }else{
                if(index < arr.length-1){
                    index++;
                    preWideDir(arr[index])
                }else{
                    for(var i = arr.length-1;i >= 0;i--){
                        let p = arr[i];
                        let stat = fs.stat(p,(err,stat) =>{
                            if(stat.isDirectory()){
                                fs.rmdir(p,resolve)
                            }else{
                                fs.unlink(p, resolve)
                            }
                        });
                    }
                }
            }
            
        })
    })
}
preWideDir("src/b.8").then(data=>{
    console.log("删除成功")
})

广度异步删除 promise

let fs = require('fs');
let path = require('path');

let arr = [];
let index = 0;
function preWideDir(dir){
    if(arr.length === 0){arr[0] = dir}
    let current = dir;
    return new Promise((resolve,reject) => {
        fs.stat(current,(err,stat) =>{ //判断当前目录状态 异步操作
            index++;
            if(stat.isDirectory()){ //如果是文件夹
                fs.readdir(current,(err,dirs)=>{  //读取文件夹的内容
                    dirs = dirs.map(d => path.join(current,d));
                    arr = [...arr,...dirs];
                    dirs = dirs.map(p => preWideDir(p));
                    if(index == arr.length-1){
                        for(let i = arr.length -1;i >= 0;i--){
                            let p = arr[i];
                            console.log(arr)
                            fs.stat(p,(err,stat) =>{
                                if(stat.isDirectory()){
                                    console.log(p + stat.isDirectory())
                                    fs.rmdir(p,resolve)
                                }else{
                                    fs.unlink(p,resolve)
                                }
                            });
                        }
                        
                    }
                })
                fs.rmdir(dir,resolve)
            }
        })
    })
}
preWideDir("src/a.7").then(data=>{
    console.log("删除成功")
})

let fs = require('fs');
let path = require('path');

let arr =[];
function preWideDir(dir){
    if(!arr[dir]) arr.push(dir); 
    console.log(dir)
    return new Promise((resolve,reject) => {
        fs.stat(dir,(err,stat) =>{ //判断当前目录状态 异步操作
            if(stat.isDirectory()){ //如果是文件夹
                fs.readdir(dir,(err,dirs)=>{  //读取文件夹的内容
                    dirs = dirs.map(d => path.join(dir,d));
                    arr = [...arr,...dirs];
                    dirs = dirs.map(p => preWideDir(p)); 
                    Promise.all(dirs).then(()=>{
                        for(let i = arr.length - 1;i >= 0;i--){
                            let p = arr[i];
                            let stat = 
                            fs.stat(p,(err,stat) =>{
                                if (err) {
                                    console.log("找不到文件"+p);
                                    return;
                                }
                                if(stat.isDirectory()){
                                    fs.rmdir(p,resolve)
                                }else{
                                    fs.unlink(p,resolve)
                                }
                            });
                        }  
                    }) 
                })
                
            }else{
                resolve();
            }
        })
    })
}


preWideDir("a.4").then(data=>{
    console.log("删除成功")
})

广度异步删除

let fs = require('fs');
let path = require('path');

let arr =[];
function prew(dir,callback){
    let arr = [dir];
    function next(index){
        if(arr[index]){
            fs.stat(arr[index],(err,stat) =>{          
                if(err) {
                    return;
                };
                if(stat.isDirectory()){
                    fs.readdir(arr[index],(err,dirs)=>{  //读取文件夹的内容
                        if(dirs.length === 0){
                            next(++index);
                            return;
                        } 
                        arr = [...arr,...dirs.map(d => path.join(arr[index],d)) ];
                        console.log(arr.length)
                        next(++index);
                    })
                }else{
                    next(++index);
                }
            })
        }else{
            for(let i = arr.length - 1;i >= 0;i--){
                let p = arr[i];
                fs.stat(p,(err,stat) =>{
                    if (err) {
                        console.log("找不到文件"+p);
                        return;
                    }
                    if(stat.isDirectory()){
                        fs.rmdir(p,null)
                    }else{
                        fs.unlink(p,null)
                    }
                });

                if(i == 0){callback()}
            }  
        }
    }
    next(0);
}


prew("a.22" ,()=>{
    console.log("删除成功")
})