NodeJs 的fs模块

<1>js 里面的fs模块

  • readFile(),readFileSync()
  • writeFile(),writeFileSync()
  • exists(path, callback)
  • mkdir(),writeFile(),readFile()
  • mkdirSync(),writeFileSync(),readFileSync()
  • readdir(),readdirSync()
  • stat()
  • watchfile(),unwatchfile()
  • createReadStream()
  • createWriteStream()

重要说明:本教程已经搬迁,此处不再维护,请访问新网址:wangdoc.com/javascript。

fsfilesystem的缩写,该模块提供本地文件的读写能力,基本上是POSIX文件操作命令的简单包装。但是,这个模块几乎对所有操作提供异步和同步两种操作方式,供开发者选择。

readFile(),readFileSync()

readFile方法用于异步读取数据。

fs.readFile('./image.png', function (err, buffer) {
  if (err) throw err;
  process(buffer);
});

readFile方法的第一个参数是文件的路径,可以是绝对路径,也可以是相对路径。注意,如果是相对路径,是相对于当前进程所在的路径(process.cwd()),而不是相对于当前脚本所在的路径。

readFile方法的第二个参数是读取完成后的回调函数。该函数的第一个参数是发生错误时的错误对象,第二个参数是代表文件内容的Buffer实例。

readFileSync方法用于同步读取文件,返回一个字符串。

var text = fs.readFileSync(fileName, 'utf8');

// 将文件按行拆成数组
text.split(/\r?\n/).forEach(function (line) {
  // ...
});

readFileSync方法的第一个参数是文件路径,第二个参数可以是一个表示配置的对象,也可以是一个表示文本文件编码的字符串。默认的配置对象是{ encoding: null, flag: 'r' },即文件编码默认为null,读取模式默认为r(只读)。如果第二个参数不指定编码(encoding),readFileSync方法返回一个Buffer实例,否则返回的是一个字符串。

不同系统的行结尾字符不同,可以用下面的方法判断。

// 方法一,查询现有的行结尾字符
var EOL =
  fileContents.indexOf('\r\n') >= 0 ? '\r\n' : '\n';

// 方法二,根据当前系统处理
var EOL =
  (process.platform === 'win32' ? '\r\n' : '\n');

writeFile(),writeFileSync()

writeFile方法用于异步写入文件。

fs.writeFile('message.txt', 'Hello Node.js', (err) => {
  if (err) throw err;
  console.log('It\'s saved!');
});

上面代码中,writeFile方法的第一个参数是写入的文件名,第二个参数是写入的字符串,第三个参数是回调函数。

回调函数前面,还可以再加一个参数,表示写入字符串的编码(默认是utf8)。

fs.writeFile('message.txt', 'Hello Node.js', 'utf8', callback);

writeFileSync方法用于同步写入文件。

fs.writeFileSync(fileName, str, 'utf8');

它的第一个参数是文件路径,第二个参数是写入文件的字符串,第三个参数是文件编码,默认为utf8。

exists(path, callback)

exists方法用来判断给定路径是否存在,然后不管结果如何,都会调用回调函数。

fs.exists('/path/to/file', function (exists) {
  util.debug(exists ? "it's there" : "no file!");
});

上面代码表明,回调函数的参数是一个表示文件是否存在的布尔值。

需要注意的是,不要在open方法之前调用exists方法,open方法本身就能检查文件是否存在。

下面的例子是如果给定目录存在,就删除它。

if (fs.existsSync(outputFolder)) {
  console.log('Removing ' + outputFolder);
  fs.rmdirSync(outputFolder);
}

mkdir(),writeFile(),readFile()

mkdir方法用于新建目录。


var fs = require('fs');

fs.mkdir('./helloDir',0777, function (err) {
  if (err) throw err;
});

mkdir接受三个参数,第一个是目录名,第二个是权限值,第三个是回调函数。

writeFile方法用于写入文件。


var fs = require('fs');

fs.writeFile('./helloDir/message.txt', 'Hello Node', function (err) {
  if (err) throw err;
  console.log('文件写入成功');
});

readFile方法用于读取文件内容。

var fs = require('fs');

fs.readFile('./helloDir/message.txt','UTF-8' ,function (err, data) {
  if (err) throw err;
  console.log(data);
});

上面代码使用readFile方法读取文件。readFile方法的第一个参数是文件名,第二个参数是文件编码,第三个参数是回调函数。可用的文件编码包括“ascii”、“utf8”和“base64”。如果没有指定文件编码,返回的是原始的缓存二进制数据,这时需要调用buffer对象的toString方法,将其转为字符串。


var fs = require('fs');
fs.readFile('example_log.txt', function (err, logData) {
  if (err) throw err;
  var text = logData.toString();
});

readFile方法是异步操作,所以必须小心,不要同时发起多个readFile请求。

for(var i = 1; i <= 1000; i++) {
  fs.readFile('./'+i+'.txt', function() {
     // do something with the file
  });
}

上面代码会同时发起1000个readFile异步请求,很快就会耗尽系统资源。

mkdirSync(),writeFileSync(),readFileSync()

这三个方法是建立目录、写入文件、读取文件的同步版本。

fs.mkdirSync('./helloDirSync',0777);
fs.writeFileSync('./helloDirSync/message.txt', 'Hello Node');
var data = fs.readFileSync('./helloDirSync/message.txt','UTF-8');
console.log('file created with contents:');
console.log(data);

对于流量较大的服务器,最好还是采用异步操作,因为同步操作时,只有前一个操作结束,才会开始后一个操作,如果某个操作特别耗时(常常发生在读写数据时),会导致整个程序停顿。

readdir(),readdirSync()

readdir方法用于读取目录,返回一个所包含的文件和子目录的数组。

fs.readdir(process.cwd(), function (err, files) {
  if (err) {
    console.log(err);
    return;
  }

  var count = files.length;
  var results = {};
  files.forEach(function (filename) {
    fs.readFile(filename, function (data) {
      results[filename] = data;
      count--;
      if (count <= 0) {
        // 对所有文件进行处理
      }
    });
  });
});

readdirSync方法是readdir方法的同步版本。下面是同步列出目录内容的代码。

