kekobin/blog

node基础篇之文件操作

Opened this issue · 0 comments

写在前面

node中的文件操作算是非常频繁的了,它有非常多的api提供了对文件及文件夹的各种操作。下面通过对常见的api的案例讲解,来了解它们的具体用法。

一 读取文件 fs.readFile()、fs.readFileSync()

fs文件操作基本都包含同步和异步两种方式,后面都会同时对每种功能提供这两种方式的讲述,不在赘述。

异步 fs.readFile(文件名,[可选参数,编码方式,如'utf-8'],callback)

fs.readFile('/Users/kekobin/node-dir-test/util.js', (err, buffer) => {
    if(err) throw err;
    console.log(buffer.toString())
})   

callback回调包含err和buffer,默认不传编码方式,返回的是buffer二进制数据,不过可以通过

buffer.toString()

转为字符串。之所以默认使用buffer,是因为二进制流占用内存小,传输和运算快,性能高。

同步 fs.readFileSync(文件名,[可选参数,编码方式,如'utf-8'])

try {
    const buffer = fs.readFileSync('/Users/kekobin/node-dir-test/util.js')
} catch(e) {}

fs.readFile()、fs.readFileSync()在读取文件时,会不断的把读取的内容缓冲到内存中,直到缓冲完整个文件,所以对于大量或者大文件的读取时,一般使用fs.createReadStream() 进行流式传输替代,减少内存的压力。

二 写入文件 fs.writeFile() fs.writeFileSync()

异步 fs.writeFile(文件名,写入的内容,[可选参数,编码方式,如'utf-8'],callback)

fs.writeFile('/Users/kekobin/node-dir-test/util.js','test writing file', (err) => {
    if(err) { console.log('写入文件失败');return; }
    console.log('写入文件成功');
})   

callback回调只有err参数,表示写入是否成功。

同步 fs.writeFileSync(文件名,写入的内容,[可选参数,编码方式,如'utf-8'])

try {
    const err = fs.writeFileSync('/Users/kekobin/node-dir-test/util.js','test writing file')   
} catch(e) {}

三 创建文件夹 fs.mkdir() fs.mkdirSync()

异步 fs.mkdir(文件名,权限,callback)

fs.mkdir('/Users/kekobin/node-dir-test/test-dir','0777', (err) => {
    if(err) { console.log('创建文件夹失败');return; }
    console.log('创建文件夹成功');
})   

"权限"指的是被创建的文件夹是否可读、可写等。如'0777'指的是root:root权限,'0750'指的是www-data:www-data权限(不过一般它也用'0755').

drwxr-x---: '0750'; drwxr-x--x: '0755';

同步 fs.mkdirSync(文件名,权限)

