本文的主要内容是对nodejs提供的一些重要模块,结合官方API进行介绍,遇到精彩的文章,我会附在文中并标明了出处。主要包括如下7个模块
- path 模块
- http 模块
- fs 模块
- url 模块
- query string 模块
- os 模块
- stream 模块
转载请注明出处,多谢支持~
path 路径相关模块
模块简介
nodejs path 模块提供了一些用于处理文件路径的工具函数,我们可以通过如下方式引用它
var path = require("path")
复制代码
path.normalize(p)
标准化路径字符串,处理冗余的“..”、“.”、“/”字符:
原则:
- 对window系统,目录分隔为'',对于UNIX系统,分隔符为'/',针对'..'返回上一级;/与\都被统一转换path.normalize(p);
- 如果路径为空,返回.,相当于当前的工作路径。
- 将对路径中重复的路径分隔符(比如linux下的/)合并为一个。
- 对路径中的.、..进行处理。(类似于shell里的cd ..)
- 如果路径最后有/,那么保留该/。
• var url1 = path.normalize('a/b/c/../user/vajoy/bin'); var url2 = path.normalize('a/b/c///../user/vajoy/bin/'); var url3 = path.normalize('a/b/c/../../user/vajoy/bin'); var url4 = path.normalize('a/b/c/../.../user/vajoy/bin/..'); var url5 = path.normalize('a/b/c/../../user/vajoy/bin/../../'); var url6 = path.normalize('a/../../user/vajoy/bin/../../'); var url7 = path.normalize('a/../../user/vajoy/bin/../../../../'); var url8 = path.normalize('./a/.././user/vajoy/bin/./');
console.log('url1:',url1); // a\b\user\vajoy\bin console.log('url2:',url2); // a\b\user\vajoy\bin
console.log('url3:',url3); // a\user\vajoy\bin console.log('url4:',url4); // a\user\vajoy console.log('url5:',url5); // a\user
console.log('url6:',url6); // ..\user
console.log('url7:',url7); // ....
console.log('url8:',url8); // user\vajoy\bin\
path.join([path1], [path2], [...])
将多个路径结合在一起,并转换为标准化的路径
var url1 = path.join('./a', 'bc', 'user/', 'vajoy', '..');
var url2 = path.join('a', '../../', 'user/', 'vajoy', '..');
var url3 = path.join('a', '../../', {}, 'vajoy', '..');
var url4 = path.join('path1', 'path2//pp\\', ../path3');
console.log('url1:',url1); // \a\b\c\user
console.log('url2:',url2); // ..\user
console.log('url3:',url3); // 存在非路径字符串,故抛出异常
console.log('url4:',url4); // path1\path2\path3
复制代码
path.resolve([from ...], to)
从源地址 from 到目的地址 to 的绝对路径
原则 以应用程序根目录为起点,根据参数字符串解析出一个绝对路径 要注意的是,如果某个 from 或 to 参数是绝对路径(比如 'E:/abc',或是以“/”开头的路径),则将忽略之前的 from 参数。
// 下文中app的根目录为D:\temp\test
var url1 = path.resolve('.', 'testFiles/..', 'trdLayer');
var url2 = path.resolve('..', 'testFiles', 'a.txt');
var url3 = path.resolve('D:/vajoy', 'abc', 'D:/a');
var url4 = path.resolve('abc', 'vajoy', 'ok.gif');
var url5 = path.resolve('abc', '/vajoy', '..', 'a/../subfile'); //'abc'参数将被忽略,源路径改从'D:/vajoy'开始
console.log('url1:',url1); //D:\temp\test\trdLayer
console.log('url2:',url2); //D:\temp\testFiles\a.txt
console.log('url3:',url3); //D:\a
console.log('url4:',url4); //D:\temp\test\abc\vajoy\ok.gif
console.log('url5:',url5); //D:\subfile
复制代码
path.relative(from, to)
获取从 from 到 to 的相对路径(即,基于from路径的两路径间的相互关系),可以看作 path.resolve 的相反实现
var url1 = path.relative('C:\\vajoy\\test\\aaa', 'C:\\vajoy\\impl\\bbb');
var url2 = path.relative('C:/vajoy/test/aaa', 'C:/vajoy/bbb');
var url3 = path.relative('C:/vajoy/test/aaa', 'D:/vajoy/bbb');
console.log('url1:',url1); //..\..\impl\bbb
console.log('url2:',url2); //..\..\bbb
console.log('url3:',url3); //D:\vajoy\bbb
复制代码
- 如果from、to指向同个路径,那么,返回空字符串。
- 如果from、to中任一者为空,那么,返回当前工作路径。
path.isAbsolute(path)
判断 path 是否绝对路径。这块可以理解为,path 是否真的是一个绝对路径(比如 'E:/abc'),或者是以“/”开头的路径,二者都会返回true
var url1 = path.isAbsolute('../testFiles/secLayer');
var url2 = path.isAbsolute('./join.js');
var url3 = path.isAbsolute('temp');
var url4 = path.isAbsolute('/temp/../..');
var url5 = path.isAbsolute('E:/github/nodeAPI/abc/efg');
var url6 = path.isAbsolute('///temp123');
console.log('url1:',url1); // false
console.log('url2:',url2); // false
console.log('url3:',url3); // false
console.log('url4:',url4); // true
console.log('url5:',url5); // true
console.log('url6:',url6); // true
复制代码
path.dirname(p)
返回路径中文件夹的路径
var url1 = path.dirname('/foo/bar/baz/asdf/a.txt');
var url2 = path.dirname('/foo/bar/baz/asdf/');
var url3 = path.dirname('C:/vajoy/test/aaa');
var url4 = path.dirname(__dirname + '/docs/a.txt')
console.log('url1:',url1); // /foo/bar/baz/asdf
console.log('url2:',url2); // /foo/bar/baz
console.log('url3:',url3); // C:/vajoy/test
console.log(url4);// D:\mobileWeb\temp\test/docs
复制代码
path.basename(p, [ext])
返回路径中的最后一部分(通常为文件名),类似于Unix 的 basename 命令。 ext 为需要截掉的尾缀内容
var url1 = path.basename('/foo/bar/baz/asdf/a.txt');
var url2 = path.basename('/foo/bar/baz/asdf/a.txt','.txt');
var url3 = path.basename('/foo/bar/baz/asdf/');
var url4 = path.basename('C:/vajoy/test/aaa');
console.log('url1:',url1); // a.txt
console.log('url2:',url2); // a
console.log('url3:',url3); // asdf
console.log('url4:',url4); // aaa
复制代码
path.extname(p)
返回路径文件中的扩展名(若存在)
var url1 = path.extname('/foo/bar/baz/asdf/a.txt');
var url2 = path.extname('/foo/bar/baz/asdf/a.txt.html');
var url3 = path.extname('/foo/bar/baz/asdf/a.');
var url4 = path.extname('C:/vajoy/test/.');
var url5 = path.extname('C:/vajoy/test/a');
console.log('url1:',url1); // .txt
console.log('url2:',url2); // .html
console.log('url3:',url3); // .
console.log('url4:',url4); //
console.log('url5:',url5); //
复制代码
path.parse(pathString)
返回路径字符串的对象
var url1 = path.parse('/foo/bar/baz/asdf/a.txt');
url1: {
root: '/',//根目录
dir: '/foo/bar/baz/asdf',//文件所在目录
base: 'a.txt',//文件名,输出文件名称以base为准,base为空,则不输出文件名
ext: '.txt',//文件扩展名
name: 'a',//文件名称, 不含扩展名(name返回的是文件名或最后文件夹名)
}
var url2=path.parse('C:\\path\\dir\\');
{ root: 'C:\\',
dir: 'C:\\path',
base: 'dir',
ext: '',
name: 'dir'
}
var url3=path.format({
root:'f:',
dir:'f:\\dir1\\dir2',
name:'file',
base:'file.nanme',
ext:'.txt'
});
//f:\dir1\dir2\file.nanme
var url3=path.format({
root:'f:',
dir:'f:\\dir1\\dir2',
name:'file',
ext:'.txt'
});
//f:\dir1\dir2\
复制代码
path.format(pathObject)
从对象中返回路径字符串,和 path.parse 相反
var pathObj = {
root: '/',
dir: '/foo/bar/baz/asdf',
base: 'a.txt',
ext: '.txt',
name: 'a'
}
var url1 = path.format(pathObj);
console.log('url1:',url1);//url1: /foo/bar/baz/asdf\a.txt
复制代码
path.sep
*返回对应平台下的文件夹分隔符,win下为'',nix下为'/'
var url1 = path.sep;
var url2 = 'foo\\bar\\baz'.split(path.sep);
var url3 = 'foo/bar/baz'.split(path.sep);
console.log('url1:',url1); // win下为\,*nix下为/
console.log('url2:',url2); // [ 'foo', 'bar', 'baz' ]?
console.log('url3:',url3); // win下返回[ 'foo/bar/baz' ],但在*nix系统下会返回[ 'foo', 'bar', 'baz' ]
复制代码
path.delimiter
*返回对应平台下的路径分隔符,win下为';',nix下为':'
var env = process.env.PATH; //当前系统的环境变量PATH
var url1 = env.split(path.delimiter);
console.log(path.delimiter);
//win下为“;”,*nix下为“:”
console.log('env:',env);
// C:\ProgramData\Oracle\Java\javapath;C:\Program Files (x86)\Intel\iCLS Client\;
console.log('url1:',url1);
// ['C:\ProgramData\Oracle\Java\javapath','C:\Program Files (x86)\Intel\iCLS Client\']
复制代码
http 网络请求模块
推荐文章
fs 文件系统操作模块
模块简介
nodejs path 模块提供了一些用于处理文件系统的小工具,我们可以通过如下方式引用它
var path = require("fs")
复制代码
同步&&异步API
使用require('fs')
载入fs模块,模块中所有方法都有同步和异步两种形式。
异步方法中回调函数的第一个参数总是留给异常参数(exception),如果方法成功完成,该参数为null或undefined。
fs.readFile('./test.txt', function(err, data) {
if (err) throw err;
console.log('文件内容:'+ data);
});
复制代码
同步写法,一般都是在异步方法名后拼接Sycn
字符串,表示是同步方法
var data = fs.readFileSync('./test.txt');
console.log('文件内容:'+ data);
复制代码
同步方法执行完并返回结果后,才能执行后续的代码。而异步方法采用回调函数接收返回结果,可以立即执行后续代码。下面的代码演示,都已异步逻辑为主。
fs.readFile
/**
* filename, 必选参数,文件名
* [options],可选参数,可指定flag(文件操作选项,如r+ 读写;w+ 读写,文件不存在则创建)及encoding属性
* callback 读取文件后的回调函数,参数默认第一个err,第二个data 数据
*/
fs.readFile(__dirname + '/test.txt', {flag: 'r+', encoding: 'utf8'}, function (err, data) {
if(err) throw err;
console.log(data);
});
复制代码
fs.writeFile
var w_data = '这是一段通过fs.writeFile函数写入的内容;\r\n';
//w_data = new Buffer(w_data);//可以将字符串转换成Buffer类型
/**
* filename, 必选参数,文件名
* data, 写入的数据,可以字符或一个Buffer对象
* [options],flag,mode(权限),encoding
* callback 读取文件后的回调函数,参数默认第一个err,第二个data 数据
*/
fs.writeFile(__dirname + '/test.txt', w_data, {flag: 'a'}, function (err) {
if(err) throw err;
console.log('写入成功');
});
复制代码
{flag: 'a'}
加上这个参数,内容将会被以追加方式写入文件,不加上这个参数则会先清空内容,再写入数据
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);
写文件,将缓冲区内数据写入使用fs.open打开的文件
/**
* 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);
fs.close(fd);
})
}
});
}
});
复制代码
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(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');
}
});
});
}
});
复制代码
fs.stat(path, callback);
查看文件与目录信息
fs.stat(__dirname + '/test.txt', function (err, stat) {
console.log('访问时间: ' + stat.atime.toString() + '; \n修改时间:' + stat.mtime);
console.log(stat.mode);
})
复制代码
fs.exists(path, callback);
查看文件与目录是否存在
/**
* path, 要查看目录/文件的完整路径及名;
* [callback(exists)], 操作完成回调函数;exists true存在,false表示不存在
*/
fs.exists(__dirname + '/test', function (exists) {
var retTxt = exists ? retTxt = '文件存在' : '文件不存在';
console.log(retTxt);
});
复制代码
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.mkdir(__dirname + '/test', function(err){
fs.rmdir(__dirname + '/test', function (err) {
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.watch(filename, [options], [listener]);
对文件或目录进行监视,并且在监视到修改时执行处理; fs.watch返回一个fs.FSWatcher对象,拥有一个close方法,用于停止watch操作; 当fs.watch有文件变化时,会触发fs.FSWatcher对象的change(err, filename)事件,err错误对象,filename发生变化的文件名
/**
* 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]);
管道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('再见')
});
复制代码
推荐文章
url 处理模块
引用
var url = require("url");
复制代码
URL 的组成介绍
对于一个 URL 字符串,其组成部分会有所有不同,其中有些部分只有在URL字符串中存在时,对应字段才会出现在解析后对象中。以下是一个 URL 例子:
http://user:pass@host.com:8080/p/a/t/h?query=string#hash
href: 解析前的完整原始 URL,协议名和主机名已转为小写
例如: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'
protocol: 请求协议,小写
例如: 'http:'
slashes: 协议的“:”号后是否有“/”
例如: true or false
auth: URL中的认证信息
例如: 'user:pass'
host: URL主机名,包括端口信息,小写
例如: 'host.com:8080'
hostname: 主机名,小写
例如: 'host.com'
port: 主机的端口号
例如: '8080'
path: pathname 和 search的合集
例如: '/p/a/t/h?query=string'
pathname: URL中路径
例如: '/p/a/t/h'
search: 查询对象,即:queryString,包括之前的问号“?”
例如: '?query=string'
query: 查询字符串中的参数部分(问号后面部分字符串),或者使用 querystring.parse() 解析后返回的对象
例如: 'query=string' or {'query':'string'}
hash: 锚点部分(即:“#”及其后的部分)
例如: '#hash'
复制代码
url.parse(urlStr[, parseQueryString][, slashesDenoteHost])
将URL字符串转换为JSON对象
var urlString = 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash';
var result = url.parse(urlString);
console.log(result);
//输出结果如下
{ protocol: 'http:',
slashes: true,
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/p/a/t/h',
path: '/p/a/t/h?query=string',
href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'
}
//第二个可选参数设置为true时,会使用querystring模块来解析URL中德查询字符串部分,默认为 false。
var result1 = url.parse(urlString, true);
console.log(result1);
//输出结果如下
{ protocol: 'http:',
slashes: true,
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: {query:"string"},
pathname: '/p/a/t/h',
path: '/p/a/t/h?query=string',
href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'
}
复制代码
url.format(urlObj)
用于格式化URL对象。输入一个 URL 对象,返回格式化后的 URL 字符串。示例如下
var urlObj = {
protocol: 'http:',
slashes: true,
hostname: 'jianshu.com',
port: 80,
hash: '#hash',
search: '?query=string',
path: '/nodejs?query=string'
}
var result = url.format(urlObj);
console.log(result);
//输出结果如下
http://jianshu.com:80?query=string#hash
/*
*传入的URL对象会做以下处理:
*
*href 属性会被忽略
*protocol无论是否有末尾的 : (冒号),会同样的处理
**这些协议包括 http, https, ftp, gopher, file 后缀是 :// (冒号-斜杠-斜杠).
**所有其他的协议如 mailto, xmpp, aim, sftp, foo, 等 会加上后缀 : (冒号)
*auth 如果有将会出现.
*host 优先使用,将会替代 hostname 和port
*hostname 如果 host 属性没被定义,则会使用此属性.
*port 如果 host 属性没被定义,则会使用此属性.
*pathname 将会同样处理无论结尾是否有/ (斜杠)
*search 将会替代 query属性
*query (object类型; 详细请看 querystring) 如果没有 search,将会使用此属性.
*search 无论前面是否有 ? (问号),都会同样的处理
*hash无论前面是否有# (井号, 锚点),都会同样处理
*/
复制代码
url.resolve(from, to)
用于拼接路径
url.resolve('/one/two/three', 'four') // '/one/two/four'
url.resolve('http://example.com/', '/one') // 'http://example.com/one'
url.resolve('http://example.com/one', '/two') // 'http://example.com/two'
复制代码
query string 参数处理模块
引用
var querystring = require('querystring')
复制代码
querystring.stringify(obj, [sep], [eq])
对象格式化成参数字符串 ,obj就是要格式化的对象,必选参数;[sep]指分隔符 默认'&'; [eq]指分配符 默认'='
var querystring = require('querystring')
var param = {name:"feng",age:"33"};
var paramStr1 = querystring.stringify(param);
console.log(paramStr1);//name=feng&age=33
var paramStr2 = querystring.stringify(param,'$','-');
console.log(paramStr2);//name-feng$age-33
复制代码
本方法会自动编码汉字
querystring.parse(str, [sep], [eq], [options])
参数字符串格式化成对象
var paramStr1 = 'name=feng&age=33';
var paramStr2 = 'name-feng$age-33';
var param1 = querystring.parse(paramStr1);
console.log(param1);//{ name: 'feng', age: '33' }
var param2 = querystring.parse(paramStr2, '$', '-');
console.log(param2);//{ name: 'feng', age: '33' }
复制代码
querystring.escape
参数编码
var param = "name=阿峰&age=33";
console.log(querystring.escape(param));
//name%3D%E9%98%BF%E5%B3%B0%26age%3D33
复制代码
querystring.unescape
参数解码
var param = "name=阿峰&age=33";
console.log(querystring.unescape(querystring.escape(param)));
//name=阿峰&age=33
复制代码
os
引用
var os = require('os');
复制代码
常用函数
//cpu架构
os.arch();
//操作系统内核
os.type();
//操作系统平台
os.platform();
//系统开机时间
os.uptime();
//主机名
os.hostname();
//主目录
os.homedir();
//内存
os.totalmem();//总内存
os.freemem();// 空闲内存
//cpu
const cpus = os.cpus();
cpus.forEach((cpu,idx,arr)=>{
var times = cpu.times;
console.log(`cpu${idx}:`);
console.log(`型号:${cpu.model}`);
console.log(`频率:${cpu.speed}MHz`);
console.log(`使用率:${((1-times.idle/(times.idle+times.user+times.nice+times.sys+times.irq))*100).toFixed(2)}%`);
});
//网卡
const networksObj = os.networkInterfaces();
for(let nw in networksObj){
let objArr = networksObj[nw];
console.log(`\r\n${nw}:`);
objArr.forEach((obj,idx,arr)=>{
console.log(`地址:${obj.address}`);
console.log(`掩码:${obj.netmask}`);
console.log(`物理地址:${obj.mac}`);
console.log(`协议族:${obj.family}`);
});
}
复制代码
stream
为什么使用流
nodejs的fs模块并没有提供一个copy的方法,但我们可以很容易的实现一个,比如:
var source = fs.readFileSync('/path/to/source', {encoding: 'utf8'});
fs.writeFileSync('/path/to/dest', source);
复制代码
上面的这段代码并没有什么问题,但是在每次请求时,我们都会把整个源文件读入到内存中,然后再把结果返回给客户端。想想看,如果源文件非常大,在响应大量用户的并发请求时,程序可能会消耗大量的内存,这样很可能会造成用户连接缓慢的问题。
理想的方法应该是读一部分,写一部分,不管文件有多大,只要时间允许,总会处理完成,这里就需要用到流的概念。
上面的文件复制可以简单实现一下:
var fs = require('fs');
var readStream = fs.createReadStream('/path/to/source');
var writeStream = fs.createWriteStream('/path/to/dest');
readStream.on('data', function(chunk) { // 当有数据流出时,写入数据
writeStream.write(chunk);
});
readStream.on('end', function() { // 当没有数据时,关闭数据流
writeStream.end();
});
复制代码
上面的写法有一些问题,如果写入的速度跟不上读取的速度,有可能导致数据丢失。正常的情况应该是,写完一段,再读取下一段,如果没有写完的话,就让读取流先暂停,等写完再继续,于是代码可以修改为:
var fs = require('fs');
var readStream = fs.createReadStream('/path/to/source');
var writeStream = fs.createWriteStream('/path/to/dest');
readStream.on('data', function(chunk) { // 当有数据流出时,写入数据
if (writeStream.write(chunk) === false) { // 如果没有写完,暂停读取流
readStream.pause();
}
});
writeStream.on('drain', function() { // 写完后,继续读取
readStream.resume();
});
readStream.on('end', function() { // 当没有数据时,关闭数据流
writeStream.end();
});
复制代码
或者使用更直接的pipe
// pipe自动调用了data,end等事件
fs.createReadStream('/path/to/source').pipe(fs.createWriteStream('/path/to/dest'));
复制代码
下面是一个完整的复制文件的过程
var fs = require('fs'),
path = require('path'),
out = process.stdout;
var filePath = 'Users/feng/Documents/something/kobe.gif';
var readStream = fs.createReadStream(filePath);
var writeStream = fs.createWriteStream('file.gif');
var stat = fs.statSync(filePath);
var totalSize = stat.size;
var passedLength = 0;
var lastSize = 0;
var startTime = Date.now();
readStream.on('data', function(chunk) {
passedLength += chunk.length;
if (writeStream.write(chunk) === false) {
readStream.pause();
}
});
readStream.on('end', function() {
writeStream.end();
});
writeStream.on('drain', function() {
readStream.resume();
});
setTimeout(function show() {
var percent = Math.ceil((passedLength / totalSize) * 100);
var size = Math.ceil(passedLength / 1000000);
var diff = size - lastSize;
lastSize = size;
out.clearLine();
out.cursorTo(0);
out.write('已完成' + size + 'MB, ' + percent + '%, 速度:' + diff + 'MB/s');
if (passedLength < totalSize) {
setTimeout(show, 500);
} else {
var endTime = Date.now();
console.log();
console.log('共用时:' + (endTime - startTime) / 1000 + '秒。');
}
}, 500);
复制代码
Readable流
fs.createReadStream(path[, options])
用来打开一个可读的文件流,它返回一个fs.ReadStream
对象。path参数指定文件的路径,可选的options是一个JS对象,可以指定一些选项,类似下面这样:
{ flags: 'r',
encoding: 'utf8',
fd: null,
mode: 0666,
autoClose: true
}
复制代码
options的flags属性指定用什么模式打开文件:
- ’w’代表写,’r’代表读,类似的还有’r+’、’w+’、’a’等,与Linux下的open函数接受的读写模式类似。
- encoding指定打开文件时使用编码格式,默认就是“utf8”,你还可以为它指定”ascii”或”base64”。
- fd属性默认为null,当你指定了这个属性时,createReadableStream会根据传入的fd创建一个流,忽略path。另外你要是想读取一个文件的特定区域,可以配置start、end属性,指定起始和结束(包含在内)的字节偏移。
- autoClose属性为true(默认行为)时,当发生错误或文件读取结束时会自动关闭文件描述符。
Readable还提供了一些函数,我们可以用它们读取或操作流:
- read([size]):如果你给read方法传递了一个制定大小作为参数,那它会返回指定数量的数据,如果数据不足,就会返回null。如果你不给read方法传参,它会返回内部缓冲区里的所有数据,如果没有数据,会返回null,此时有可能说明遇到了文件末尾。read返回的数据可能是Buffer对象,也可能是String对象。
- setEncoding(encoding):给流设置一个编码格式,用于解码读到的数据。调用此方法后,read([size])方法返回String对象。
- pause():暂停可读流,不再发出data事件
- resume():恢复可读流,继续发出data事件
- pipe(destination,[options]):把这个可读流的输出传递给destination指定的Writable流,两个流组成一个管道。options是一个JS对象,这个对象有一个布尔类型的end属性,默认值为true,当end为true时,Readable结束时自动结束Writable。注意,我们可以把一个Readable与若干Writable连在一起,组成多个管道,每一个Writable都能得到同样的数据。这个方法返回destination,如果destination本身又是Readable流,就可以级联调用pipe(比如我们在使用gzip压缩、解压缩时就会这样,马上会讲到)。
- unpipe([destination]):端口与指定destination的管道。不传递destination时,断开与这个可读流连在一起的所有管道。
Readable流提供了以下事件:
- readable:在数据块可以从流中读取的时候发出。它对应的处理器没有参数,可以在处理器里调用read([size])方法读取数据。
- data:有数据可读时发出。它对应的处理器有一个参数,代表数据。如果你只想快快地读取一个流的数据,给data关联一个处理器是最方便的办法。处理器的参数是Buffer对象,如果你调用了Readable的setEncoding(encoding)方法,处理器的参数就是String对象。
- end:当数据被读完时发出。对应的处理器没有参数。
- close:当底层的资源,如文件,已关闭时发出。不是所有的Readable流都会发出这个事件。对应的处理器没有参数。
- error:当在接收数据中出现错误时发出。对应的处理器参数是Error的实例,它的message属性描述了错误原因,stack属性保存了发生错误时的堆栈信息。
一个基本的可读流实例
var fs = require('fs');
var readable = fs.createReadStream('text.js',{
flags: 'r',
encoding: 'utf8',
autoClose: true,
mode: 0666,
});
readable.on('open', function(fd){
console.log('file was opened, fd - ', fd);
});
readable.on('readable', function(){
console.log('received readable');
});
readable.on('data', function(chunk){
console.log('read %d bytes: %s', chunk.length, chunk);
});
readable.on('end', function(){
console.log('read end');
});
readable.on('close', function(){
console.log('file was closed.');
});
readable.on('error', function(err){
console.log('error occured: %s', err.message);
});
复制代码
Writable
Writable流提供了一个接口,用来把数据写入到目的设备(或内存)中。
Writable提供了一些函数来操作流
write(chunk[,encoding][,callback])
可以把数据写入流中。其中,chunk是待写入的数据,是Buffer或String对象。这个参数是必须的,其它参数都是可选的。如果chunk是String对象,encoding可以用来指定字符串的编码格式,write会根据编码格式将chunk解码成字节流再来写入。callback是数据完全刷新到流中时会执行的回调函数。write方法返回布尔值,当数据被完全处理后返回true(不一定是完全写入设备哦)。end([chunk] [,encoding][,callback])
方法可以用来结束一个可写流。它的三个参数都是可选的。chunk和encoding的含义与write方法类似。callback是一个可选的回调,当你提供它时,它会被关联到Writable的finish事件上,这样当finish事件发射时它就会被调用。
现在我们来看看Writable公开的事件:
finish: 在end()被调用、所有数据都已被写入底层设备后发射。对应的处理器函数没有参数。 pipe: 当你在Readable流上调用pipe()方法时,Writable流会发射这个事件,对应的处理器函数有一个参数,类型是Readable,指向与它连接的那个Readable流。 unpipe: 当你在Readable流上调用unpipe()方法时,Writable流会发射这个事件,对应的处理器函数有一个参数,类型是Readable,指向与刚与它断开连接的那个Readable流。 error: 出错时发射,对应的处理器函数的参数是Error对象。
看一个简单实例
var fs = require('fs');
var writable = fs.createWriteStream('example.txt',{
flags: 'w',
defaultEncoding: 'utf8',
mode: 0666,
});
writable.on('finish', function(){
console.log('write finished');
process.exit(0);
});
writable.on('error', function(err){
console.log('write error - %s', err.message);
});
writable.write('hello 地方', 'utf8');
writable.end();
复制代码