重要说明:本教程已经搬迁,此处不再维护,请访问新网址:wangdoc.com/javascript。
fs
是filesystem
的缩写,该模块提供本地文件的读写能力,基本上是POSIX文件操作命令的简单包装。但是,这个模块几乎对所有操作提供异步和同步两种操作方式,供开发者选择。
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
方法用于异步写入文件。
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方法用来判断给定路径是否存在,然后不管结果如何,都会调用回调函数。
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方法用于新建目录。
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异步请求,很快就会耗尽系统资源。
这三个方法是建立目录、写入文件、读取文件的同步版本。
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
方法用于读取目录,返回一个所包含的文件和子目录的数组。
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方法的参数是一个文件或目录,它产生一个对象,该对象包含了该文件或目录的具体信息。我们往往通过该方法,判断正在处理的到底是一个文件,还是一个目录。
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方法监听一个文件,如果该文件发生变化,就会自动触发回调函数。
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
方法往往用于打开大型的文本文件,创建一个读取操作的数据流。所谓大型文本文件,指的是文本文件的体积很大,读取操作的缓存装不下,只能分成几次发送,每次发送会触发一个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
方法创建一个写入数据流对象,该对象的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();
});
}
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
//公共引用
var fs = require('fs'),
path = require('path');
//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);
});
// 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('写入成功');
}
});
// fs.appendFile(filename,data,[options],callback);
fs.appendFile(__dirname + '/test.txt', '使用fs.appendFile追加文件内容', function () {
console.log('追加内容完成');
});
// 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);
});
//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());
});
}
});
}
});
//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());
})
}
});
}
});
// 使用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);
})
});
});
//使用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('创建目录成功')
});
//使用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');
}
});
})();
}
}
});
//fs.stat(path, callback);
//fs.lstat(path, callback); //查看符号链接文件
/**
* path, 要查看目录/文件的完整路径及名;
* [callback(err, stats)], 操作完成回调函数;err错误对象,stat fs.Stat一个对象实例,提供如:isFile, isDirectory,isBlockDevice等方法及size,ctime,mtime等属性
*/
//实例,查看fs.readdir
//fs.exists(path, callback);
/**
* path, 要查看目录/文件的完整路径及名;
* [callback(exists)], 操作完成回调函数;exists true存在,false表示不存在
*/
fs.exists(__dirname + '/te', function (exists) {
var retTxt = exists ? retTxt = '文件存在' : '文件不存在';
console.log(retTxt);
});
//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);
})
});
//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('修改权限成功')
});
//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('重命名成功')
});
//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('删除空目录成功!');
});
//对文件进行监视,并且在监视到文件被修改时执行处理
//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,监视到文件有修改');
});
//取消对文件进行监视
//fs.unwatchFile(filename, [listener]);
/**
* filename, 完整路径及文件名;
* [listener], 要取消的监听器事件,如果不指定,则取消所有监听处理事件
*/
var listener = function (curr, prev) {
console.log('我是监视函数')
}
fs.unwatchFile(__dirname + '/test.txt', listener);
// 对文件或目录进行监视,并且在监视到修改时执行处理;
// 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);
/*
* 流,在应用程序中表示一组有序的、有起点有终点的字节数据的传输手段;
* 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 结束写入,之后再写入会报错;
*/
//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);
//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('系统缓存区数据已经全部输出。')
});
//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('再见')
});
NodeJs只能做的两件事是什么?
实际上,所有后台语言都能做的两件事是: 文件操作和网络编程.
这其实是所有语言的根本。 计算机无外乎就是文件和通信。Linux中,是把一切都当做文件,如果理解了这一点那就无可厚非了.
所以,这里,我想介绍一下NodeJS中一个重要的模块--fs.
这里我给大家放一个我的框架图~
(为什么不是http? 懒~)
let's start.
针对于fs,我们切实围绕几个问题来吧~
fs是如何操作文件的?
drain和write到底是什么关系?
fs怎么写出向gulp那样实时监听文件改变的插件?
关于fs的API,直接参考Nodejs官网. 同样,放上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()
首先这两个东西,是底层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.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
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("追加成功"); } }) |
因为是追加的内容,所以内容会自动在该文件后面
上面说的方法都是异步操作,异步操作会返回一个回调函数,在回调函数里面执行结束语句,不然会出现错误
而所有的同步函数,都只是在异步函数后面加上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("创建成功"); } }) |
1 2 3 4 5 6 7 |
fs.chmod("img",0333,function (err) { if(err){ return console.log(err); }else { console.log("修改ok"); } }) |
1 2 3 4 5 6 7 8 |
//修改文件夹名称 fs.rename("img","image",function (err) { if(err){ return console.log(err); }else { console.log("修好"); } }) |
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); } }) |
判断一个位置问价是否是文件或者是文件件
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"); |