node 之fs 操作檔案 ? 快取Buffer ?

渣渣的生存之道發表於2019-03-04

在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

node 之fs 操作檔案 ? 快取Buffer ?
以上就是我們的許可權 - 代表當前是檔案,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目錄是空的,此時我們需要對資料夾進行遍歷, 遍歷分先序,中序,後序,(刪除目錄一般是先序)或者深度和廣度 ;

node 之fs 操作檔案 ? 快取Buffer ?

先寫一個同步一層的

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("刪除成功")
})




複製程式碼

相關文章