~ Node.js 内置模块:fs / path

与浏览器程序相比,Node.js 作为服务器端程序,其最大的特点在于:没有了浏览器的安全限制。

相对应的,它必须能够接受网络需求、读写文件操作和处理二进制内容。


Node.js 常用模块,实现了服务器端程序的一些基本功能,其中:

  • fs 模块:文件及目录操作,即,对系统文件和目录进行读写操作
  • path 模块:路径处理,包含一系列处理和转换文件路径的工具集

编程风格( 异步和底层操作 ):所有方法都有异步( 基于回调函数的编程风格,推荐 )和同步的形式

异步 I/O input/output
1. 文件操作
2. 网络操作
 
在浏览器中也存在异步操作:
1. 定时任务
2. 事件处理 
3. Ajax 回调处理
 
JS的运行时是单线程的,但引入了事件队列机制。所以,Nodejs中的事件模型与浏览器中的事件模型类似:
   单线程 + 事件对列机制
Nodejs中异步执行的任务:文件I/O、网络I/O

fs 模块

// 引入模块
   const fs = require('fs');

[1] 文件操作


  • 获取文件信息
fs.stat(path,callback)
其中,path是文件路径;callback是回调函数,带有两个参数:errstats
// 代码示例
    const fs = require('fs');
    fs.stat('./a.json',(err,stats)=>{
  	  if(err) return;
	  console.log(stats);
    })

stats 是 fs.Stats 类的对象,其属性如下:

/*
  Stats {
    ...
    atime: 2020-12-01T03:51:50.561Z,    // 文件访问的时间
    mtime: 2020-12-01T03:29:35.765Z,    // 文件数据发生变化的时间
    ctime: 2020-12-01T03:29:35.765Z,    // 文件的状态(如权限)发生变化的时间
    birthtime: 2020-12-01T03:26:42.874Z    // 文件创建的时间
  }
*/

其方法包括( 可以用这些方法判断文件的类型 ):

  •   stats.isFile()  如果是文件,返回 true,否则返回 false
  • stats.isDirectory() 如果是目录,返回 true,否则返回 false

...

  // 代码示例
     let fs = require('fs');
 
     fs.stat('./a.json',(err,stats)=>{
     	     if(stats.isFile()){
		   console.log('文件类型');
	     }else if(stats.isDirectory()){
		   console.log('目录类型')
	     }
     })

  • 读取文件
fs.readFile(file[,options],callback)
其中,file是文件名;callback,是一个回调函数,有两个参数:err 和 data(文件内容);
而,第二个参数是编码 — 不写,默认为 null,则 data 是一个 buffer 实例对象;
如果写了,如 utf8,则回调函数获取到的数据就是字符串。
   # 代码示例
     fs.readFile('./a.json',(err,data)=>{
	     console.log(data);  // 数据是一个buffer对象
     })
 
     fs.readFile('./a.json','utf8',(err,data)=>{
	     console.log(data);  // 获取到的数据是字符串
     })

  • 写入文件                            // 如果文件不存在,则新建;如果存在,则会被替换
fs.writeFile(file,data[,option],callback)
其中,file为文件名;data要写入的数据,可以是字符串,也可以是buffer对象;
option,编码,不写则默认为utf8callback,回调函数只有一个参数:err
     # 代码示例
     fs.writeFile('./a.js','hello',(err)=>{
	     if(err){
		     console.log(err);
   	     }
     })

  • 追加文件                            // 追加数据到一个文件中;如果不存在,则创建文件
fs.appendFile(file,data[,option],callback)
其中,file是文件名;data是要追加写入文件的数据,可以是字符串,也可以是buffer对象;
options,编码,不写则默认为utf8callback,回调函数,只有一个参数:err
     # 代码示例
     fs.appendFile('./a.js','world',(err)=>{
	     if(err){
		     console.log(err)
	     }
     })

  • 删除文件
fs.unlink(path,callback)
其中,path为文件路径;callback,回调函数,只有一个参数:err
     # 代码示例
     fs.unlink('./abc/a.js',(err)=>{
	   if(err){
	 	console.log(err);
	   }
	   console.log('删除文件成功');
     })

  • 重命名文件( 类似于:剪切,然后,重命名 )
