Nodejs API 学习系列(一)

本文的主要内容是对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', 'b////c', '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('再见')
});

推荐文章

fs模块实例参考

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

其他流

参考文章

nodejs stream手册

其他系列文章

Nodejs模块学习笔记
极客学院 nodejs官方文档

未完待续~

推荐阅读更多精彩内容

  • https://nodejs.org/api/documentation.html 工具模块 Assert 测试 ...
    KeKeMars阅读 4,369评论 0 6
  • 文件系统模块是一个封装了标准的 POSIX 文件 I/O 操作的集合。通过require('fs')使用这个模块。...
    保川阅读 416评论 0 0
  • Node.js是目前非常火热的技术,但是它的诞生经历却很奇特。 众所周知,在Netscape设计出JavaScri...
    w_zhuan阅读 2,774评论 2 40
  • 模块 名词解释:每一个js文件就是一个模块,而文件路径就是模块名。每个模块(也就是每个js文件)都有requir,...
    亲爱的孟良阅读 166评论 0 0
  • 虽然时不时会有点小思念,但是内心平静而感恩了 我删了过去一年喜欢的人 我可是说,在大理的那段时间,接受了孤独,所以...
    helen1990_阅读 34评论 0 0