美文网首页
Nodejs API 学习系列(一)

Nodejs API 学习系列(一)

作者: 艾伦先生 | 来源:发表于2016-12-21 15:42 被阅读274次

    本文的主要内容是对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官方文档

    未完待续~

    相关文章

      网友评论

          本文标题:Nodejs API 学习系列(一)

          本文链接:https://www.haomeiwen.com/subject/yzuqvttx.html