Node.js(三)

129 阅读7分钟

核心模块之FS

  1. FS 是内置的核心模块,提供文件系统操作的API
  2. fd就是操作系统分配给被打开文件的标识

常见flag 操作符

7-操作位.png

权限位

7-权限位.png

文件操作API

  1. readFile:从指定文件中读取数据
  2. writeFile:向指定文件中写入数据
  3. appendFile:追加的方式向指定文件中写入数据
  4. copyFile:将某个文件中的数据拷贝至另一个文件
  5. watchFile:对指定文件进行监控
const fs = require('fs')
const path = require('path')
// readFile
// 第一个参数说访问文件,第二个参数是使用什么样的字符编码,第三个参数是一个回调函数
fs.readFile(path.resolve('8-data.txt'), 'utf-8', (err, data) => {
	console.log(err) // null(没有错误所以返回这个)
	console.log(data) // 徐镜泉学习
})
// writeFile,默认是覆盖操作,会将原本的内容覆盖
// 如果操作的目录不存在的话,会创建一个文件
// 第一个参数是访问的文件,第二个参数是写入的数据,第三个是一个对象,第四个是是一个回调函数
fs.writeFile(
	'8-data.txt',
	'hello node.js',
	{
		mode: 438, // 操作权限
		flag: 'r+', // 写入方式,重第一个位置写入不清空原来的内容,'w+'就是默认的配置,清空原本的内容在写入
		encoding: 'utf-8',
	},
	(err) => {
		if (!err) {
			fs.readFile(path.resolve('8-data.txt'), 'utf-8', (err, data) => {
				console.log(data) // hello node.js,
			})
		}
	}
)
// appendFile
// 第一个参数是文件路径,第二个参数是写入的内容
// 如果写入的路径不存在,会自动创建一个文件
fs.appendFile('8-data.txt', '徐镜泉学习', (err) => {
	console.log('写入成功')
})
// copyFile
// 第一个参数是数据源的路径,第二个参数是复制去那个地方
// 将拷贝的内容替换目标文件内容
fs.copyFile('8-data.txt', 'data.txt', () => {
	console.log('拷贝成功')
})
// watchFile
// 第一个参数是监控的文件路径,第二个参数是一个对象(目前表示每20毫秒监控一次)
fs.watchFile('8-data.txt', { interval: 20 }, (curr, prev) => {
	if (curr.mtime !== prev.mtime) {
		console.log('文件被修改了')
		fs.unwatchFile('8-data.txt') // 取消监控
	}
})

文件打开与关闭

  1. open
  2. close
const fs = require('fs')
const path = require('path')
// open  打开文件
fs.open(path.resolve('data.txt'),'r',(err,fd)=>{
    console.log(fd); // 21
})
// close 关闭文件
fs.open(path.resolve('data.txt'),'r',(err,fd)=>{
     // 这个时候fd可以用来追踪或者定位当前操作的文件资源
    fs.close(fd,err =>{
        console.log('关闭成功'); //关闭成功
    })
})

大文件读写操作

