树结构实战,获取文件夹大小

news2024/11/26 16:24:11

文件IO是一个耗时操作,要尽量避免频繁读取磁盘。

而我们需要分析磁盘的占用空间,无法避免需要读取,但是期望只通过一次文件IO操作,来获取到所有某个目录下所有文件夹的信息。

所以需要一种方式可以仅进行一轮磁盘操作,就能获取到对应目录下的所有信息。

为了达到这个目标,做了以下尝试:

  • 使用树结构来模拟文件夹结构
  • 通过一次全局IO操作获取所有原始磁盘数据,原始数据保存在内存中
  • 操作内存,完成目录下所有子文件夹信息获取

CHFileTool.showFileInfo(rootPath: NSHomeDirectory())

import UIKit

class CHFileTool: NSObject {

    static func showFileInfo(rootPath: String) {

        // 1.一次IO操作,获取所有文件信息
        let fileManager = FileManager.default

        let fileArray = try? fileManager.subpathsOfDirectory(atPath: rootPath)

        var fileModelArray = [CHFileModel]()
        fileArray?.forEach { file in
            let fullPath = rootPath + "/" + file
            let att = try? fileManager.attributesOfItem(atPath: fullPath)
            if let att {

                let fileSize = att[FileAttributeKey.size] as? Int ?? 0
                let fileType = att[FileAttributeKey.type] as? FileAttributeType
                let fileModel = CHFileModel()
                fileModel.fileSize = fileSize
                fileModel.filePath = file
                if let fileType {
                    if fileType == .typeRegular {
                        fileModel.fileType = .file
                    } else if fileType == .typeDirectory {
                        fileModel.fileType = .directory
                    } else {
                        fatalError("不支持的文件类型 \(fileType)")
                    }
                } else {
                    fatalError("文件类型获取失败, \(att)")
                }
                fileModelArray.append(fileModel)
            } else {
                fatalError("文件信息获取失败, \(fullPath)")
            }
        }

        print(fileModelArray)

        // 2.构建文件树结构, 计算文件夹大小
        let rootName = (rootPath as NSString).lastPathComponent

        let rootModel = CHFileModel()
        rootModel.fileSize = 0
        rootModel.filePath = rootName
        rootModel.fileType = .directory

        fileModelArray.forEach { model in
            let pathComponents = (model.filePath as NSString).pathComponents
            self.buildTree(parenetNode: rootModel, currentNode: model, pathComponents: pathComponents)
        }

        rootModel.subNode.forEach { (key: String, value: CHFileModel) in
            rootModel.fileSize += value.fileSize
        }


        // 3.输出文件夹信息
        print("统计信息 -- start")
        rootModel.showOneLevelInfo()

        var dirArray = [CHFileModel]()
        self.getAllDirectory(rootNode: rootModel, dirArray: &dirArray)

        print("按照文件名排序")
        dirArray.sort { pre, next in
            pre.filePath < next.filePath
        }
        print(dirArray)


        print("按照文件大小排序")
        dirArray.sort { pre, next in
            pre.fileSize > next.fileSize
        }
        print(dirArray)

        print("统计信息 -- end")

    }

    // 遍历获取根节点下所有文件夹信息
    private static func getAllDirectory(rootNode: CHFileModel, dirArray: inout [CHFileModel] ) {

        if rootNode.fileType == .directory {
            dirArray.append(rootNode)
        }
        rootNode.subNode.forEach { (key: String, value: CHFileModel) in
            self.getAllDirectory(rootNode: value, dirArray: &dirArray)
        }
    }


    // 构造树结构
    private static func buildTree(parenetNode: CHFileModel, currentNode: CHFileModel, pathComponents: [String]) {

        if pathComponents.isEmpty {
            return
        }

        var nextPathComponents = pathComponents
        let currentPath = nextPathComponents.removeFirst()

        // 查子路径
        // 子路径存在, 增加文件大小, 继续分解pathComponents
        // 子路径不存在, 创建子路径, 保存子路径, 分解pathComponents
        if let subNode = parenetNode.subNode[currentPath] {
            subNode.fileSize += currentNode.fileSize
            self.buildTree(parenetNode: subNode, currentNode: currentNode, pathComponents: nextPathComponents)

        } else {

            let subNode = CHFileModel()
            subNode.fileSize = currentNode.fileSize
            subNode.filePath = parenetNode.filePath + "/" + currentPath
            subNode.parentNode = parenetNode
            if pathComponents.count > 1 {
                subNode.fileType = .directory
            } else {
                subNode.fileType = currentNode.fileType
            }
            parenetNode.subNode[currentPath] = subNode
            self.buildTree(parenetNode: subNode, currentNode: currentNode, pathComponents: nextPathComponents)

        }
    }

}

