前言
这一节主要用递归或者while
来创建、删除目录。
创建目录
fs.mkdir
不能自动创建空目录,也就是说如果没有b目录,fs.mkdir('./a/b/c/d')
就会报错。
改善这一缺陷的同步创建目录
function mkdirSync (directory) {
let isAbsolute = path.isAbsolute(directory)
// ['', 'User', ....]
// 排除绝对路径split后第一个参数为空的情况
let dirs = directory.split('/').filter(dir => dir)
dirs.forEach((dir, idx) => {
// 数组方法为前闭后开
let current = dirs.slice(0, idx + 1).join('/')
if (isAbsolute) {
current = insertStr(current, 0, '/')
}
try {
fs.accessSync(current)
} catch (e) {
fs.mkdirSync(current)
}
})
}
mkdirSync(path.resolve(__dirname, './test/a/b/c/d'))
mkdirSync('./test/a2/b2/c2/d2')
异步创建目录
function mkdirAsync (directory, cb) {
console.log(directory)
let isAbsolute = path.isAbsolute(directory)
// ['', 'User', ....]
// 排除绝对路径split后第一个参数为空的情况
let dirs = directory.split('/').filter(dir => dir)
const dirsLen = dirs.length
function next (idx) {
if (idx === dirsLen) return cb()
let current = dirs.slice(0, idx + 1).join('/')
if (isAbsolute) {
current = insertStr(current, 0, '/')
}
fs.access(current, (err) => {
if (err) {
fs.mkdir(current, (err) => {
if (err) return console.log(err)
idx += 1
next(idx)
})
} else {
idx += 1
next(idx)
}
})
}
next(0)
}
mkdirAsync(path.resolve(__dirname, './test/e/f/g/h'), () => {
console.log('创建成功')
})
mkdirAsync('./test/d2/e2/f2/g2', () => {
console.log('创建成功')
})
异步创建目录async版本
async function mkdirAsync2 (directory) {
const sep = path.sep
const dirs = directory.split(sep)
const len = dirs.length
let idx = 1
function mk () {
if (idx > len) return // 因为slice前闭后开,所以要到len才能包含最后一位
const current = dirs.slice(0, idx).join(sep)
try {
fs.accessSync(current)
idx++
mk()
} catch (e) {
fs.mkdirSync(current)
mk()
}
}
await mk()
}
mkdirAsync2('./test/32gggggg/d/f/g/h').then(() => {
console.log('./test/g/d/f/g/h创建成功')
})
async function observeIsExist (dir) {
return new Promise((resolve, reject) => {
fs.access(dir, (err) => {
if (err) reject(err)
resolve()
})
})
}
async function mkDir (dir) {
return new Promise((resolve, reject) => {
fs.mkdir(dir, (err) => {
if (err) reject(err)
resolve()
})
})
}
async function mkDirByPromise (directory) {
const sep = path.sep
const dirs = directory.split(sep)
const len = dirs.length
for (let i = 1; i < len + 1; ++i) {
const current = dirs.slice(0, i).join(sep)
try {
await observeIsExist(current)
} catch (e) {
await mkDir(current)
}
}
}
mkDirByPromise('./test/4d/d/f/g/h').then(() => {
console.log('./test/g/d/f/g/h创建成功')
})
删除目录
fs.rmdir也是如此,只能删除一个目录,不能递归删除。
先序 深度 同步版本,效率较低
function removeDeepSync (directory) {
const statObj = fs.statSync(directory)
if (statObj.isDirectory()) {
const childDirs = fs.readdirSync(directory)
// 先删子目录
childDirs.forEach((dir) => {
removeDeepSync(path.resolve(directory, './', dir))
})
// 再删父目录
fs.rmdirSync(directory)
} else {
fs.unlinkSync(directory)
}
}
removeDeepSync('./test/a2')
先序 广度 同步版本,效率最低
// 先序 广度 同步
// 广度的基本思路:遍历每层目录,列出所有文件及文件夹,然后从最底层开始删除,一直删到最上面一层
// 比如 这样到目录结构['a', 'a/b', 'a/c', 'a/b/d', 'a/c/e'] -> 倒序删除
function removeWideSync (directory) {
let dirs = [directory]
let idx = 0
// 这里用while循环
while (dirs[idx]) {
const statObj = fs.statSync(dirs[idx])
if (statObj.isDirectory()) {
let childDirs = fs.readdirSync(dirs[idx])
// 注意这里只要和父级resolve就可以,不用每次都从0-idx,这样会造成路径多次叠加
childDirs = childDirs.map((dir) => path.resolve(dirs[idx], './', dir))
dirs = [...dirs, ...childDirs]
}
idx++
}
// 还可以用递归
// function push (idx) {
// if (idx === dirs.length) return
// const statObj = fs.statSync(dirs[idx])
// if (statObj.isDirectory()) {
// let childDirs = fs.readdirSync(dirs[idx])
// // 注意这里只要和父级resolve就可以,不用每次都从0-idx,这样会造成路径多次叠加
// childDirs = childDirs.map((dir) => path.resolve(dirs[idx], './', dir))
// dirs = [...dirs, ...childDirs]
// }
// idx += 1
// push(idx)
// }
// push(0)
dirs.reverse().forEach((dir) => {
const statObj = fs.statSync(dir)
if (statObj.isDirectory()) {
fs.rmdirSync(dir)
} else {
fs.unlink(dir)
}
})
}
removeWideSync('./test/e')
先序 异步 串行 广度,效率最低
// 先序 异步 串行 广度
// ['a', 'a/b', 'a/c', 'a/b/d', 'a/b/e, 'a/c/d', 'a/c/e']
function removeWideParallelAsync (directory, cb) {
let dirs = [directory]
function flatTree (idx) {
if (idx === dirs.length) return rm()
let current = dirs[idx]
fs.stat(current, (err, stat) => {
if (err) return console.log(err)
if (stat.isDirectory()) {
fs.readdir(current, (err, childDirs) => {
if (err) return console.log(err)
childDirs = childDirs.map((dir) => path.resolve(current, dir))
dirs = dirs.concat(childDirs)
flatTree(++idx)
})
} else {
flatTree(++idx)
}
})
}
flatTree(0)
function rm () {
const current = dirs.pop()
if (!current) return cb()
fs.stat(current, (err, stat) => {
if (err) return console.log(err)
if (stat.isDirectory()) {
fs.rmdir(current, rm)
} else {
fs.unlink(current, rm)
}
})
}
}
removeWideParallelAsync('./test/a2', () => {
console.log('删除成功')
})
先序 异步 深度 串行版本,效率较低
// 先序 异步 深度 串行
function removeDeepSerialAsync (directory, cb) {
fs.stat(directory, (err, stat) => {
if (err) return console.log(err)
if (stat.isDirectory()) {
fs.readdir(directory, (err, files) => {
if (err) return console.log(err)
let idx = 0
const len = files.length
if (files.length > 0) {
let current = path.resolve(directory, files[idx])
removeDeepSerialAsync(current, final)
} else {
fs.rmdir(directory, (err) => {
if (err) return console.log(err)
cb()
})
}
// 会发现,这个final做的事情,其实和上面的是一样的。
// 遇到这种情况,说明这个递归还可以优化。
// 将两部分互补一下,生成一个函数调用就可以了。
function final () {
if (++idx < len) {
let current = path.resolve(directory, files[idx])
removeDeepSerialAsync(current, cb)
} else {
console.log('directory', directory)
fs.rmdir(directory, (err) => {
if (err) return console.log(err)
cb()
})
}
}
})
} else {
fs.unlink(directory, cb)
}
})
}
removeDeepSerialAsync('./test/a2', () => {
console.log('成功')
})
function removeDeepSerialAsync2 (directory, cb) {
fs.stat(directory, (err, stat) => {
if (err) return console.log(err)
if (stat.isDirectory()) {
fs.readdir(directory, (err, dirs) => {
if (err) return console.log(err)
// 这里直接可以直接resolve directory的原因是
// 这里的dirs是它的直接子目录
dirs = dirs.map((dir) => path.resolve(directory, dir))
function next (idx) {
// 所有最底层的空目录在这里被删除
if (idx === dirs.length) {
fs.rmdir(directory, (err) => {
if (err) return console.log(err)
cb()
})
} else {
// 相当于有两层递归
// 这里的next用于删除相邻目录
removeDeepSerialAsync2(dirs[idx], () => next(idx + 1))
}
}
next(0)
})
} else {
// 所有最底层的文件在这里被删除
fs.unlink(directory, cb)
}
})
}
removeDeepSerialAsync2('./test/a', () => {
console.log('成功')
})
function removeDeepSerialAsync3 (directory, cb) {
fs.stat(directory, (err, stat) => {
if (err) return console.log(err)
// 如果是文件,那么继续往下深探,从最底层的开始删除,删完了删同层级的,同层级的删完了删父级本身。
if (stat.isDirectory()) {
fs.readdir(directory, (err, childDirs) => {
if (err) return console.log(err)
childDirs = childDirs.map((dir) => path.resolve(directory, dir))
function next (idx) {
if (idx === childDirs.length) {
return fs.rmdir(directory, cb)
}
removeDeepSerialAsync3(childDirs[idx], () => { next(++idx) })
}
next(0)
})
} else {
fs.unlink(directory, cb)
}
})
}
// 如果是以下目录结构
// a
// b c
// d e f h.txt
// 删除顺序就是d、e、b、f、h.txt、c、a
removeDeepSerialAsync3('./test/a', () => {
console.log('成功')
})
先序 深度 并行 异步版本,效率最高。异步的核心其实都是找到最里层的空目录和文件进行删除。
// 先序 深度 并行 异步
// 异步的核心其实都是找到最里层的**空目录**和**文件**进行删除
// 并行删除的核心就需要计数,等所有直接子目录或子文件都删除完了,就删除父级目录。
function removeDeepParallelAsync2 (directory, cb) {
fs.stat(directory, (err, stat) => {
if (err) return console.log(err)
if (stat.isDirectory()) {
fs.readdir(directory, (err, childDirs) => {
if (err) return console.log(err)
const len = childDirs.length
if (len === 0) {
fs.rmdir(directory, (err) => {
if (err) return console.log(err)
cb()
})
}
let idx = 0
childDirs.forEach((dir) => {
dir = path.resolve(directory, dir)
removeDeepParallelAsync2(dir, done)
})
function done () {
if (++idx === len) {
fs.rmdir(directory, (err) => {
if (err) return console.log(err)
cb()
})
}
}
})
} else {
fs.unlink(directory, (err) => {
if (err) return console.log(err)
cb()
})
}
})
}
removeDeepParallelAsync2('./test/a2', () => {
console.log('成功')
})
先序 异步 深度 并行
// 先序 异步 深度 并行
function removeDeepParallelAsyncPromise (directory) {
return new Promise((resolve, reject) => {
fs.stat(directory, (err, stat) => {
if (err) return console.log(err)
if (stat.isDirectory()) {
fs.readdir(directory, (err, childDirs) => {
if (err) return console.log(err)
childDirs = childDirs.map((dir) => removeDeepParallelAsyncPromise(path.resolve(directory, dir)))
Promise.all(childDirs).then(() => fs.rmdir(directory, resolve))
})
} else {
fs.unlink(directory, resolve)
}
})
})
}
removeDeepParallelAsyncPromise('./test/a').then(() => {
console.log('删除成功')
})