const fs = require('fs')
let buf = Buffer.alloc(10)
// read : 所谓的读操作就是将数据从磁盘文件中写入到 buffer 中
// data.txt 内容为 1234567890
fs.open('data.txt','r',(err,rfd)=>{
// 第一个参数(fd)就是当前操作的文件,可以通过打开文件的第二个参数追踪到当前操作的文件资源
// 第二个参数(buffer)表示当前的缓冲区
// 第三个参数 (offset) 表示从 buffer 的那个位置开始写入(写入buffer里)
// 第四个参数 (length) 表示当前写入的是长度
// 第五个参数 (position) 表示从对应的字节开始读起

fs.read(rfd,buf,0,3,0,(err,readBytes,data)=>{
    console.log(readBytes,'readBytes'); // 3 
    console.log(data,'data'); // <Buffer 31 32 33 00 00 00 00 00 00 00>
    console.log(data.toString(),'string'); // 123
})

fs.read(rfd,buf,0,4,0,(err,readBytes,data)=>{
    console.log(readBytes,'readBytes'); // 4 
    console.log(data,'data'); // <Buffer 31 32 33 34 00 00 00 00 00 00>
    console.log(data.toString(),'string'); // 1234
})

fs.read(rfd,buf,1,4,0,(err,readBytes,data)=>{
    console.log(readBytes,'readBytes'); // 4 
    console.log(data,'data'); // <Buffer 00 31 32 33 34 00 00 00 00 00> (因为读操作第一个参数是从1开始的所以第一个为空)
    console.log(data.toString(),'string'); // (这里面空格)1234
})

 fs.read(rfd,buf,0,3,2,(err,readBytes,data)=>{
    console.log(readBytes,'readBytes'); // 3 
    console.log(data,'data'); // <Buffer 33 34 35 00 00 00 00 00 00 00>
    console.log(data.toString(),'string'); // 345
})
})
// write 将缓冲区里面的内容写入的磁盘文件中
buf = Buffer.from('1234567890')
// w表示写操作
fs.open('big.txt', 'w', (err, wfd) => {

    // 第一个参数(fd)就是当前操作的文件,可以通过打开文件的第二个参数追踪到当前操作的文件资源
    // 第二个参数(buffer)表示当前的缓冲区
    // 第三个参数 (offset) 表示从 buffer 的那个位置开始读取数据写入文件
    // 第四个参数 (length) 表示当前写入的是长度
    // 第五个参数 (position) 表示从对应的字节开始读起 一般不做修改 写 0 就好了

    // fs.write(wfd, buf, 0, 3, 0, (err, written, buffer) => {
    //     // big.txt 文件内容为 123
    //     console.log(written, 'written'); // 3
    //     console.log(buffer, 'buffer'); //  <Buffer 31 32 33 34 35 36 37 38 39 30>
    //     console.log(buffer.toString(), 'buffer'); // 1234567890
    //     fs.close(wfd)
    // })

    // fs.write(wfd, buf, 0, 4, 0, (err, written, buffer) => {
    //     // big.txt 文件内容为 1234
    //     console.log(written, 'written'); // 4
    //     console.log(buffer, 'buffer'); //  <Buffer 31 32 33 34 35 36 37 38 39 30>
    //     console.log(buffer.toString(), 'buffer'); // 1234567890
    //     fs.close(wfd)
    // })

    fs.write(wfd, buf, 1, 3, 0, (err, written, buffer) => {
        // big.txt 文件内容为 234
        console.log(written, 'written'); // 3
        console.log(buffer, 'buffer'); //  <Buffer 31 32 33 34 35 36 37 38 39 30>
        console.log(buffer.toString(), 'buffer'); // 1234567890
        fs.close(wfd)
    })
})

文件拷贝自定义实现

const fs = require('fs')

/**
 * 01 打开 a 文件,利用 read 将数据保存到 buffer 暂存起来
 * 02 打开 b 文件,利用 write 将 buffer 中数据写入到 b 文件中 
 * */
let buf = Buffer.alloc(10)
// 数据的完全拷贝
const BUFFER_SIZE = buf.length
let readOffset = 0
fs.open('a.txt', 'r', (err, rfd) => {
    // a+ 表示追加操作
    fs.open('b.txt', 'a+', (err, wfd) => {
        function next () {
            fs.read(rfd, buf, 0, BUFFER_SIZE, readOffset, (err, readBytes) => {
                // readBytes  是每次读取的字节数
                if (!readBytes) {
                    // 如果条件成立说明内容已经读取完毕
                    fs.close(rfd, () => { })
                    fs.close(wfd, () => { })
                    console.log('拷贝完成');
                    return
                }
                readOffset += readBytes
                // 前面读取多少就写入多少
                fs.write(wfd, buf, 0, readBytes, (err, written) => {
                    next()
                })
            })
        }
        next()
    })
})

目录操作API(常见)

  1. access: 判断文件或目录是否具有操作权限
  2. stat:获取目录及文件信息
  3. mkdir:创建目录
  4. rmdir:删除目录
  5. readdir:读取目录中内容
  6. unlink:删除指定文件
const fs = require('fs')
// access  判断文件或目录是否具有操作权限
fs.access('a.txt', (err) => {
    if (err) {
        console.log(err);
    } else {
        console.log('有操作权限');
    }
})
// stat  获取目录及文件信息
fs.stat('a.txt', (err, statObj) => {
    console.log(statObj.size) // 大小 48
    console.log(statObj.isFile()) // 判断是否文件 true
    console.log(statObj.isDirectory()) // 判断是否目录 false
})
// mkdir 创建目录
fs.mkdir('a/b/c', (err) => {
    if (!err) {
        console.log('创建成功');
    } else {
        console.log(err); // 会报错,因为当前没有这个目录,父级目录不存在
    }
})
// mkdir 创建目录 接受第二个参数以后就不用考虑父级目录存在不存在
fs.mkdir('a/b/c', { recursive: true }, (err) => {
    if (!err) {
        console.log('创建成功');
    } else {
        console.log(err); // 会报错,因为当前没有这个目录,父级目录不存在
    }
})
// rmdir 删除目录
fs.rmdir('a/b/c', (err) => {
    // 目前删除的是c目录
    if (!err) {
        console.log('删除成功');
    } else {
        console.log(err);
    }
})
// rmdir 删除目录 recursive 为false只能删除空目录
// recursive 为 true 能删除全部目录,整个目录
fs.rmdir('a/b/c', { recursive: true }, (err) => {
    // 目前删除的是全部目录
    if (!err) {
        console.log('删除成功');
    } else {
        console.log(err);
    }
})
// readdir 读取目录中内容
fs.readdir('a', (err, files) => {
    console.log(files); // [ 'a.txt', 'b' ]
})