enum CHFileType: String {
    // 占位
    case none
    // 文件类型
    case file
    // 目录类型
    case directory
}

class CHFileModel: NSObject {

    // 文件路径
    var filePath: String = ""
    // 文件大小
    var fileSize: Int = 0
    // 文件类型
    var fileType: CHFileType = .none

    // 父节点
    var parentNode: CHFileModel?

    // 目录类型下 有子节点, 文件类型为空字典
    var subNode: [String: CHFileModel] = [:]


    /// 文件大小转换
    func readAbleFileSize() -> String {

        // 系统是按照1000算的, 和系统保持一致吧
        let KB = 1000.0

        if self.fileSize < 1024 {
            return "\(self.fileSize) B"
        } else if self.fileSize < 1024 * 1024 {
            return String(format: "%.2f KB", (self.fileSize/KB))
        } else if self.fileSize < 1024 * 1024 * 1024 {
            return String(format: "%.2f MB", (self.fileSize/KB/KB))
        } else if self.fileSize < 1024 * 1024 * 1024 * 1024 {
            return String(format: "%.2f GB", (self.fileSize/KB/KB/KB))
        }
        fatalError("文件类型太大, 检查是否取错值")
    }

    @discardableResult
    func showOneLevelInfo() -> String {
        var result = self.description

        self.subNode.forEach { (key: String, value: CHFileModel) in
            result.append("\t" + value.description)
        }
        print(result)
        return result
    }


    override var description: String {
        get {
            return self.filePath + " 文件大小: \(self.readAbleFileSize()) "  + "文件类型: \(self.fileType.rawValue)\n"
        }
    }

}
import Foundation

// 左侧为CGFloat, 右侧为Int
public func * (left: CGFloat, right: Int) -> CGFloat {
    return left * CGFloat(right)
}


public func + (left: CGFloat, right: Int) -> CGFloat {
    return left + CGFloat(right)
}


public func - (left: CGFloat, right: Int) -> CGFloat {
    return left - CGFloat(right)
}


public func / (left: CGFloat, right: Int) -> CGFloat {
    if right == 0 {
        return CGFloat.nan
    } else {
        return left * CGFloat(right)
    }
}

// 左侧为Int, 右侧为CGFloat
public func * (left: Int, right: CGFloat) -> CGFloat {
    return CGFloat(left) * right
}


public func + (left: Int, right: CGFloat) -> CGFloat {
    return CGFloat(left) + right
}


public func - (left: Int, right: CGFloat) -> CGFloat {
    return CGFloat(left) - right
}


public func / (left: Int, right: CGFloat) -> CGFloat {
    if right == 0 {
        return CGFloat.nan
    } else {
        return CGFloat(left) / right
    }
}

找资料的时候发现,有更高效准确的方法获取文件夹的真实磁盘大小,

  • 获取单个文件夹的效率也提升了10倍
  • 使用att[FileAttributeKey.size] 累加和前面的字节数累加;
  • 使用totalFileAllocatedSize实测与显示简介中的磁盘数值一样;

https://www.itguest.com/post/feidce2b6.html

https://gist.github.com/NikolaiRuhe/408cefb953c4bea15506a3f80a3e5b96

https://github.com/NikolaiRuhe/NRFoundation/blob/master/NRFoundation/NRFileManager.m



import Foundation

public extension FileManager {

    /// Calculate the allocated size of a directory and all its contents on the volume.
    ///
    /// As there's no simple way to get this information from the file system the method
    /// has to crawl the entire hierarchy, accumulating the overall sum on the way.
    /// The resulting value is roughly equivalent with the amount of bytes
    /// that would become available on the volume if the directory would be deleted.
    ///
    /// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
    /// directories, hard links, ...).

    func allocatedSizeOfDirectory(at directoryURL: URL) throws -> UInt64 {

        // The error handler simply stores the error and stops traversal
        var enumeratorError: Error? = nil
        func errorHandler(_: URL, error: Error) -> Bool {
            enumeratorError = error
            return false
        }

        // We have to enumerate all directory contents, including subdirectories.
        let enumerator = self.enumerator(at: directoryURL,
                                         includingPropertiesForKeys: Array(allocatedSizeResourceKeys),
                                         options: [],
                                         errorHandler: errorHandler)!

        // We'll sum up content size here:
        var accumulatedSize: UInt64 = 0

        // Perform the traversal.
        for item in enumerator {

            // Bail out on errors from the errorHandler.
            if enumeratorError != nil { break }

            // Add up individual file sizes.
            let contentItemURL = item as! URL
            accumulatedSize += try contentItemURL.regularFileAllocatedSize()
        }

        // Rethrow errors from errorHandler.
        if let error = enumeratorError { throw error }

        return accumulatedSize
    }
}


