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