var files = fs.readdirSync(dir);
files.forEach(function (filename) {
  var fullname = path.join(dir,filename);
  var stats = fs.statSync(fullname);
  if (stats.isDirectory()) filename += '/';
  process.stdout.write(filename + '\t' +
    stats.size + '\t' +
    stats.mtime + '\n'
  );
});

stat()

stat方法的参数是一个文件或目录,它产生一个对象,该对象包含了该文件或目录的具体信息。我们往往通过该方法,判断正在处理的到底是一个文件,还是一个目录。

var fs = require('fs');

fs.readdir('/etc/', function (err, files) {
  if (err) throw err;

  files.forEach( function (file) {
    fs.stat('/etc/' + file, function (err, stats) {
      if (err) throw err;

      if (stats.isFile()) {
        console.log("%s is file", file);
      }
      else if (stats.isDirectory ()) {
      console.log("%s is a directory", file);
      }
    console.log('stats:  %s',JSON.stringify(stats));
    });
  });
});

watchfile(),unwatchfile()

watchfile方法监听一个文件,如果该文件发生变化,就会自动触发回调函数。

var fs = require('fs');

fs.watchFile('./testFile.txt', function (curr, prev) {
  console.log('the current mtime is: ' + curr.mtime);
  console.log('the previous mtime was: ' + prev.mtime);
});

fs.writeFile('./testFile.txt', "changed", function (err) {
  if (err) throw err;

  console.log("file write complete");   
});

unwatchfile方法用于解除对文件的监听。

createReadStream()

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

var fs = require('fs');

function readLines(input, func) {
  var remaining = '';

  input.on('data', function(data) {
    remaining += data;
    var index = remaining.indexOf('\n');
    var last  = 0;
    while (index > -1) {
      var line = remaining.substring(last, index);
      last = index + 1;
      func(line);
      index = remaining.indexOf('\n', last);
    }

    remaining = remaining.substring(last);
  });

  input.on('end', function() {
    if (remaining.length > 0) {
      func(remaining);
    }
  });
}

function func(data) {
  console.log('Line: ' + data);
}

var input = fs.createReadStream('lines.txt');
readLines(input, func);

createWriteStream()

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

var 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();
  });
}

<2>NodeJs 里面的fs模块

Node.js 提供一组类似 UNIX(POSIX)标准的文件操作API。 Node 导入文件系统模块(fs)语法如下所示:

var fs = require("fs")

对文件的操作

文件读取

Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。
异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。
建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。

同步

//同步 所有同步的函数都是函数后面加Sync;
 var res = fs.writeFileSync("1.txt","我是写入内容");

异步

//文件的读取
fs.readFile("1.txt",function (err,data) {
     if (err){
        console.log(err)
     }else {
         console.log(data.toString())
     }
 })

文件写入

//创建: writeFile(文件名,写入内容,配置参数,回调函数) 异步
//配置参数:
/*
* a :追加
* w :写入
* r :读取
* */
fs.writeFile("2.txt","我是2.txt",{flag:"a"},function (err) {
    if(err){
        console.log(err);
    }else {
        console.log("写入成功");
    }
})

// 追加写入
fs.appendFile("2.txt","我是追加的字符",function (err) {
    if(err){
      return  console.log(err);
    }else {
        console.log("追加成功");
    }
})

文件名修改

//文件修改(文件名的修改) 
 fs.rename("5.txt","1.txt",function (err) {
     if(err){
         console.log(err)
     }else {
         console.log("修改成功");
     }
 })

 

//文件删除
 fs.unlink("2.txt",function (err) {
     if(err){
         return console.log(err)
     }else {
         console.log("删除成功")
     }
 })

由于fs模块中没有对文件的复制,我们可以自己通过以上操作封装一个

function mycopy(file1,file2) {
    //读取文件
    var res = fs.readFile(file1,function (err,data) {
        if (err){
            console.log(err)
        }else {
            var res = data.toString()
            fs.writeFile(file2,res,function (err) {
                if(err){
                    console.log(err)
                }else {
                    console.log("写入成功");
                }
            })
        }
    })
}

//
function mycopy(src,dest) {
    fs.writeFileSync(dest,fs.readFileSync(src));
}

对文件夹的操作

文件夹的操作/目录操作

// 目录创建:
// 1、1:执行-x 2、2:写-w 3、4:读-r
fs.mkdir("10",0666,function (err) {
    if(err){
        console.log(err)
    }else {
        console.log("创建成功");
    }
})

修改文件夹权限

fs.chmod("11",0777,function (err) {
    if(err){
        console.log(err)
    }else {
        console.log("修改权限成功")
    }
})

判断文件或者文件夹是否存在

fs.exists("10",function (exists) {
    if(exists){
        console.log("文件夹已存在");
    }else {
        fs.mkdir("10",0777,function (err) {
            if(err){
                return console.log(err);
            }else{
                console.log("创建成功");
            }
        })
    }
})

删除文件夹 : 只能删除空文件夹

fs.rmdir("10",function (err) {
    if(err){
        return console.log(err)
    }else {
        console.log("删除成功");
    }
})

读取文件夹

fs.readdir("10",function (err,data) {
    if(err){
        console.log(err);
    }else {
        console.log(data);
    }
})

显示文件的详细信息:

//针对详细信息来判断是否是文件
fs.stat("10",function (err,data) {
    if(err){
       return console.log(err)
    }else {
        // console.log(data);
        //判断是否是文件
        var res = data.isFile();
        //判断是否是文件夹
        // data.isDirectory();
        if(res){
            console.log("是文件")
        }else {
            console.log("是文件夹")
        }
    }
})

由于node.js中没有删除包含文件的文件夹的函数,因此我们仿写一个函数来删除包含文件的文件的函数