fs.rename(oldpath,newpath,callback)
其中,oldpath是被重命名的文件路径;newpath是重命名后的新文件路径;
callback,回调函数,只有一个参数:err
     # 代码示例:会删除a.js文件,在新路径上创建b.js文件    
     fs.rename('./abc/a.js','./b.js',(err)=>{
	   if(err){
		console.log(err);
	   }
     })

  • 文件的流式操作( 主要用于大文件处理 )
/*
	fs.createReadStream(path[,option])   读文件
	fs.createWriteStream(path[,option])  写文件
        readstream.pipe(writestream)   直接把输入流和输出流关联起来
*/
 
const path = require('path');
const fs = require('fs');
 
let spath = path.join(__dirname,'file.zip');    // 原路径
let dpath = path.join('/Users/admin/webpiece','file.zip');    // 目标路径
 
let readstream = fs.createReadStream(spath);         // 读文件的数据流
let writestream = fs.createWriteStream(dpath);       // 写文件的数据流
 
// 基于事件的处理方式
// readstream.on('data',(chunk)=>{          // data,是一个事件,当读取数据的时候触发,每读取一次,触发一次;chunk参数的意思是“一块数据”
// 	writestream.write(chunk);
// });
// readstream.on('end',()=>{            // 数据处理完成的时候触发“end”事件
// 	console.log('文件处理完成');
// });
 
// 上述注释的内容,可以直接写为:
// pipe 的作用是直接把输入流和输出流关联起来
readstream.pipe(writestream);

所以,大文件的流式操作可以简写为:

const path = require('path');
const fs = require('fs');
 
let spath = path.join(__dirname,'file.zip');
let dpath = path.join('/Users/admin/webpiece','file.zip');
 
fs.createReadStream(spath).pipe(fs.createWriteStream(dpath))

[2] 目录操作


  • 创建目录
fs.mkdir(path,callback)
其中,path为目录的路径;callback,回调函数,只有一个参数:err
     // 在当前目录创建 abc 目录
     fs.mkdir('abc',(err)=>{
	      if(err){
		     console.log(err);
	      }
     })
     或
     // 在当前目录下创建 abcd 目录
     let path = require('path');
     fs.mkdir(path.join(__dirname,'abcd'),(err)=>{
	     if(err){
		     console.log(err);
	     }
     })

  • 删除目录
fs.rmdir(path,callback)
其中,path为目录的路径;callback,回调函数,只有一个参数:err
     # 代码示例
     fs.rmdir('abcd',(err)=>{
	     if(err){
		     console.log(err)
	     }
     })

  • 读取目录
fs.readdir(path[,option],callback)
其中,path为文件路径;option,编码,不写,则默认为utf8;
callback,回调函数,有两个参数:errfiles,而,files 表示 path 中不包括 ... 在的目录/文件的数组
     # 代码示例
     fs.readdir(__dirname,(err,files)=>{
	     if(err)return;
	     console.log(files);  // files表示当前目录下的所有目录和文件
	     // [ 'a.json', 'abc', 'app.js', 'b.js', 'package.json' ]
     })

[3] 代码示例:对 files 进行遍历,并通过 fs.stat() 对文件类型进行判断
let fs = require('fs');
let path = require('path');
 
fs.readdir(__dirname,(err,files)=>{
	files.forEach((item,index)=>{  // foreach遍历
		fs.stat(path.join(__dirname,item),(err,stats)=>{
			if(stats.isFile()){
				console.log(item,'文件');
			}else if(stats.isDirectory()){
				console.log(item,'目录');
			}
		})
	})
})

path 模块

// 引入模块
   let path = require('path');

[1] 路径的基本结构                                                  // dirname   +   basename(extname)

  • 获取文件的全路径
console.log(__filename);  // /Users/admin/webpiece/Project/test/test/app.js
  • dirname
console.log(path.dirname(__filename));    // /Users/admin/webpiece/Project/test/test
console.log(__dirname);  // /Users/admin/webpiece/Project/test/test
  • basename
console.log(path.basename(__filename));  // app.js
console.log(path.basename(__filename,'.js'));  // app  // 不含扩展名
  • extname
console.log(path.extname(__filename));  // .js