fileprivate let allocatedSizeResourceKeys: Set<URLResourceKey> = [
    .isRegularFileKey,
    .fileAllocatedSizeKey,
    .totalFileAllocatedSizeKey,
]


fileprivate extension URL {

    func regularFileAllocatedSize() throws -> UInt64 {
        let resourceValues = try self.resourceValues(forKeys: allocatedSizeResourceKeys)

        // We only look at regular files.
        guard resourceValues.isRegularFile ?? false else {
            return 0
        }

        // To get the file's size we first try the most comprehensive value in terms of what
        // the file may use on disk. This includes metadata, compression (on file system
        // level) and block size.

        // In case totalFileAllocatedSize is unavailable we use the fallback value (excluding
        // meta data and compression) This value should always be available.

        return UInt64(resourceValues.totalFileAllocatedSize ?? resourceValues.fileAllocatedSize ?? 0)
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1399871.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】安装n卡驱动以及可能遇到的问题

文章目录 1.换源以及更新2.安装依赖3. 安装n卡驱动独显与核显切换nvidia-settings消失忘记安装依赖无法进入图形化界面的急救命令行无响应办法 1.换源以及更新 目前&#xff0c;换源完全只需要鼠标点点点就可以完成了&#xff0c;打开应用列表里的Software & Updates&…

vue3-模版引用

模版引用 ref 属性 场景&#xff1a;需要直接访问底层 DOM 元素。 方法&#xff1a;使用特殊的 ref 属性。 <input ref"input">ref 属性 允许我们在一个特定的 DOM 元素或子组件实例被挂载后&#xff0c;获得对它的直接引用。 访问模板引用 小 Demo: 当 i…

rk1126, 实现 yolov8 目标检测

基于 RKNN 1126 实现 yolov8 目标检测 Ⓜ️ RKNN 模型转换 ONNX yolo export model./weights/yolov8s.pt formatonnx导出 RKNN 这里选择输出 concat 输入两个节点 onnx::Concat_425 和 onnx::Concat_426 from rknn.api import RKNNONNX_MODEL ./weights/yolov8s.onnxRKNN_MOD…

flink operator 拉取阿里云私有镜像(其他私有类似)

创建 k8s secret kubectl --namespace flink create secret docker-registry aliyun-docker-registry --docker-serverregistry.cn-shenzhen.aliyuncs.com --docker-usernameops_acr1060896234 --docker-passwordpasswd --docker-emailDOCKER_EMAIL注意命名空间指定你使用的 我…

使用 vsCode创建GO项目

最近回顾了一下go的使用&#xff1a;具体操作看下面的参考连接&#xff0c;下面只描述一些踩过的坑&#xff1a; 1. go安装配置 安装go->配置go环境变量 推荐官网下载&#xff0c;速度很快&#xff1b; 这里需要配置五个参数&#xff1a;GOPATH/GOROOT/Path、GO111MODULE/…

GitHub提交 / 拉取时 443 fatal: unable to access ‘https:

这个问题嘛 懂得都懂 但是用了魔法后依旧会出现443错误 排查了工具发现并不是工具的问题 修改一下git代理即可解决 解决方法如下 确保魔法可用的情况下 打开魔法 打开系统设置 > 网络和Internet > 代理 找到自己的代理IP 如下 这里以我的代理IP和端口举例 在…

【工具】使用ssh进行socket5代理

文章目录 shellssh命令详解正向代理&#xff1a;反向代理&#xff1a;本地 socks5 代理 shell ssh -D 3333 root192.168.0.11 #输入密码 #3333端口已经使用远程机进行转发设置Windows全局代理转发 socks127.0.0.1 3333如果远程机为公网ip&#xff0c;可通过搜索引擎查询出网…

【C语言】数据在内存中的存储知识点(1)

一、整数在内存中的存储 二、大小端字节序和字节序判断 三、浮点数在内存中的存储

Linux的IO文件操作和文件系统

前要&#xff1a;本次我想给您带来关于 IO 和文件的知识&#xff0c;而文件在本系列中分为内存上的文件和磁盘上的文件。 1.文件概念 1.1.文件读写 在谈及系统接口之前&#xff0c;我们先来从 C 语言的角度来谈及一些前要知识&#xff0c;以辅助我们后续来理解系统 IO。 我们…

burp靶场--文件上传

burp靶场–文件上传 https://portswigger.net/web-security/file-upload/lab-file-upload-remote-code-execution-via-web-shell-upload 1.文件上传 1、原理&#xff1a;文件上传漏洞是指Web服务器允许用户将文件上传到其文件系统&#xff0c;而不充分验证文件的名称、类型、…

Shell 脚本实现自动启动程序、日志管理和定时任务监控

简介 本篇将通过Shell 脚本实现自动启动Java程序、日志管理和定时任务监控。脚本启动程序具灵活定制、可移植性和扩展性强的优点&#xff0c;可以根据需要添加额外的功能、配置选项和自定义行为&#xff0c;从而满足更具体的要求。 脚本编写 vim start_program.sh#!/bin/bas…

Lambda支持的方法引用

目录 引用类中的静态方法替换lambda引用对象实例化方法替换lambda引用类中的实例方法替换lambda引用构造器替换lambda 引用类中的静态方法替换lambda 引用类方法&#xff1a;引用类的静态方法&#xff1b;类名::静态方法名 demo: 将String类型数据转换成为Integer类型 创建一个…

MySQL 8.3 发布, 它带来哪些新变化?

1月16号 MySQL 官方发布 8.3 创新版 和 8.0.36 长期支持版本 (该版本 没有新增功能&#xff0c;更多是修复bug )&#xff0c;本文基于 官方文档 说一下 8.3 版本带来的变化。 一 增加的特性 1.1 GTID_NEXT 支持增加 TAG 选项。 之前的版本中 GTID_NEXTUUID:number &#xff…

Ant下载及安装

文章目录 前言一、Ant下载二、Ant安装三、Ant配置环境变量 前言 Ant是一个帮助构建软件的Java库和命令行工具。Ant是一个Java库和命令行工具&#xff0c;其任务是将构建文件中描述的过程驱动为相互依赖的目标和扩展点。Ant的主要已知用途是构建Java应用程序。Ant提供了许多内置…

DolphinScheduler-3.2.0集群部署教程

本文目录 1.集群部署方案(2 Master 3 Worker)2.前置准备工作3.端口说明4.DS集群部署1.时间同步2.配置用户、权限3.配置集群免密登陆4.ZK集群启动5.初始化数据库1.创建数据库、用户、授权2.解压缩安装包3.添加MySQL驱动至libs目录 6.配置文件修改1.dolphinscheduler_env.sh 配置…

为vs code配置unity开发环境

1.安装.NET.Core SDK 我们可以访问官网下载安装SDK及tool&#xff08;https://www.microsoft.com/net/download/core&#xff09;下载。有的系统只提供了执行文件&#xff0c;没有提供安装包&#xff0c;需要自己做一些配置。 下载好对应的版本就可以安装了&#xff0c;安装好以…

linux 使用笔记

1.查看运行内存 a.Free 快速查看内存的方法&#xff0c;也是经常使用的命令&#xff0c; -h 更人性化的显示内存的单元 -m 以M的形式显示 b.Top Top命令提供了实时性的运行中的程序的资源使用统计。可以根据内存的使用和大小来进行排序。 如上所示&#xff0c;top命令可以看…

实现SERVLET应用程序

实现SERVLET应用程序 Smart Software 的开发人员希望开发一个Web应用程序,使用servlet显示保存在表中的雇员信息。该应用程序需要有用户界面,用户可在该用户界面中指定要查看雇员数据的雇员ID。该界面还应显示网站被访问的次数。 解决方案 要解决上述问题,需要执行以下任务…

cs231n assignment1——SVM

整体思路 加载CIFAR-10数据集并展示部分数据数据图像归一化&#xff0c;减去均值&#xff08;也可以再除以方差&#xff09;svm_loss_naive和svm_loss_vectorized计算hinge损失&#xff0c;用拉格朗日法列hinge损失函数利用随机梯度下降法优化SVM在训练集和验证集计算准确率&a…

gin渲染篇

1. 各种数据格式的响应 json、结构体、XML、YAML类似于java的properties、ProtoBuf package mainimport ("github.com/gin-gonic/gin""github.com/gin-gonic/gin/testdata/protoexample" )// 多种响应方式 func main() {// 1.创建路由// 默认使用了2个中…