// 删除文件夹的函数 同步
var removeDir = function(src) {
   // 获取到文件夹里的内容
   var arr = fs.readdirSync(src);
   //判断是否是文件,如果是文件就删除;如果是文件夹再执行相同过程
    for(var i=0;i

 

<3>Node.js文件系统的API fs模块

Node.js的文件系统的Api

//公共引用
var fs = require('fs'),
path = require('path');

1、读取文件readFile函数

//readFile(filename,[options],callback);

/**
 * filename, 必选参数,文件名
 * [options],可选参数,可指定flag(文件操作选项,如r+ 读写;w+ 读写,文件不存在则创建)及encoding属性
 * callback 读取文件后的回调函数,参数默认第一个err,第二个data 数据
 */

fs.readFile(__dirname + '/test.txt', {flag: 'r+', encoding: 'utf8'}, function (err, data) {
    if(err) {
     console.error(err);
     return;
    }
    console.log(data);
});

2、写文件

// fs.writeFile(filename,data,[options],callback);
var w_data = '这是一段通过fs.writeFile函数写入的内容;\r\n';
var w_data = new Buffer(w_data);

/**
 * filename, 必选参数,文件名
 * data, 写入的数据,可以字符或一个Buffer对象
 * [options],flag,mode(权限),encoding
 * callback 读取文件后的回调函数,参数默认第一个err,第二个data 数据
 */

fs.writeFile(__dirname + '/test.txt', w_data, {flag: 'a'}, function (err) {
   if(err) {
    console.error(err);
    } else {
       console.log('写入成功');
    }
});

3、以追加方式写文件

// fs.appendFile(filename,data,[options],callback);

fs.appendFile(__dirname + '/test.txt', '使用fs.appendFile追加文件内容', function () {
  console.log('追加内容完成');
});

4、打开文件

// fs.open(filename, flags, [mode], callback);

/**
 * filename, 必选参数,文件名
 * flags, 操作标识,如"r",读方式打开
 * [mode],权限,如777,表示任何用户读写可执行
 * callback 打开文件后回调函数,参数默认第一个err,第二个fd为一个整数,表示打开文件返回的文件描述符,window中又称文件句柄
 */

fs.open(__dirname + '/test.txt', 'r', '0666', function (err, fd) {
  console.log(fd);
});

5、读文件,读取打开的文件内容到缓冲区中;

//fs.read(fd, buffer, offset, length, position, callback);
/**
 * fd, 使用fs.open打开成功后返回的文件描述符
 * buffer, 一个Buffer对象,v8引擎分配的一段内存
 * offset, 整数,向缓存区中写入时的初始位置,以字节为单位
 * length, 整数,读取文件的长度
 * position, 整数,读取文件初始位置;文件大小以字节为单位
 * callback(err, bytesRead, buffer), 读取执行完成后回调函数,bytesRead实际读取字节数,被读取的缓存区对象
 */

fs.open(__dirname + '/test.txt', 'r', function (err, fd) {
  if(err) {
    console.error(err);
    return;
  } else {
    var buffer = new Buffer(255);
    console.log(buffer.length);
    //每一个汉字utf8编码是3个字节,英文是1个字节
    fs.read(fd, buffer, 0, 9, 3, function (err, bytesRead, buffer) {
      if(err) {
        throw err;
      } else {
        console.log(bytesRead);
        console.log(buffer.slice(0, bytesRead).toString());
        //读取完后,再使用fd读取时,基点是基于上次读取位置计算;
        fs.read(fd, buffer, 0, 9, null, function (err, bytesRead, buffer) {
          console.log(bytesRead);
          console.log(buffer.slice(0, bytesRead).toString());
        });
      }
    });
  }
});

6、写文件,将缓冲区内数据写入使用fs.open打开的文件

//fs.write(fd, buffer, offset, length, position, callback);

/**
 * fd, 使用fs.open打开成功后返回的文件描述符
 * buffer, 一个Buffer对象,v8引擎分配的一段内存
 * offset, 整数,从缓存区中读取时的初始位置,以字节为单位
 * length, 整数,从缓存区中读取数据的字节数
 * position, 整数,写入文件初始位置;
 * callback(err, written, buffer), 写入操作执行完成后回调函数,written实际写入字节数,buffer被读取的缓存区对象
 */

fs.open(__dirname + '/test.txt', 'a', function (err, fd) {
  if(err) {
    console.error(err);
    return;
  } else {
    var buffer = new Buffer('写入文件数据内容');
    //写入'入文件'三个字
    fs.write(fd, buffer, 3, 9, 12, function (err, written, buffer) {
      if(err) {
        console.log('写入文件失败');
        console.error(err);
        return;
      } else {
        console.log(buffer.toString());
        //写入'数据内'三个字
        fs.write(fd, buffer, 12, 9, null, function (err, written, buffer) {
          console.log(buffer.toString());
        })
      }
    });
  }
});

7、刷新缓存区;

// 使用fs.write写入文件时,操作系统是将数据读到内存,再把数据写入到文件中,当数据读完时并不代表数据已经写完,因为有一部分还可能在内在缓冲区内。
// 因此可以使用fs.fsync方法将内存中数据写入文件;--刷新内存缓冲区;

//fs.fsync(fd, [callback])
/**
 * fd, 使用fs.open打开成功后返回的文件描述符
 * [callback(err, written, buffer)], 写入操作执行完成后回调函数,written实际写入字节数,buffer被读取的缓存区对象
 */

fs.open(__dirname + '/test.txt', 'a', function (err, fd) {
  if(err)
    throw err;
  var buffer = new Buffer('我爱nodejs编程');
  fs.write(fd, buffer, 0, 9, 0, function (err, written, buffer) {
    console.log(written.toString());
    fs.write(fd, buffer, 9, buffer.length - 9, null, function (err, written) {
      console.log(written.toString());
      fs.fsync(fd);
      fs.close(fd);
    })
  });
});

8、创建目录;

//使用fs.mkdir创建目录
//fs.mkdir(path, [mode], callback);

/**
 * path, 被创建目录的完整路径及目录名;
 * [mode], 目录权限,默认0777
 * [callback(err)], 创建完目录回调函数,err错误对象
 */

fs.mkdir(__dirname + '/fsDir', function (err) {
  if(err)
    throw err;
  console.log('创建目录成功')
});

9、读取目录;

//使用fs.readdir读取目录,重点其回调函数中files对象
//fs.readdir(path, callback);

/**
 * path, 要读取目录的完整路径及目录名;
 * [callback(err, files)], 读完目录回调函数;err错误对象,files数组,存放读取到的目录中的所有文件名
 */

fs.readdir(__dirname + '/fsDir/', function (err, files) {
  if(err) {
    console.error(err);
    return;
  } else {
    files.forEach(function (file) {
      var filePath = path.normalize(__dirname + '/fsDir/' + file);
      fs.stat(filePath, function (err, stat) {
        if(stat.isFile()) {
          console.log(filePath + ' is: ' + 'file');
        }
        if(stat.isDirectory()) {
          console.log(filePath + ' is: ' + 'dir');
        }
      });
    });
    for (var i = 0; i < files.length; i++) {
      //使用闭包无法保证读取文件的顺序与数组中保存的致
      (function () {
        var filePath = path.normalize(__dirname + '/fsDir/' + files[i]);
        fs.stat(filePath, function (err, stat) {
          if(stat.isFile()) {
            console.log(filePath + ' is: ' + 'file');
          }
          if(stat.isDirectory()) {
            console.log(filePath + ' is: ' + 'dir');
          }
        });
      })();
    }
  }
});

10、查看文件与目录的信息;

//fs.stat(path, callback);
//fs.lstat(path, callback); //查看符号链接文件
/**
 * path, 要查看目录/文件的完整路径及名;
 * [callback(err, stats)], 操作完成回调函数;err错误对象,stat fs.Stat一个对象实例,提供如:isFile, isDirectory,isBlockDevice等方法及size,ctime,mtime等属性
 */

//实例,查看fs.readdir

11、查看文件与目录的是否存在

//fs.exists(path, callback);

/**
 * path, 要查看目录/文件的完整路径及名;
 * [callback(exists)], 操作完成回调函数;exists true存在,false表示不存在
 */

fs.exists(__dirname + '/te', function (exists) {
  var retTxt = exists ? retTxt = '文件存在' : '文件不存在';
  console.log(retTxt);
});

12、修改文件访问时间与修改时间

//fs.utimes(path, atime, mtime, callback);

/**
 * path, 要查看目录/文件的完整路径及名;
 * atime, 新的访问时间
 * ctime, 新的修改时间
 * [callback(err)], 操作完成回调函数;err操作失败对象
 */

fs.utimes(__dirname + '/test.txt', new Date(), new Date(), function (err) {
  if(err) {
    console.error(err);
    return;
  }
  fs.stat(__dirname + '/test.txt', function (err, stat) {
    console.log('访问时间: ' + stat.atime.toString() + '; \n修改时间:' + stat.mtime);
    console.log(stat.mode);
  })
});

13、修改文件或目录的操作权限

//fs.utimes(path, mode, callback);

/**
 * path, 要查看目录/文件的完整路径及名;
 * mode, 指定权限,如:0666 8进制,权限:所有用户可读、写,
 * [callback(err)], 操作完成回调函数;err操作失败对象
 */

fs.chmod(__dirname + '/fsDir', 0666, function (err) {
  if(err) {
    console.error(err);
    return;
  }
  console.log('修改权限成功')
});

14、移动/重命名文件或目录

//fs.rename(oldPath, newPath, callback);

/**
 * oldPath, 原目录/文件的完整路径及名;
 * newPath, 新目录/文件的完整路径及名;如果新路径与原路径相同,而只文件名不同,则是重命名
 * [callback(err)], 操作完成回调函数;err操作失败对象
 */
fs.rename(__dirname + '/test', __dirname + '/fsDir', function (err) {
  if(err) {
    console.error(err);
    return;
  }
  console.log('重命名成功')
});

15、删除空目录

//fs.rmdir(path, callback);

/**
 * path, 目录的完整路径及目录名;
 * [callback(err)], 操作完成回调函数;err操作失败对象
 */

fs.rmdir(__dirname + '/test', function (err) {
  fs.mkdir(__dirname + '/test', 0666, function (err) {
    console.log('创建test目录');
  });
  if(err) {
    console.log('删除空目录失败,可能原因:1、目录不存在,2、目录不为空')
    console.error(err);
    return;
  }
  console.log('删除空目录成功!');
});

16、监视文件

//对文件进行监视,并且在监视到文件被修改时执行处理
//fs.watchFile(filename, [options], listener);

/**
 * filename, 完整路径及文件名;
 * [options], persistent true表示持续监视,不退出程序;interval 单位毫秒,表示每隔多少毫秒监视一次文件
 * listener, 文件发生变化时回调,有两个参数:curr为一个fs.Stat对象,被修改后文件,prev,一个fs.Stat对象,表示修改前对象
 */
 
fs.watchFile(__dirname + '/test.txt', {interval: 20}, function (curr, prev) {
  if(Date.parse(prev.ctime) == 0) {
    console.log('文件被创建!');
  } else if(Date.parse(curr.ctime) == 0) {
    console.log('文件被删除!')
  } else if(Date.parse(curr.mtime) != Date.parse(prev.mtime)) {
    console.log('文件有修改');
  }
});
fs.watchFile(__dirname + '/test.txt', function (curr, prev) {
  console.log('这是第二个watch,监视到文件有修改');
});

17、取消监视文件

//取消对文件进行监视
//fs.unwatchFile(filename, [listener]);

/**
 * filename, 完整路径及文件名;
 * [listener], 要取消的监听器事件,如果不指定,则取消所有监听处理事件
 */

var listener = function (curr, prev) {
  console.log('我是监视函数')
}
fs.unwatchFile(__dirname + '/test.txt', listener);

18、监视文件或目录

// 对文件或目录进行监视,并且在监视到修改时执行处理;
// fs.watch返回一个fs.FSWatcher对象,拥有一个close方法,用于停止watch操作;
// 当fs.watch有文件变化时,会触发fs.FSWatcher对象的change(err, filename)事件,err错误对象,filename发生变化的文件名
// fs.watch(filename, [options], [listener]);

/**
 * filename, 完整路径及文件名或目录名;
 * [listener(event, filename], 监听器事件,有两个参数:event 为rename表示指定的文件或目录中有重命名、删除或移动操作或change表示有修改,filename表示发生变化的文件路径
 */

var fsWatcher = fs.watch(__dirname + '/test', function (event, filename) {
  //console.log(event)
});

//console.log(fsWatcher instanceof FSWatcher);

fsWatcher.on('change', function (event, filename) {
  console.log(filename + ' 发生变化')
});

//30秒后关闭监视
setTimeout(function () {
  console.log('关闭')
  fsWatcher.close(function (err) {
    if(err) {
      console.error(err)
    }
    console.log('关闭watch')
  });
}, 30000);

19、文件流

/*
 * 流,在应用程序中表示一组有序的、有起点有终点的字节数据的传输手段;
 * Node.js中实现了stream.Readable/stream.Writeable接口的对象进行流数据读写;以上接口都继承自EventEmitter类,因此在读/写流不同状态时,触发不同事件;
 * 关于流读取:Node.js不断将文件一小块内容读入缓冲区,再从缓冲区中读取内容;
 * 关于流写入:Node.js不断将流数据写入内在缓冲区,待缓冲区满后再将缓冲区写入到文件中;重复上面操作直到要写入内容写写完;
 * readFile、read、writeFile、write都是将整个文件放入内存而再操作,而则是文件一部分数据一部分数据操作;
 *
 * -----------------------流读取-------------------------------------
 * 读取数据对象:
 * fs.ReadStream 读取文件
 * http.IncomingMessage 客户端请求或服务器端响应
 * net.Socket    Socket端口对象
 * child.stdout  子进程标准输出
 * child.stdin   子进程标准入
 * process.stdin 用于创建进程标准输入流
 * Gzip、Deflate、DeflateRaw   数据压缩
 *
 * 触发事件:
 * readable  数据可读时
 * data      数据读取后
 * end       数据读取完成时
 * error     数据读取错误时
 * close     关闭流对象时
 *
 * 读取数据的对象操作方法:
 * read      读取数据方法
 * setEncoding   设置读取数据的编
 * pause     通知对象众目停止触发data事件
 * resume    通知对象恢复触发data事件
 * pipe      设置数据通道,将读入流数据接入写入流;
 * unpipe    取消通道
 * unshift   当流数据绑定一个解析器时,此方法取消解析器
 *
 * ------------------------流写入-------------------------------------
 * 写数据对象:
 * fs.WriteStream           写入文件对象
 * http.clientRequest       写入HTTP客户端请求数据
 * http.ServerResponse      写入HTTP服务器端响应数据
 * net.Socket               读写TCP流或UNIX流,需要connection事件传递给用户
 * child.stdout             子进程标准输出
 * child.stdin              子进程标准入
 * Gzip、Deflate、DeflateRaw  数据压缩
 *
 * 写入数据触发事件:
 * drain            当write方法返回false时,表示缓存区中已经输出到目标对象中,可以继续写入数据到缓存区
 * finish           当end方法调用,全部数据写入完成
 * pipe             当用于读取数据的对象的pipe方法被调用时
 * unpipe           当unpipe方法被调用
 * error            当发生错误
 *
 * 写入数据方法:
 * write            用于写入数据
 * end              结束写入,之后再写入会报错;
 */

20、创建读取流

//fs.createReadStream(path, [options])
/**
 * path 文件路径
 * [options] flags:指定文件操作,默认'r',读操作;encoding,指定读取流编码;autoClose, 是否读取完成后自动关闭,默认true;start指定文件开始读取位置;end指定文件开始读结束位置
 */

var rs = fs.createReadStream(__dirname + '/test.txt', {start: 0, end: 2});
  //open是ReadStream对象中表示文件打开时事件,
rs.on('open', function (fd) {
  console.log('开始读取文件');
});

rs.on('data', function (data) {
  console.log(data.toString());
});

rs.on('end', function () {
  console.log('读取文件结束')
});
rs.on('close', function () {
  console.log('文件关闭');
});

rs.on('error', function (err) {
  console.error(err);
});

//暂停和回复文件读取;
rs.on('open', function () {
  console.log('开始读取文件');
});

rs.pause();

rs.on('data', function (data) {
  console.log(data.toString());
});

setTimeout(function () {
  rs.resume();
}, 2000);

21、创建写入流

//fs.createWriteStream(path, [options])
/**
 * path 文件路径
 * [options] flags:指定文件操作,默认'w',;encoding,指定读取流编码;start指定写入文件的位置
 */

/* ws.write(chunk, [encoding], [callback]);
 * chunk,  可以为Buffer对象或一个字符串,要写入的数据
 * [encoding],  编码
 * [callback],  写入后回调
 */

/* ws.end([chunk], [encoding], [callback]);
 * [chunk],  要写入的数据
 * [encoding],  编码
 * [callback],  写入后回调
 */

var ws = fs.createWriteStream(__dirname + '/test.txt', {start: 0});
var buffer = new Buffer('我也喜欢你');
ws.write(buffer, 'utf8', function (err, buffer) {
  console.log(arguments);
  console.log('写入完成,回调函数没有参数')
});
//最后再写入的内容
ws.end('再见');
//使用流完成复制文件操作
var rs = fs.createReadStream(__dirname + '/test.txt')
var ws = fs.createWriteStream(__dirname + '/test/test.txt');

rs.on('data', function (data) {
  ws.write(data)
});

ws.on('open', function (fd) {
  console.log('要写入的数据文件已经打开,文件描述符是: ' + fd);
});

rs.on('end', function () {
  console.log('文件读取完成');
  ws.end('完成', function () {
    console.log('文件全部写入完成')
  });
});

 
//关于WriteStream对象的write方法返回一个布尔类型,当缓存区中数据全部写满时,返回false;
//表示缓存区写满,并将立即输出到目标对象中
 
//第一个例子
var ws = fs.createWriteStream(__dirname + '/test/test.txt');
for (var i = 0; i < 10000; i++) {
  var w_flag = ws.write(i.toString());
  //当缓存区写满时,输出false
  console.log(w_flag);
}


//第二个例子
var ws = fs.createWriteStream(__dirname + '/test/untiyou.mp3');
var rs = fs.createReadStream(__dirname + '/test/Until You.mp3');
rs.on('data', function (data) {
  var flag = ws.write(data);
  console.log(flag);
});

//系统缓存区数据已经全部输出触发drain事件
ws.on('drain', function () {
  console.log('系统缓存区数据已经全部输出。')
});

22、管道pipe实现流读写

//rs.pipe(destination, [options]);
/**
 * destination 必须一个可写入流数据对象
 * [opations] end 默认为true,表示读取完成立即关闭文件;
 */

var rs = fs.createReadStream(__dirname + '/test/Until You.mp3');
var ws = fs.createWriteStream(__dirname + '/test/untiyou.mp3');
rs.pipe(ws);
rs.on('data', function (data) {
  console.log('数据可读')
});
rs.on('end', function () {
  console.log('文件读取完成');
  //ws.end('再见')
});


<4>node.js高阶模块-fs模块

 NodeJs只能做的两件事是什么?
实际上,所有后台语言都能做的两件事是: 文件操作和网络编程.
这其实是所有语言的根本。 计算机无外乎就是文件和通信。Linux中,是把一切都当做文件,如果理解了这一点那就无可厚非了.
所以,这里,我想介绍一下NodeJS中一个重要的模块--fs.
这里我给大家放一个我的框架图~

(为什么不是http? 懒~)
let's start.
针对于fs,我们切实围绕几个问题来吧~

  1. fs是如何操作文件的?

  2. drain和write到底是什么关系?

  3. fs怎么写出向gulp那样实时监听文件改变的插件?

关于fs的API,直接参考Nodejs官网. 同样,放上fs的基本架构图:
NodeJs 的fs模块_第1张图片
(图有点大,大家另外开一个窗口看吧)
我们围绕这些问题来展开,说明吧.

fs操作文件的几种方式

这里,我们针对文件最基本的两种操作进行相关的解释和说明吧--read&&write
读写文件有哪几种操作方式呢?
我们先从最简便的开始吧~

先熟悉API: fs.createReadStream(path[, options]) path就是打开的文件路径,options有点复杂:

{
  flags: 'r',
  encoding: null,
  fd: null,
  mode: 0o666,
  autoClose: true
}

实际上我们一般也用不到options,除非你是获取已经打开后的文件.具体描述详见.官网.
ok~ 现在正式打开一个文件:

const fs = require('fs');
const read = fs.createReadStream('sam.js',{encoding:'utf8'});
read.on('data',(str)=>{
    console.log(str);
})
read.on('end',()=>{
    console.log('have already opened');
})

实际上,我们就是利用fs继承的readStream来进行操作的.

使用open打开文件
同样上:API:
fs.open(path, flags[, mode], callback)这个和上面的readStream不同,open打开文件是一个持续状态,相当于会将文件写入到内存当中. 而readStream只是读取文件,当读取完毕时则会自动关闭文件--相当于fs.open+fs.close两者的结合~ 其中flags和mode 就是设置打开文件的权限,以及文件的权限模式(rwx). 
使用open来打开一个文件

const fs = require('fs');
fs.open('sam.js','r',(err,fd)=>{
    fs.fstat(fd,(err,stat)=>{
        var len = stat.size;  //检测文件长度
        var buf = new Buffer(len);
        fs.read(fd,buf,0,len,0,(err,bw,buf)=>{
            console.log(buf.toString('utf8'));
            fs.close(fd);
        })
    });
});

使用相关的read/readdir/readFile/readlink
read方法,使用来读取已经打开后的文件。 他不用用来进行打开文件操作,这点很重要》 那还有其他方法,在读的过程可以直接打开文件吗? 
absolutely~
这里就拿readFile和readdir举例吧
API
fs.readFile(file[, options], callback): file就是文件路径,options可以为object也可以为string. 不过最常用的还是str. 我们直接看demo:

const fs = require('fs');
fs.readFile('sam.js','utf8',(err,data)=>{
    console.log(`the content is ,${data}`);
})

另外一个readdir,顾名思义该API就是用来读取文件夹的.实际上,该API也没有什么卵用~
fs.readdir(path, callback):用来获取文件下所有的文件(包括目录),并且不会进行recursive.并且callback(err,files)中的files只是以数组的形式放回该目录下所有文件的名字
show u code:

//用来检查,上层目录中那些是file,那些是dir
const fs = require('fs');
fs.readdir('..', (err,files)=>{
    var path,stat;
    files.map((val)=>{
        path = `../${val}`;
        stat= fs.statSync(path);
        if(stat.isFile()){
            console.log(`file includes ${val}`);
        }else if(stat.isDirectory()){
            console.log(`dir includes ${val}`);
        }
    })
})

nodejs 打开文件的所有方式就是以上这几种.接下来我们再来看一下,如果写入文件吧~

写入文件

同样,先介绍最简单的吧.

fs.createWriteStream(path[, options]): path就是文件路径.而options和上面的createWriteStream一样比较复杂;

{
  flags: 'w',
  defaultEncoding: 'utf8',
  fd: null,
  mode: 0o666
}

实际上,我们只需要写好path就enough了.
直接看demo吧:

//用来写入str的操作
const fs = require('fs');
const write = fs.createWriteStream('sam.js');
write.on('drain',()=>{
    write.resume();
});
var writeData = function(){
    var i = 1000;
    while(i--){
        if(!write.write('sam')){
            write.pause();
        }
    }
}
writeData();

实际上,上面那段代码是最常用的写入文件的写法.drain是代表,写入内存已经清空后,可以继续写入时触发的事件.这就是第二个问题: drain和write到底是什么关系? 这个问题,我们放到后面讲解,这里先继续说一下如何写入内容.

使用fs.write方法直接写入内容:
fs.writeAPI其实就有两个:
fs.write(fd, buffer, offset, length[, position], callback):这一种,是用来直接写入Buffer数据内容的. 
fs.write(fd, data[, position[, encoding]], callback):这一种,是用来写入str数据内容的.
不过,fs.write()该方法,也是建立在已有文件打开的基础上的.
直接看一下demo:

//使用Buffer写入
const fs = require('fs');
fs.open('sam.js','w+',(err,fd)=>{
    var buf = new Buffer("sam",'utf8');
    fs.write(fd,buf,0,buf.length,0,(err,bw,buf)=>{
        fs.close(fd);
    });
})
//直接使用string写入:
const fs = require('fs');
fs.open('sam.js','w+',(err,fd)=>{
    fs.write(fd,'sam','utf8',0,(err,bw,buf)=>{
        fs.close(fd);
    });
})

通常情况下,我们也不会用来写入Buffer的. 所以,第二种方法就足够了.

同理,能否直接写入未打开的文件呢?
当然是可以的,所以这里介绍最后一种方法. 使用writeFile和appendFile来写入数据.

fs.writeFile(file, data[, options], callback):直接写入指定文件. 写入的内容会直接覆盖掉原始内容.
fs.appendFile(file, data[, options], callback):真正的用来append file

//检测文件是否存在,如果存在则增加内容,否则新建文件并写入内容.
const fs = require('fs');
var writeData = function() {
    fs.access('sam.js', (noAccess) => {
        if (noAccess) {
            fs.writeFile('sam.js', 'sam', (err) => {
                if (!err) console.log('writeFile success')
            })
        } else {
            fs.appendFile('sam.js', 'sam', (err) => {
                if (!err) console.log('appendFile success~');
            });
        }
    })
}
writeData()

大致梳理一下上面说的内容吧:
NodeJs 的fs模块_第2张图片

drain和stream.write的关联

首先这两个东西,是底层writeStream提供的. write这个方法不用解释了吧~ 关键drain到底怎么使用~ 这也是官网没说清楚的地方:

If a stream.write(chunk) call returns false, then the 'drain' event will indicate when it is appropriate to begin writing more data to the stream.

实际上,我们判断用没用到drain事件的机制,是根据write方法的返回值来进行判断的. 官方也给出一个demo,用来测试drain事件的触发.

const fs = require('fs');
const writer = fs.createWriteStream('sam.js');
writeOneMillionTimes(writer,'sam','utf8',()=>{});

没错,这样确实会多次触发drain事件.但是,他到底是什么时候会触发呢?
根据源码的介绍,write方法在使用时,会内置一个Buffer用来写入数据.我们可以理解该Buffer就是该次写入的最大内存值~ 那到底是多少呢? 源码:

var defaultHwm = this.objectMode ? 16 : 16 * 1024;
//即,默认为16KB

相当于,我们每次写入都会有16KB的空间开着~ 如果写入data已经填满了16KB, 而我们还继续写入就有可能造成 memory leak~ 这就go die了。轻者卡一卡,重则死机都有可能. 那如果按照官网那种写法的话,每次写入一个大文件,都要写入老长老长的函数呢?
伦家~才不要~
实际上,我们直接提炼一下,使用stream.once('drain')事件来进行处理.

if(!stream.write(data))stream.once('drain',()=>{
    stream.write(otherData);
})

或者当你使用readStream和writeStream用来读写文件时~可以使用

//试一试读取一个较大的文件,你会发现drain事件也会触发~ 所以我们需要使用pause和resume来暂停流的读取,防止memory leak~
const fs = require('fs');
const read = fs.createReadStream('唱歌的孩子.mp3');
const write = fs.createWriteStream('get.mp3');
read.on('data',(chunk)=>{
    if(!write.write(chunk))read.pause();
});
write.on('drain',()=>{
    console.log('drain');
    read.resume();
})
read.on('end',()=>{
    console.log(`finish`);
})

如何写出像gulp一样监听文件变化的插件呢?

首先,我们看一下监听插件的配置:

gulp.task('sync', function() {
    var files = [
        'app/**/*.html',
        'app/styles/**/*.css',
        'app/img/**/*.png',
        'app/src/**/*.js'
    ];
    browserSync.init(files, {
        server: {
            baseDir: './app'
        }
    });
});

首先,我们设置了files之后,就可以监听文件,并且开启一个服务~
而实际上,就是使用Nodejs底层的fs.watch对文件进行监听.我们来使用fs.watch和fs.watchFile来实现文件的监听~
这里,我们先从简单的watchFile入手~ 
根据nitoyon的解释,我们可以得出两个结论

  • fs.watch() uses native API

  • fs.watchFile() periodically executes fs.stat()

所以,底层上来看,其实fs.watchFile是周期性执行fs.stat的,速度上来看,肯定会慢的. 不多说了,我们看一下demo:

const fs = require('fs');
fs.watchFile('sam.js', {
    persistent:true,
    interval:3000
}, (cur,prev)=>{
    if(cur.mtime>prev.mtime){
        console.log('change');
        console.log(cur,prev);
    }
})

这里,主要想谈及一下watchFile中的第二个参数,options中的interval. 这个东西有点傻逼~ 为什么呢? 因为,他并不是在一定时间内,触发watch,而是在第一次触发后的interval时间内,不会触发watch. 即,他会发生改变的积累~ 在interval时间内改变的内容,只会在最后一次中呈现出来~ 而他的底层其实就是调用fs.stat来完成的.这里,我们使用fs.stat来模仿一遍~

const fs = require('fs'),
    Event = require('events').EventEmitter,
    event = new Event();


//原始方法getCur
//原始属性prev
var watchFile = function(file,interval,cb){
    var pre,cur;
    var getPrv = function(file){
        var stat = fs.statSync(file);
        return stat;
    }
    var getCur = function(file){
        cur = getPrv(file);
        console.log(cur,pre);
        if(cur.mtime.toString()!==pre.mtime.toString()){
            cb('change');
        }
        pre = cur; //改变初始状态
    }
    var init = (function(){
        pre = getPrv(file); //首先获取pre
        event.on('change',function(){
            getCur(file);
        });
        setInterval(()=>{
            event.emit('change');
        },interval);
    })()
}
watchFile('sam.js',2000,function(eventname){
    console.log(eventname);
})

上述,完善了一下,在指定时间内,对文件改动进行监听,和fs.watchFile不同.
ok~ 这个out-of-date的监听方式,我们大致了解了. 接下来我们来看一下,如何使用v0.5.x版本退出的新API:fs.watch. 我们参考官网:

fs.watch should be used instead of fs.watchFile and fs.unwatchFile when possible.

为什么呢?
不为什么. 因为,fs.watch调用的是native API。而fs.watchFile是调用的是fs.stat. 比起来,时间肯定会慢一点.

那怎么使用fs.watch监听文件呢?
先看一下API吧:
fs.watch(filename, options):其实和fs.watchFile没差多少. 不多options里面有一个参数不同:

{ persistent: true, recursive: false }

即,该API不仅可以监听文件,还可以监听目录.其中recursive表示递归,用来监听目录下的文件。 不过NodeJS如是说:

The recursive option is only supported on OS X and Windows.

懂了吧. 不过基本上, 该API的覆盖率也足够了.别告诉我,你用linxu写代码.

const fs = require('fs');
fs.watch('..',{recursive:true},function(event,filename){
    console.log(`event is ${event} and filename is ${filename}`);
})

在MAC OX 11完美通过. 每当保存一次,就会触发一次。不过当你修改文件名时,便会触发两次. 一次是,原文件被修改,另一次是新文件被创建.即.

event is rename and filename is app/sam.html
event is rename and filename is app/index.html

<5>node.js里面的fs用法详解

Node.js之fs用法详解

2018-01-22

Node.js 内置的fs模块就是文件系统模块,负责读写文件。和所有其他JS模块不同的是,fs模块同时提供了异步和同步的方法。

  • 文件写入
1
2
3
4
5
6
7
8
9
var fs = require("fs");
//       要写入的文件   要写入的内容       a追加|w写入(默认)|r(读取)  回调函数
fs.writeFile("11.txt","我是要写入的11.txt文件的内容",{flag:"a"},function (err) {
    if(err){
        return console.log(err);
    }else {
        console.log("写入成功");
    }
})

运行上述代码的时候,会发现该父级文件夹下会自动生成一个11.txt文件。

1
2
3
4
5
6
7
8
fs.appendFile("11.txt","这是要追加的内容",function (err) {
    if(err){
        return console.log(err);
    }else {
        console.log("追加成功");
    }

})

NodeJs 的fs模块_第3张图片

因为是追加的内容,所以内容会自动在该文件后面

上面说的方法都是异步操作,异步操作会返回一个回调函数,在回调函数里面执行结束语句,不然会出现错误

而所有的同步函数,都只是在异步函数后面加上Sync

1
2
var res = fs.writeFileSync("11.txt","这里面是使用同步方法写的内容");
console.log(res);
  • 文件读取

异步方法读取文件

1
2
3
4
5
6
7
8
9
//文件读取
fs.readFile("11.txt",function (err,data) {
    if(err){
        return console.log(err);
    }else {
        //toString() 将buffer格式转化为中文
        console.log(data.toString());
    }
})

如果使用同步的方法,不需要在后面使用回调方法

1
2
var data = fs.readFileSync("11.txt");
console.log(data.toString());
  • 文件修改
1
2
3
4
5
6
7
8
//    要修改名字的文件  修改后的名字  回调函数
fs.rename("11.txt","22.txt",function (err) {
    if(err){
        console.log(err);
    }else {
        console.log("修改成功");
    }
})
  • 文件删除
1
2
3
4
5
6
7
8
//删除文件
fs.unlink("11.txt",function (err) {
    if(err){
        return console.log(err);
    }else {
        console.log("删除成功");
    }
})
  • 文件复制(先读取,在复制)

异步方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fs.readFile("22.txt",function (err,data) {
    if(err){
        return console.log(err);
    }else{
        var getData = data.toString();
        fs.writeFile("33.txt",getData,function (err) {
            if(err){
                return console.log(err);
            }else {
                console.log("复制欧克");
            }
        })
    }
})

同步方法,相比异步少了很对回调

1
2
var res = fs.writeFileSync("44.txt",fs.readFileSync("22.txt"));
console.log(res);
  • 文件夹创建
1
2
3
4
5
6
7
8
9
//文件夹创建
//1 -- 执行   2 -- 写入  4 -- 读取  7=1+2+4  以为创建的文件夹可执行可读可写
fs.mkdir("img",0777,function (err) {
    if(err){
        console.log(err);
    }else {
        console.log("创建成功");
    }
})

上述的权限就是在文件简介里面权限
NodeJs 的fs模块_第4张图片

  • 修改文件夹权限
1
2
3
4
5
6
7
fs.chmod("img",0333,function (err) {
    if(err){
        return console.log(err);
    }else {
        console.log("修改ok");
    }
})

NodeJs 的fs模块_第5张图片

  • 修改文件夹名字,与修改文件是同一个函数
1
2
3
4
5
6
7
8
//修改文件夹名称
fs.rename("img","image",function (err) {
    if(err){
        return console.log(err);
    }else {
        console.log("修好");
    }
})
  • 判断某个文件件是否存在,如果不存在创建,exists函数,是唯一一个回调函数中不带err的回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
fs.exists("img",function (exists) {
    if(exists){
        console.log("该文件夹已经存在");
    }else {
        fs.mkdir("img",function (err) {
            if(err){
                return console.log(err);
            }else {
                console.log("创建成功");
            }
        })
    }
})
  • 删除文件夹(只能删除空的文件夹)
1
2
3
4
5
6
7
fs.rmdir("img",function (err) {
    if(err){
        return console.log(err);
    }else {
        console.log("删除成功");
    }
})
  • 读取文件夹里面的信息
1
2
3
4
5
6
7
fs.readdir("image",function (err,data) {
    if(err){
        console.log(err);
    }else {
        console.log(data);
    }
})

NodeJs 的fs模块_第6张图片

  • 判断一个位置问价是否是文件或者是文件件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    fs.stat("image",function (err,data) {
        if(err){
            return console.log(err);
        }else {
            //判断是否是文件
            if(data.isFile()){
                //是文件
                console.log("yes");
            }else{
                //是文件夹
                console.log("no");
            }
        }
    })
    
  • 删除非空文件夹
    首先获取到该文件夹里面所有的信息,遍历里面的信息,判断是文件还是文件夹,如果是文件直接删除,如果是文件,进入文件,重复上述过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function delFile(url) {
    var data = fs.readdirSync(url);
    for(var i = 0;i < data.length;i++){
        // console.log(data[i])
        var path = url + "/" +data[i];
        console.log(path);
        var stat = fs.statSync(path);
        if(stat.isFile()){
            fs.unlinkSync(path);
        }else{
            delFile(path);
        }
    }
    fs.rmdirSync(url);
}
delFile("image");

 

 

你可能感兴趣的:(前端环境)