[2] 路径的格式化处理

  • path.parse()    将一个字符串路径转换为对象格式(string->obj
console.log(path.parse(__filename));
// 结果如下:
let obj={
  root: '/',
  dir: '/Users/admin/webpiece/Project/test/test',
  base: 'app.js',
  ext: '.js',
  name: 'app'
}
  • path.format()    将一个对象格式化为字符串路径(obj->string
console.log(path.format(obj));  // /Users/admin/webpiece/Project/test/test/app.js

[3] 路径分隔符

  • path.sep  返回对应平台下的文件分隔符,win下为'\',*nix下为'/'
console.log('far/bar/baz'.split(path.sep));
// win下返回['foo/bar/baz'],但在*nix系统下会返回['foo','bar','baz']
  • path.delimiter  返回对应平台下的路径分隔符,win下为';',*nix下为':'

[4] 路径规范化  &  路径拼接

  • path.normalize(p)    规范化路径,处理冗余的“..”、“.”、“/”字符
    • 发现多个斜杠时,会替换成一个斜杠
    • 当路径末尾包含一个斜杠时,保留。Windows系统使用反斜杠

简单来说就是,把一个不太合理的路径进行格式化,形成一个规范的路径

console.log(path.normalize('a/b/c/../user/bin'));//a\b\user\bin
console.log(path.normalize('a/b/c///../user/bin/'));//a\b\user\bin\
  • path.join([path1], [path2], [...])    路径拼接 — 将多个路径结合在一起,并转换为规范化路径
console.log(path.join('////./a', 'b////c', 'user/'));//\a\b\c\user
console.log(path.join(__dirname,'a.json'));   // /Users/admin/webpiece/Project/test/test/a.json

[5] 绝对路径和相对路径

  • path.isAbsolute(path)  

其中,path 是一个绝对路径( 比如 'E:/abc' ),或者是以“/”开头的路径,二者都会返回 true

console.log(path.isAbsolute('../testFiles/secLayer'));//false
console.log(path.isAbsolute('./join.js'));//false
console.log(path.isAbsolute('temp'));//false
console.log(path.isAbsolute('/temp/../..'));//true
console.log(path.isAbsolute('E:/github/nodeAPI/abc/efg'));//true
console.log(path.isAbsolute('///temp123'));//true
  • path.relative(from, to)    计算从 fromto 的相对路径
console.log(path.relative('C:/test/aaa', 'C:/bbb'));//..\..\bbb
console.log(path.relative('C:/test/aaa', 'D:/bbb'));//D:\bbb
 
path.resolve([from ...], to)  解析路径,从源地址 from 到目的地址 to 的绝对路径,类似在 shell 里的 cd 命令
 
// 解析路径
path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile')
 
// 类似于:
    cd foo/bar
    cd /tmp/file/
    cd ..
    cd a/../subfile
    pwd
 
// 拓展:如果某个 from 或 to 参数是绝对路径(比如 'E:/abc',或是以“/”开头的路径),则将忽略之前的from参数
console.log(path.resolve('D:', 'abc', 'D:/a'));//D:\a

综合案例

代码示例:fs 模块和 path 模块的综合运用 — 初始化目录结构


const path = require('path');
const fs = require('fs');
 
// 存储目录
let root = '/Users/admin/webpiece';
// 文件内容
let fileContent = `
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			welcome to this page
		</div>
	</body>
</html>
`;
 
// 初始化数据
let initData = {
	projectName:'mydemo',
	data:[{
		name:'img',
		type:'dir'
	},{
		name:'css',
		type:'dir'
	},{
		name:'js',
		type:'dir'
	},{
		name:'index.html',
		type:'file'
	}]
}
 
// 创建项目根路径
fs.mkdir(path.join(root,initData.projectName),(err)=>{
	if(err) return;
	// 创建子目录和文件
	initData.data.forEach(item=>{
		if(item.type == 'dir'){
			// 创建子目录
			fs.mkdir(path.join(root,initData.projectName,item.name),(err)=>{
				// console.log(err);
			});
		}else if(item.type == 'file'){
			// 创建文件并写入内容
			fs.writeFile(path.join(root,initData.projectName,item.name),fileContent,(err)=>{
				// console.log(err);
			});
		}
	})
})