readFile(readFileSync)(path[, options], callback)
第一个参数是文件路径、第二个参数是配置的参数例如编码格式,第三个是回调函数(同步方法是没有回调函数的)
//是把整个文件作为一个整体
fs.readFile('./1.txt',{encoding:'utf8'},function (err,data) {
console.log(err);
console.log(data);
});
//同步方法是没有回调函数的
let result=fs.readFileSync('./1.txt',{encoding: 'utf8'});
console.log(result);
复制代码
writeFile(writeFileSync)(file, data[, options], callback)
第一个参数是文件路径、第二个参数是写入的内容,第三个是参数配置,第四个是回调函数。
fs.writeFile('./2.txt','123',{encoding: 'utf8'},(err) => {
console.log('write OK');
})
fs.writeFileSync('./3.txt','456');
复制代码
简单的实现拷贝
const fs=require('fs');
function copy(src,dest,cb) {
fs.readFile(src,(err,data) => {
fs.writeFile(dest,data,cb);
});
}
copy('3.txt','4.txt',() => {
console.log('拷贝完成');
});
复制代码
appendFile(追加文件内容,不能使用writeFile,因为writeFile存在的话会先清空在写入,不存在会创建)
fs.writeFile('./4.txt','789',{flag:'a'});
fs.appendFile('./4.txt','789');
复制代码
flags(配置参数选项)
linux权限
编码问题(gbk->utf8转换过程中会出现一个BOM头)
let path = require('path');
let result = fs.readFileSync(path.resolve(__dirname,'./text.txt'));
console.log(stripBOM(result).toString())
function stripBOM(content){
if(Buffer.isBuffer(content)){
if(content[0] === 0xef && content[1] === 0xbb && content[2] === 0xbf ){
return content.slice(3);
}
}else{
if (content.charCodeAt(0) === 0xFEFF) {
content = content.slice(1);
}
}
return content;
}
复制代码
open(filename,flags,[mode],callback)
//fd file descriptor 文件描述符 是一个数字或者说索引
fs.open('./5.txt','r',(err,fd) => {
console.log(fd);
fs.open('./4.txt','r',(err,fd) => {
console.log(fd);
});
});
复制代码
read(fd, buffer, offset, length, position, callback((err, bytesRead, buffer)))
- 第一个参数是文件描述符
- 第二个参数用来存放读取的内容的buffer
- 第三个参数存放内容的buffer的起始索引
- 第四个参数存放内容的长度
- 第五个参数读取文件的起始索引
- 第六个参数是回调函数(bytesRead是实际读取的字节数)
fs.open('./6.txt','r',0666,(err,fd) => {
let buffer=Buffer.alloc(6);//[0,1,2,3,4,5]
fs.read(fd,buffer,0,3,3,(err,bytesRead) => {
fs.read(fd,buffer,3,3,6,(err,byteRead) => {
console.log(buffer.toString());
});
});
});
复制代码
write(fd, buffer[, offset[, length[, position]]], callback)
- 第一个参数是文件描述符
- 第二个参数用来存放要写入的内容的buffer
- 第三个参数存放要写入的内容的buffer的起始索引
- 第四个参数存放要写入的内容的长度
- 第五个参数写入文件的起始索引
- 第六个参数是回调函数
fs.open('./6.txt','r+',0666,(err,fd) => {
let buffer=Buffer.from('珠峰培训');//[0,1,2,3,4,5,6,7,8,9,10,11]
//fd buffer offset
fs.write(fd,buffer,3,6,3,(err,bytesWritten) => {
console.log(err);
// 先同步缓存在进行关闭
fs.fsync(fd,(err) => {
fs.close(fd,(err) => {
console.log('关闭文件');
});
});
});
});
复制代码
fsync(fd,[callback]) (同步磁盘缓存)
close(fd,[callback])(关闭文件)
read和write实现的copy
function copy(src,dest) {
fs.open(src,'r',(err,readFd)=> {
fs.open(dest,'w',(err,writeFd) => {
let buffer=Buffer.alloc(BUFFER_SIZE);
let readed=0;
let writed=0;
function next() {
fs.read(readFd,buffer,0,BUFFER_SIZE,readed,(err,bytesRead) => {
readed+=bytesRead;
bytesRead&&fs.write(writeFd,buffer,0,bytesRead,writed,(err,bytesWritten) => {
writed+=bytesWritten;
next();
});
});
};
next();
});
});
}
复制代码
目录操作
- mkdir (创建目录的时候要求父目录必须存在)
fs.mkdir('a/b/c',err => {
console.log(err);
console.log('创建成功');
});
复制代码
- access (判断文件是否存在)
fs.access('b',(err) => {
console.log(err);
});
复制代码
同步创建目录
function mkpSync(dir) {
let parts=dir.split(path.sep);//['a','b','c']
for (let i=1;i<=parts.length;i++){
// a a/b a/b/c
let current=parts.slice(0,i).join(path.sep);
try {
fs.accessSync(current);
} catch (err) {
fs.mkdirSync(current);
}
}
}
复制代码
异步创建目录
function mkpAsync(dir,callback) {
let parts=dir.split(path.sep);//[a,b,c]
let index=1;
function next() {
if (index>parts.length) return callback();
let current=parts.slice(0,index).join(path.sep);//a
index++;
fs.access(current,(err) => {
if (err) {
fs.mkdir(current,next);
} else {
next();
}
});
}
next();
}
复制代码
终极await/async实现创建目录
function promisify(fn) {
return function (...args) {
return new Promise((resolve,reject) => {
fn.call(null,...args,err=>err? reject():resolve());
});
}
}
async function mkp(dir) {
let parts=dir.split(path.sep);//['a','b','c']
for (let i=1;i<=parts.length;i++){
// a a/b a/b/c
let current=parts.slice(0,i).join(path.sep);
try {
await promisify(fs.access)(current);
} catch (err) {
await promisify(fs.mkdir)(current);
}
}
}
复制代码
删除目录(要删除目录需要先把目录中的内容读出来)
读取目录内容、路径拼接、判断文件类型(不同方法处理)
- 删除目录(fs.rmdirSync)
- 删除文件(fs.unlinkSync)
let files = fs.readdirSync('a');
files = files.map(file => path.join('a',file));
files.forEach(file=>{
let statObj = fs.statSync(file);
if(statObj.isDirectory()){
fs.rmdirSync(file);
}else{
fs.unlinkSync(file);
}
})
复制代码
先序深度优先
// 同步
let fs = require('fs');
let path = require('path');
function removeDir(p) {
let statObj = fs.statSync(p);
if(statObj.isDirectory()){
let dirs = fs.readdirSync(p);
dirs = dirs.map(dir => path.join(p,dir));
for(let i = 0; i<dirs.length;i++){
// 深度 先将儿子移除掉 再删除掉自己
removeDir(dirs[i]);
}
fs.rmdirSync(p);
}else{
fs.unlinkSync(p);
}
}
// promise 并行
let fs = require('fs');
let path = require('path');
function removeDir(p) {
return new Promise((resolve,reject)=>{
fs.stat(p,(err,statObj)=>{ // 判断文件类型 是目录 递归 否则就删除即可
if(statObj.isDirectory()){
fs.readdir(p, function (err, dirs) {
// 映射路径
dirs = dirs.map(dir => path.join(p, dir));
// 映射promise
dirs = dirs.map(dir => removeDir(dir));
// 删除完儿子后 删除自己
Promise.all(dirs).then(() => {
fs.rmdir(p, resolve);
});
});
}else{
fs.unlink(p,resolve);
}
});
});
}
function removeDir(p,callback) {
fs.stat(p,function (err,statObj) {
if (statObj.isDirectory()){
fs.readdir(p,function (err,dirs) {
dirs = dirs.map(dir=>path.join(p,dir));
// 我们希望 可以同时删除这些目录
if(dirs.length == 0) return fs.rmdir(p,callback);
// 先预定一个函数 所有儿子都删除了的函数回调
let index = 0;
function all() {
index++;
if (index === dirs.length) fs.rmdir(p, callback);
// if (index === dirs.length) fs.rmdir(p, ()=>callback());
}
dirs.forEach(dir=>{
removeDir(dir, all);
});
});
}else{
fs.unlink(p,callback)
}
});
}
// async和await
let fs = require('fs');
let path = require('path');
let util = require('util');
let stat = util.promisify(fs.stat);
let readdir = util.promisify(fs.readdir);
let rmdir = util.promisify(fs.rmdir);
let unlink = util.promisify(fs.unlink);
async function removeDir(p) {
let statObj = await stat(p);
if(statObj.isDirectory()){
let dirs = await readdir(p);
dirs = dirs.map(dir=>path.join(p,dir));
dirs = dirs.map(dir => removeDir(dir));
await Promise.all(dirs);
await rmdir(p);
}else{
// 要等待文件删除后 才让promise执行完 所以需要await
await unlink(p);
}
}
// 回调写法 串行
let fs = require('fs');
let path = require('path');
function removeDir(p,callback) {
fs.stat(p,(err,statObj)=>{
if(statObj.isDirectory()){
fs.readdir(p,function (err,dirs) {
// 异步怎么递归?
// next函数用来递归的
dirs = dirs.map(dir => path.join(p, dir));
// 标识先删除第一个
function next(index) {
if (index === dirs.length) return fs.rmdir(p, callback)
let file = dirs[index];
// 删除目录后将下一次的删除继续传递
removeDir(file, ()=>next(index+1));
}
next(0);
})
}else{ // 文件删除执行callback即可
fs.unlink(p,callback);
}
});
}
复制代码
先序广度优先
let fs = require('fs');
let path = require('path');
function removeDir(p) {
let arr = [p];
let index = 0;
let current;
while (current = arr[index++]) {
let statObj = fs.statSync(current);
if (statObj.isDirectory()) {
let dirs = fs.readdirSync(current);
arr = [...arr, ...dirs.map(dir => path.join(current, dir))];
}
}
for (let i = arr.length - 1; i >= 0; i--) {
let statObj = fs.statSync(arr[i]);
if (statObj.isDirectory()) {
fs.rmdirSync(arr[i])
}else{
fs.unlinkSync(arr[i])
}
}
}
复制代码
可读流、可写流
流有两种模式 一种是暂停模式 一种是流动模式
createReadStream
let rs = fs.createReadStream('./1test.js',{
flags:'r', // 读取的方式
// encoding:null,// 编码 buffer
autoClose:true,
start:0,
end:9, // 包后
highWaterMark:2 // 最高水位线
});
let arr = []
rs.on('data',function (data) {
rs.pause(); // 暂停 暂停触发data事件
arr.push(data);
setTimeout(() => {
rs.resume();
}, 1000);
});
rs.on('error',function (err) {
console.log(err);
});
rs.on('end',function () {
console.log(Buffer.concat(arr).toString());
});
复制代码
createWriteStream
写 (第一次会真的往文件里写) 后面会写到缓存中。highWaterMark只是一个标识而已,一般配合着读取来用。当写入的内容超过highWaterMark的时候会暂停一下。
let fs = require('fs');
let ws = fs.createWriteStream('2.txt',{
flags:'w',
encoding:'utf8',
autoClose:true,
start:0,
highWaterMark:3
});
let flag = ws.write('1');
console.log(flag); // true
flag = ws.write('1');
console.log(flag); // true
flag = ws.write('1');
console.log(flag); // false
ws.on('drain',function () {
console.log('抽干')
});
// 抽干方法必须当前的写入的内容(内存+文件) 已经大于等于了highWater,才会触发drain,当内容全部写入后 会执行drain方法
ws.end('我死了');//会将缓存区的内容 清空后再关闭文件
ws.write('ok');// write after end不能再结束后继续写入
复制代码
手动实现的可读流(createReadStream)
let EventEmitter = require('events');
let fs = require('fs');
class ReadStream extends EventEmitter {
constructor(path, options = {}) {
super();
// 默认参数配置
this.path = path;
this.autoClose = options.autoClose || true;
this.flags = options.flags || 'r';
this.encoding = options.encoding || null;
this.start = options.start || 0;
this.end = options.end || null;
this.highWaterMark = options.highWaterMark || 64 * 1024;
// 应该有一个读取文件的位置 可变的(可变的位置)
this.pos = this.start;
// 控制当前是否是流动模式
this.flowing = null;
// 构建读取到的内容的buffer
this.buffer = Buffer.alloc(this.highWaterMark);
// 当创建可读流 要将文件打开
this.open(); // 异步执行
// 判断是否绑定了新的事件
this.on('newListener', (type) => {
if(type === 'data'){ // 用户监听了data事件,就开始读取吧
this.flowing = true;
this.read();// 开始读取文件
}
});
}
read(){
// 这时候文件还没有打开呢,等待着文件打开后再去读取
if(typeof this.fd !== 'number'){
// 等待着文件打开,再次调用read方法
return this.once('open',()=>this.read());
}
// 开始读取了
// 文件可能有10个字符串
// start 0 end 4
// 每次读三个 3
// 0-2
// 34
let howMuchToRead = this.end ? Math.min(this.highWaterMark,this.end - this.pos+1) :this.highWaterMark
// 文件描述符 读到哪个buffer里 读取到buffer的哪个位置
// 往buffer里读取几个,读取的位置
// 想读三个 文件只有2个
fs.read(this.fd, this.buffer,0,howMuchToRead,this.pos,(err,bytesRead)=>{
if (bytesRead>0){ // 读到内容了
this.pos += bytesRead;
// 保留有用的
let r = this.buffer.slice(0, bytesRead);
r = this.encoding ? r.toString(this.encoding) : r;
// 第一次读取
this.emit('data', r);
// 流动模式
if (this.flowing) {
this.read();
}
}else{
this.emit('end');
this.destroy();
}
});
}
pipe(dest){
this.on('data',(data)=>{
let flag = dest.write(data);
if(!flag){
this.pause();
}
});
dest.on('drain',()=>{
this.resume();
});
this.on('end',()=>{
this.destroy();
});
}
destroy() { // 判断文件是否打开 (将文件关闭掉)
// 文件已经打开了
if (typeof this.fd === 'number') {
fs.close(this.fd, () => {
this.emit('close');
});
return;
}
this.emit('close');
}
open() { // 打开文件的逻辑
fs.open(this.path, this.flags, (err, fd) => {
if (err) {
this.emit('error', err);
if (this.autoClose) {
this.destroy(); // 销毁 关闭文件(触发close事件)
} return;
}
this.fd = fd;
this.emit('open'); // 触发文件开启事件
});
}
pause(){
this.flowing = false;
}
resume(){
this.flowing = true;
this.read(); // 继续读取
}
}
module.exports = ReadStream;
复制代码
手动实现的可写流(createWriteStream)
let fs = require('fs');
let EventEmitter = require('events');
class WriteStream extends EventEmitter{
constructor(path,options ={}){
super();
this.path = path;
this.flags = options.flags || 'w';
this.mode = options.mode || 0o666;
this.highWaterMark = options.highWaterMark || 16*1024;
this.start = options.start || 0;
this.autoClose = options.autoClose|| true;
this.encoding = options.encoding || 'utf8';
// 是否需要触发drain事件
this.needDrain = false;
// 是否正在写入
this.writing = false;
// 缓存 正在写入就放到缓存中
this.buffer = [];
// 算一个当前缓存的个数
this.len = 0;
// 写入的时候也有位置关系
this.pos = this.start;
this.open();
}
// 0 [1 2]
write(chunk, encoding = this.encoding,callback){
chunk = Buffer.isBuffer(chunk)?chunk:Buffer.from(chunk);
this.len += chunk.length;// 每次调用write就统计一下长度
this.needDrain = this.highWaterMark <= this.len;
// this.fd
if(this.writing){
this.buffer.push({chunk,encoding,callback});
}else{
// 当文件写入后 清空缓存区的内容
this.writing = true; // 走缓存
this._write(chunk,encoding,()=>this.clearBuffer());
}
return !this.needDrain; // write 的返回值必须是true / false
}
_write(chunk,encoding,callback){
if (typeof this.fd !== 'number') {
return this.once('open', () => this._write(chunk, encoding, callback));
}
// fd是文件描述符 chunk是数据 0 写入的位置和 长度 , this.pos偏移量
fs.write(this.fd, chunk,0,chunk.length,this.pos,(err,bytesWritten)=>{
this.pos += bytesWritten;
this.len -= bytesWritten; // 写入的长度会减少
callback();
});
}
clearBuffer(){
let buf = this.buffer.shift();
if(buf){
this._write(buf.chunk, buf.encoding, () => this.clearBuffer());
}else{
this.writing = false;
this.needDrain = false; // 触发一次drain 再置回false 方便下次继续判断
this.emit('drain');
}
}
destroy(){
if(typeof this.fd === 'number'){
fs.close(this.fd,()=>{
this.emit('close');
});
return
}
this.emit('close');
}
open(){
fs.open(this.path,this.flags,this.mode,(err,fd)=>{
if(err){
this.emit('error');
this.destroy();
return
}
this.fd = fd;
this.emit('open');
});
}
}
module.exports = WriteStream;
复制代码
继承Stream的可读流
//我的流如果继承了 Readable接口 就必须要重写一个_read的方法,并且有push方法
class MyReadStream extends Readable{ // read _read
constructor(){
super();
this.index = 0;
}
_read(){
if(this.index == 5){
return this.push(null); // 读取完毕了
}
this.push(this.index+++''); // push方法也是Readble实现的
}
}
let rs = new MyReadStream();
rs.on('data',function (data) {
console.log(data);
})
rs.on('end',function () {
console.log('end');
})
复制代码
继承Stream的可写流
let { Writable } = require('stream'); // 流的模块
let fs = require('fs');
// 我的流如果继承了 Readable接口 就必须要重写一个_write的方法
class MyWriteStream extends Writable { // write _write
constructor() {
super();
}
_write(chunk,encoding,clearBuffer) {
fs.appendFile('1.txt',chunk,function () {
clearBuffer();
})
}
}
let ws = new MyWriteStream();
ws.write('hello','utf8',function () {
console.log('ok');
});
ws.write('hello', 'utf8', function () {
console.log('ok');
});
复制代码
readable
let fs = require('fs');
let rs = fs.createReadStream('./1.txt',{
autoClose:true,
start:0,
flags:'r',
encoding:'utf8',
highWaterMark:3// 默认先在杯子里 填 3滴水
})
// 暂停模式先把水杯 给你填满,自己去喝 喝多少取决于你自己
// 1).readable 当杯子里的水 是空的时候 会触发readable事件(还会将杯子里的水在填入 highWaterMark个)
// 2).如果当前杯子里的水 小于hightWaterMark 会再次读取highWaterMark个
// 3) 行读取器
rs.on('readable',()=>{
let r = rs.read(1);
console.log(rs._readableState.length);// 查看剩余的数量
setTimeout(()=>{
console.log(rs._readableState.length);
},5000)
});
复制代码
lineReader
// 行读取器 没读完一行 就把这一行的内容 发射出来
let EventEmitter = require('events');
let fs = require('fs');
class LineReader extends EventEmitter {
constructor(path) {
super();
this.path = path;
let RETURN = 13;
let LINE = 10;
this.arr = []; // 存放内容的
// \r 13 windows 怎么表示是新的一行 就用\r
// \n 10 mac 没有\r 只有\n
this._rs = fs.createReadStream(this.path); // 64k
// 判断用户监听了newLine事件
let r ;
this.on('newListener', (type) => {
if (type === 'newLine') {
this._rs.on('readable', () => {
let current; // 当前读出来的内容
while (current = this._rs.read(1)) {
switch (current[0]) {
case RETURN:
r = Buffer.concat(this.arr).toString();
this.emit('newLine', r);
this.arr = [];
// 如果下一个是换行 我就抛弃掉如果不是换行 我就留到数组里
let next = this._rs.read(1);
if (next[0] !== LINE) {
this.arr.push(current);
}
break;
case LINE:
r = Buffer.concat(this.arr).toString();
this.emit('newLine', r);
this.arr = [];
default:
this.arr.push(current);
}
}
});
this._rs.on('end', () => {
let r = Buffer.concat(this.arr).toString();
this.emit('newLine', r);
})
}
})
}
}
// 行读取器
let lineReader = new LineReader('./1.txt');
lineReader.on('newLine', (data) => {
console.log(data, '-------------');// 123 // 456 // 789
});
复制代码
转化流和双工流
双工流需要重写_write和_read方法。转化流重写_transform,应用场景在压缩。
let {Duplex,Transform} = require('stream');
class MyDuplex extends Duplex{
// 可能是没关系 也可能是有关系
_read(){
this.push('hello');
this.push(null);
}
_write(chunk,encoding,clearBuffer){
console.log(chunk);
clearBuffer();
}
}
let my = new MyDuplex();
my.on('data',function (data) {
console.log(data);
})
my.write('hello')
my.write('hello')
// 转化流 用的比较多的地方 压缩
class MyTransfer extends Transform{
_transform(chunk,encoding,clearBuffer){ // 参数和可写流是一样的
let str = chunk.toString().toUpperCase();
this.push(str);
clearBuffer();
}
}
let my = new MyTransfer();
// 会箭头可读流中的内容 把内容写入到可写流中
process.stdin.pipe(my).pipe(process.stdout); // 应用场景压缩
复制代码