美文网首页
fs模块(二)

fs模块(二)

作者: 吴晗君 | 来源:发表于2019-04-27 23:28 被阅读0次

    前言

    这一节主要用递归或者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('删除成功')
    })
    

    相关文章

      网友评论

          本文标题:fs模块(二)

          本文链接:https://www.haomeiwen.com/subject/plzlnqtx.html