// readdir 读取目录中内容
fs.readdir('a/b', (err, files) => {
    console.log(files); // [ 'b.txt' ]
})
// unlink 删除指定文件
fs.unlink('a/a.txt', (err) => {
    if (!err) {
        console.log('删除成功');
    }
})

创建目录之同步实现

const fs = require('fs')
const path = require('path')

/**
 * 01 将来调用时需要接收类似于 a/b/c 这样的路径,他们之间是采用 / 去行连接
 * 02 利用 / 分隔符将路径进行拆分,将每一项放入一个数组中进行管理  ['a','b','c']
 * 03 对上述的数组进行遍历,我们需要拿到每一项,然后与前一项进行拼接 /
 * 04 判断一个当前拼接之后的路径是否具有可操作的权限,如果有则证明存在,否则的话就需要执行创建
 * */

function makeDirSync (dirPath) {
    // 获取当前操作系统的目录路径分隔符
    let items = dirPath.split(path.sep)
    // console.log(path.sep); // /
    // console.log(items);// ['a','b','c']
    for (let i = 1; i <= items.length; i++) {
        let dir = items.slice(0, i).join(path.sep)
        try {
            fs.accessSync(dir)
        } catch {
            fs.mkdirSync(dir)
        }
    }
}

makeDirSync('a/b/c')

创建目录之异步操作

const fs = require('fs')
const path = require('path')
const { promisify } = require('util') // node里面的工具包

// 第一种写法
// function mkDir (dirPath, cb) {
//     let parts = dirPath.split(path.sep)
//     let index = 1

//     function next () {
//         if (index > parts.length) return cb && cb()
//         let current = parts.slice(0, index++).join(path.sep)
//         fs.access(current, (err) => {
//             if (err) {
//                 // 没有操作权限,需要创建
//                 fs.mkdirSync(current, next)
//             } else {
//                 next()
//             }
//         })
//     }
//     next()
// }

// mkDir('a/b/c', () => {
//     console.log('创建成功');
// })

// 第二种写法
// 将 access 与 mkdir 处理成 async 风格
const access = promisify(fs.access)
const mkdir = promisify(fs.mkdir)
async function mkDir (dirPath, cb) {
    let parts = dirPath.split(path.sep)
    for (let index = 1; index <= parts.length; index++) {
        let current = parts.slice(0, index++).join(path.sep)
        try {
            await access(current)
        } catch {
            await mkdir(current)
        }
    }
    cb && cb()
}

mkDir('a/b/c', () => {
    console.log('创建成功');
})

删除目录之异步实现

const fs = require('fs')
const path = require('path')

/**
 * 需求:自定义一个函数,接收一个路径,然后执行删除
 * 01 判断当前传入的路径是否为一个文件,是的话直接删除文件即可
 * 02 如果当前传入的是一个目录,我们需要继续读取目录中的内容,然后再执行删除操作
 * 03 将删除行为定义成一个函数,然后通过递归的方式进行复用
 * 04 将当前的名称拼接成在删除时可使用的路径
*/

function myRmdir (dirPath, cb) {
    // 判断当前 dirPath 的类型
    // 获取目录及文件信息
    fs.stat(dirPath, (err, statObj) => {
        if (statObj.isDirectory()) {
            // 目录 ---> 继续读取
            fs.readdir(dirPath, (err, files) => {
                // console.log(files); // [ 'a.txt', 'b' ]
                let dirs = files.map(item => {
                    // 变成路径
                    return path.join(dirPath, item)
                })
                console.log(dirs); //[ 'a/a.txt', 'a/b' ]
                let index = 0
                function next () {
                    if (index === dirs.length) return fs.rmdir(dirPath, cb)
                    let current = dirs[index++]
                    myRmdir(current, next)
                }
                next()
            })
        } else {
            // 文件 ---> 直接删除
            fs.unlink(dirPath, cb)
        }
    })
}

// myRmdir('big.txt', () => {
//     console.log('删除成功了');
// })

// myRmdir('a', () => {
//     console.log('删除成功了');
// })

myRmdir('c', () => {
    console.log('删除成功了');
})