node 基础学习(二) -- 流

博客分类: 原创

node 基础学习(二) -- 流

最近的又搞了几个月的业务,对自己的用户行为系统又进行了一次重构。加入了时间维度和页面维度。数据还没有跟上,所以可以在暂时的时间里看看自己的东西,继续自己的node学习。

处理文件路径

有时在处理路径问题时,用字符串的方式去处理时,不知道/是加了还是没有加,处理起来就难免会多打几个日志来确定一下。核心模块中有一个path模块是处理路径的好方法。

__dirname   //这个是一个全局变量。代表当前路径。
var path = require('path');
// 规范路径
path.normalize('/a//b/c/../d');     // 输出 '/a/b/d'
// 拼接路径
path.join('a', 'b', 'c', 'd');      // 输出 'a/b/c/d'
// 将多个路径组合起来
path.resolve('/a/b', '../c')        // 输出 '/a/c'
// 查找两个路径之间的相对路径
path.relative('/a/b/c', '/a/d/e')   // 输出 '../../d/e'
// 提取路径中文件夹的部分
path.dirname('a/b/c/d/e')           // 输出 'a/b/c/d'
// 提取文件名部分
path.basename('a/b/c/d/e.xxx')      // 输出 'e.xxx'
// 返回最后一个文件名的'.'之后的部分,如果是没有则返回''。如果是'd.'则返回'.'
path.extname('a/b/c/d/e.xxx')       // 输出 '.xxx'
// 返回路径对象
path.parse('/a/b/c.html')           // 输出 { root: '/', dir: '/a/b', base: 'c.html', ext: '.html', name: 'c' }
// 与上一个方法相反
path.format({ root: '/', dir: '/a/b', base: 'c.html', ext: '.html', name: 'c' })    // 输出 '/a/b/c.html'
// 判断是否为绝对路径
path.isAbsolute('a/b/c')            // 输出 false

输入输出流

js中我们一般用console.log来输出日志信息,node也支持用这个来向运行环境里输出一些日志,帮助调试,但是打了日志并不能停下来。如果要在程序运行途中需要主动输入怎么办呢,这个时候node提供了输入输出流,node提供三种流对象。这个三个流对象都挂载在全局对象process中。

process.stdin   // 标准输入 可读流
process.stdout  // 标准输出 可写流
process.stderr  // 标准错误 可写流

如何使用?看了下面的程序就应该懂了,这个流是用来干什么的了。注释也说的很明白。

// 输出一行字
process.stdout.write('你的名字是?');
// 监听输入事件
process.stdin.on('data', function(data){
    // 输出用户主动输入的数据
    process.stdout.write(data);
    // 结束输入
    process.stdin.pause();
})
// 等待输入,书上说是为了防止程序退出,但我没有发现有这个功能,甚至我不知道这个东西用来干嘛
process.stdin.resume();
// 对输入进行编码
process.stdin.setEncoding('utf8');

argv

如何在运行node程序之初进行传参呢?有一个argv对象会接受用户在运行程序之初所传入的参数,这个参数也是挂在到process上的。现在有个argv.js文件,文件中的代码如下。

process.argv.map(function (item, index, array) {
    console.log(item)
});

执行代码:

node argv.js karyn 22

输出代码:

node
/home/work/aaa.js
karyn
22

退出程序

node也提供,程序运行完毕后主动退出的方法process.exit()

ANSI转义码

console.log('\033[91mqi.song\033[39m karyn');