try {
    const err = fs.mkdirSync(''/Users/kekobin/node-dir-test/test-dir',0777)   
} catch(e) {}

四 获取已存在的文件或文件夹状态 fs.stat() fs.statSync()

往往通过这两个api判断正在处理的是文件,还是文件夹

异步 fs.stat(文件名或者文件夹名)

fs.stat('/Users/kekobin/node-dir-test/test-dir',(err, stats) => {
    if(err) throw err;
    if(stats.isFile()) {}
    if(stats.isDirectory()) {}
})   

回调中参数stats是一个包含文件或者文件夹信息的对象,最常用的是使用stats.isFile()判断是否文件,使用stats.isDirectory()判断是否文件夹。

同步 fs.statSync(文件名或者文件夹名)

try {
    const stats = fs.statSync('/Users/kekobin/node-dir-test/test-dir')   
} catch(e) {}

由于fs.exists()已经废弃了,所以判断文件或者文件夹是否存在,也可以通过这两个api判断,即读取一个文件夹,判断stats.isDirectory()是否为true,true说明存在,否则不存在。

五 读取目录 readdir(),readdirSync()

这两个api会返回一个所包含的文件和子目录的数组。

异步 fs.readdir(文件夹名)

const dir = '/Users/kekobin/node-dir-test/';
fs.readdir(dir, function (err, files) {
    if (err) {
      throw err;
      return;
    }

    files.forEach( (filename, index) => {
        const fullname = path.join(dir,filename);
        fs.stat(fullname, (err, stats) => {
            if(err) throw err;
            if(stats.isDirectory()) {

            }
            if(stats.isFile()) {
            }
        })
    });
});  

一般用于遍历文件夹,生成文件树等操作。

同步 fs.readdirSync(文件夹名)

try {
    const files = fs.readdirSync(dir)   
} catch(e) {}

六 创建文件读取流 fs.createReadStream(文件名)

往往用于打开大型的文本文件,创建一个读取操作的数据流。所谓大型文本文件,指的是文本文件的体积很大,读取操作的缓存装不下,只能分成几次发送,每次发送会触发一个data事件,发送结束会触发end事件。

let result = '';
fs.createReadStream('/Users/kekobin/node-dir-test/util.js')
.on('data', (data) => {
    result += data;
})
.on('end', () => {
    console.log('获取最终文件读取的内容', result);
})

七 创建文件写入流 fs.createWriteStream(文件名)

创建一个写入数据流对象,该对象的write方法用于写入数据,end方法用于结束写入操作。

const out = fs.createWriteStream(fileName, {
    encoding: 'utf8'
});
out.write(str);
out.end();

如createWriteStream和createReadStream配合实现拷贝大型文件。

function fileCopy(filename1, filename2, done) {
    var input = fs.createReadStream(filename1);
    var output = fs.createWriteStream(filename2);
  
    input.on('data', function(d) { output.write(d); });
    input.on('error', function(err) { throw err; });
    input.on('end', function() {
      output.end();
      if (done) done();
    });
}
// 将util.js拷贝到util2.js
fileCopy('/Users/kekobin/node-dir-test/util.js', '/Users/kekobin/node-dir-test/util2.js', function() {
    console.log('end')
})

八 删除文件 fs.unlink() fs.unlinkSync()

异步 fs.unlink(文件名)

fs.unlink('/Users/kekobin/node-dir-test/util2.js', function(err){
    if(err) throw err;
    console.log('文件删除成功');
}); 

同步 fs.unlinkSync(文件名)

try { fs.unlinkSync('/Users/kekobin/node-dir-test/util2.js') } catch(){}

九 删除目录 fs.rmdir() fs.rmdirSync()

异步 fs.rmdir(文件夹名,callback)

fs.rmdir('/Users/kekobin/node-dir-test/test-dir', function(err){
    if(err) throw err;
    console.log('目录删除成功');
}); 

同步 fs.rmdirSync(文件名)

try { fs.rmdirSync('/Users/kekobin/node-dir-test/test-dir') } catch(){}

不过,该方法只能删除空目录,不为空的是删除不了的。后者使用下面的方式删除:

var deleteFolderRecursive = function(path) {
  if( fs.existsSync(path) ) {
    fs.readdirSync(path).forEach(function(file,index){
      var curPath = path + "/" + file;
      if(fs.lstatSync(curPath).isDirectory()) { // recurse
        deleteFolderRecursive(curPath);
      } else { // delete file
        fs.unlinkSync(curPath);
      }
    });
    fs.rmdirSync(path);
  }
};
view raw

十 检查路径是否存在 fs.existsSync(path)

if(fs.existsSync(path)){}

该方法如果存在返回true,否则返回false,不会像fs.statsSync那样在文件不存在时报错

实例:同步读取某个目录下所有文件

const getFiles = function(dir){
    const results = [];
    const files = fs.readdirSync(dir, 'utf8');

    files.forEach(function(file){
        const fullname = path.resolve(dir, file);
        const stats = fs.statSync(fullname);

        if(stats.isFile()){
            results.push(fullname);
        }else if(stats.isDirectory()){
            results = results.concat( getFiles(fullname) );
        }
    });

    return results;
};

const files = getFiles('/Users/kekobin/node-dir-test/');

realpathSync realpath

获取文件的真实绝对路径

image

大文件读取

先使用程序得到一个大文件:

const fs = require('fs');
const file = fs.createWriteStream('./big.file');
for(let  i = 0;i<=1e6;i++) {
    file.write('Lorem ipsum dolor sit amet, consectetur adipisicing elit. \n');
}
file.end();

image

然后读取它:

const fs = require('fs');
const server = require('http').createServer();
server.on('request', (req, res) => {
    fs.readFile('./big.file', (err, data) => {
        if(err) throw err;
        res.end(data);
    })
});
server.listen(8000);

我们可以先看看这个进程的内存占用情况:
image

然后访问下 curl 127.0.0.1:8000 看看现在的内存占用情况:
image

很明显,通过fs.readFile 是将文件内容直接读取到内从中,小文件还行,大文件可能会将内容撑爆掉。大文件一般是使用一行一行读取的形式,node给出的方案是 readInline,这个就不深究了。

参考

文件读取
nodejs流-你需要知道的一切