上面就是一种着色的方法。(如果没看到字,那说明你的命令行背景色和字体颜色一样了)

  • \033 表示转义序列开始
  • [ 表示开始颜色设置
  • 91 表示前景色为橘黄色
  • 39 表明把颜色设置回去
  • m 表示颜色使设置结束

文件流

获取文件信息

var path = require('path'),
    fs = require('fs');

fs.stat(path.join(__dirname, 'xxx.js'), function(err, stats){
    if(err){
        throw err;
    }else{
        console.log(stats)
        console.log(stats.isFile())
        console.log(stats.isDirectory())
        console.log(stats.isBlockDevice())
        console.log(stats.isCharacterDevice())
        console.log(stats.isSymbolicLink())
        console.log(stats.isFIFO())
        console.log(stats.isSocket())
    }
})

// 输出
{
    dev: 16777220,
    mode: 33188,
    nlink: 1,
    uid: 501,
    gid: 0,
    rdev: 0,
    blksize: 4096,
    ino: 67732262,
    size: 84,
    blocks: 8,
    atime: Mon Oct 26 2015 15: 38: 18 GMT + 0800(CST),
    mtime: Mon Oct 12 2015 17: 54: 07 GMT + 0800(CST),
    ctime: Mon Oct 12 2015 17: 54: 07 GMT + 0800(CST),
    birthtime: Mon Sep 28 2015 16: 10: 25 GMT + 0800(CST)
}
true
false
false
false
false
false
false

打开文件,并读取指定大小的字符串。如果文件太大,按照固定大小读取到内存中会溢出的话可以考虑下面这个方式

var path = require('path'),
    fs = require('fs');

function readFileBytes(fd, size, times, callback){
    var readBuffer = new Buffer(size),
        bufferOffset = 0,
        bufferLength = readBuffer.length,
        filePosition = times * bufferLength;
    fs.read(fd, readBuffer, bufferOffset, bufferLength, filePosition, function(err, readBytes){
        if(err){
            callback(err, null)
        }else{
            callback(null, readBuffer.slice(0, readBytes).toString())
        }
    })
}

fs.open(path.join('/home/q/log/data/done', '20151001.log'), 'r', function(err, fd){
    if(err){
        throw err;
    }else{
        var i = 0 ;
        function readCallback(err, data){
            if(err){
                console.log('出错了:'+ err)
            }else{
                if(data && data.length){
                    process.nextTick(function(){
                        readFileBytes(fd, 1024, ++i, readCallback)
                    })
                }else{
                    console.log('读完了')
                }
            }
        }
        readFileBytes(fd, 1024, i, readCallback)
    }
})

如果文件太大,按照行读取文件

var path = require('path'),
    fs = require('fs');

function readLines(readStream, callback) {
    var remaining = '';

    // 实际上也是一堆一堆的读的,最小为64K
    readStream.on('data', function(data) {
        // 暂停读取
        readStream.pause();
        remaining += data;
        var index = remaining.indexOf('\n');
        while (index > -1) {
            var line = remaining.substring(0, index);
            remaining = remaining.substring(index + 1);
            callback(line);
            index = remaining.indexOf('\n');
        }
        // 继续读取
        process.nextTick(function(){
            readStream.resume()
        })
    });

    readStream.on('end', function() {
        if (remaining.length > 0) {
            callback(remaining);
        }
        console.log('读完了')
    });
}

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

// 可以跟参数,每次读取固定大小内容。还可以定义输出的类型
var logs = fs.createReadStream(path.join('/home/q/log/data/done', '20151001.log'));
readLines(logs, readCallback);

网络流

HTTP的请求对象是一个可读流,HTTP响应对象是一个可写流。当有进程读取数据,并将数据发送给另一个进程时,读取流速度大于写入流时一般就会出现满客户端问题。下面这种方式创建两个流可以一定程度上缓解这个问题。

var http = require('http'),
    path = require('path'),
    fs = require('fs');

require('http').createServer(function(req, res){
    // 返回对应的静态文件的内容,创建一个文件读取流
    var rs = fs.createReadStream(path.join('/home/q/log/data/test', '20151009.log'));

    rs.on('data', function(chunk){
        // 持续向 res 中写入文件内容,判断写缓冲区是否写满
        if(!res.write(chunk)){
            // 如果写缓冲区不可用,暂停读取数据
            rs.pause();
        }
    })
    // 写缓冲区可用,会触发"drain"事件
    res.on('drain', function(){
        //重新启动读取数据
        rs.resume();
    })
    // 文件读取流完成,则完成网络写入流
    rs.on('end', function(){
        res.end();
    })
}).listen(3333)

另一种简要的写法,使用 pipe 流。

var http = require('http'),
    path = require('path'),
    fs = require('fs');

require('http').createServer(function(req, res){
    var rs = fs.createReadStream(path.join('/home/q/log/data/test', '20151009.log'));
    // 如果不需要 end 处理,可以更简单的写成 rs.pipe(res),会自动处理 end 时机问题。
    rs.pipe(res, {end: false});

    rs.on('end', function(){
        res.write('读取完毕');
        res.end();
    })

}).listen(3333)

简单的复制文件的方法

fs.createReadStream(path).pipe(fs.createWriteStream(path));