Go 中如何高效遍历目录?探索几种方法

news2024/12/23 13:31:13

请添加图片描述

嗨,大家好!我是波罗学。本文是系列文章 Go 技巧第十八篇,系列文章查看:Go 语言技巧。

目录遍历是一个很常见的操作,它的使用场景有如文件目录查看(最典型的应用如 ls 命令)、文件系统清理、日志分析、项目构建等。

本文将尝试逐步介绍在 Go 中几种遍历目录文件的方法,从传统的 ioutil.ReadDir 函数开始,逐渐深入。

请添加图片描述

文中也会提供示例代码、提供一些性能剖析,以便于大家更好地理解。

ioutil.ReadDir

首先,Go 中目录文件遍历的第一种方式是 ioutil.ReadDir 函数。

在 Go 1.16 版本前,ioutil.ReadDir 就是遍历目录的标准方法,它的返回结构是目录中文件的 FileInfo 列表,简单直接。

示例代码:

func main() {
    files, err := ioutil.ReadDir(".")
    if err != nil {
        log.Fatal(err)
    }

    for _, f := range files {
        fmt.Println(f.Name())
    }
}

但它的缺点也非常明显,性能不高。导致它的主要原因有如下几点:

完全加载

这就导致了 ioutil.ReadDir 在返回结果前,会将目录下所有文件的信息完全加载到内存中。对于包含大量文件的目录,它就需要在内存中存储大量的 FileInfo 对象,毫无疑问,这会增加内存使用。

FileInfo 开销

由于是完全加载,每个 FileInfo 对象都包含了文件的详细信息,如文件名、大小、修改时间等都会在返回之前都已经加载完成。但获取这些信息需进行系统调用。而每个文件都要做这样的调用,当文件数量很多时,这些系统调用的累积开销可以变得不容忽视了。

无法分批处理

由于 ioutil.ReadDir 是一次性返回所有文件信息,没有提供分批处理的能力。无论目录中有多少文件,都要等待所有文件信息读取完成,这在处理目录中包含大量文件的场景中,也就无法提前并行处理,效率是可想而知的。

这一点其实和我们前面的一篇文章,介绍的 GO 中按行(或者说按块)读取文件的逻辑是类似的,一次加载全部内容,有潜在的性能问题。

由于 ioutil.ReadDir 有这么多的缺点,所以它在 Go 1.16 及更高版本已经被弃用了。

那现在我们该用什么方法呢?

os.ReadDir

从 Go 1.16 版本起,标准库针对目录遍历查看提供了新的函数 os.ReadDir,以用来简化和提高遍历目录文件的效率。

函数签名如下:

func ReadDir(name string) ([]DirEntry, error)

os.ReadDir 函数返回一个按文件名排序的 DirEntry 类型切片。如果在读取目录项时遇到错误,它也会尽量返回已读取内容。这种设计同时兼顾了效率和错误处理的需要。

示例代码:

func main() {
    files, err := os.ReadDir(".")
    if err != nil {
        log.Fatal(err)
    }

    for _, file := range files {
        fmt.Println(file.Name())
    }
}

os.ReadDir 相比于旧方法 ioutil.ReadDir 的有什么优势?为什么丢弃 ioutil.ReadDir 而引入这个新的 os.ReadDir

如果对比两者源码,会发现差异主要在返回的类型上。os.ReadDir 返回的 []DirEntry 而非 []FileInfo。它还具有性能优势。

为什么?

因为 DirEntry 允许按需获取文件详情,即懒加载,而非是遍历目录时立即加载所有文件属性。很多场景下,我们并不需要

我在 MacOS 系统下测试的 DirEntry 接口的实际变量类型为 os.unixDirent

它的源码如下:

func (d *unixDirent) Name() string   { return d.name }
func (d *unixDirent) IsDir() bool    { return d.typ.IsDir() }
func (d *unixDirent) Type() FileMode { return d.typ }

func (d *unixDirent) Info() (FileInfo, error) {
	if d.info != nil {
		return d.info, nil
	}
	return lstat(d.parent + "/" + d.name)
}

我们只有在调用 Info 方法时,才会真正通过 lstat 发起系统调用。

如果你有将旧代码迁移到 DirEntry 的需求, Go 1.17 还引入了 fs.FileInfoToDirEntry 函数,允许我们将 FileInfo 对象转换为 DirEntry 对象。

info, _ := os.Stat("somefile")
dirEntry := fs.FileInfoToDirEntry(info)

看到这,对于认真思考的朋友,或许已经发现我们还有一个问题没解决,即 os.ReadDir 不是也不支持分批处理的能力吗?

继续往下看吧,我将介绍一个更底层的方法。

os.FileReadDir 方法

我们知道 os.Open 是用于打开文件的,但其实它也可用于打开目录。如果 os.Open 打开的是目录,我们在它返回的 os.File 上调用 ReadDir 以查看目录内容。

示例代码:

func main() {
    dir, err := os.Open(".")
    if err != nil {
        log.Fatal(err)
    }
    defer dir.Close()

    files, err := dir.ReadDir(-1)
    if err != nil {
        log.Fatal(err)
    }

    for _, file := range files {
        fmt.Println(file.Name())
    }
}

如上的代码其实类似于 os.ReadDir 内容的实现代码。

os.ReadDir 源码如下:

func ReadDir(name string) ([]DirEntry, error) {
	f, err := Open(name)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	dirs, err := f.ReadDir(-1)
	sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
	return dirs, err
}

这种方法更底层,提供了更多的灵活性。我们就可以用它分批读取目标。

如何实现呢?

核心就是那句的 dir.Readdir(-1),它的入参指定了每次读取文件的数量,而 -1 表示读取目录的所有内容。我们只要将 -1 改为分批读取的数量即可,多次循环即可。

示例代码:

func main() {
    dir, err := os.Open(".")
    if err != nil {
        log.Fatal(err)
    }
    defer dir.Close()

    for {
        files, err := dir.ReadDir(10) // 每批读取10个条目
        if err == io.EOF {
            break // 遍历完成
        }
        if err != nil {
            log.Fatal(err) // 处理其他错误
        }

        for _, file := range files {
            fmt.Println(file.Name())
        }
    }
}

这段代码演示了如何使用 File.Readdir 分批处理目录中的文件。通过这种方式,可以更有效地管理内存使用。

补充一点

在写这篇文章时,我发现 os.File 有两个查看目录的方法,分别是 ReaddirReadDir。功能的区别的新的 ReadDir 返回的是 []DirEntry,而 Readdir 返回的是 []FileInfo

换句话说,ReadDir 本质上是 Readdir 的升级版。

它们的函数签名,如下所示:

func (f *File) Readdir(n int) ([]FileInfo, error)
func (f *File) ReadDir(n int) ([]DirEntry, error)

这是因为不支持可选参数和重载但要解决兼容问题采取的措施吗?真的是蚌埠住了。
请添加图片描述

目录的递归遍历

现在,还差最后一个内容没有介绍,那就是递归目录遍历。

针对目录的递归遍历,Go 中提供了一个专门的函数,filepath.Walk。它可以遍历指定目录下的所有子目录。

示例代码:

func main() {
    err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        fmt.Println(path)
        return nil
    })
    if err != nil {
        fmt.Printf("error walking the path %v: %v\n", ".", err)
    }
}

我们通过遍历的回调函数中在处理每个文件。它简化了目录的递归遍历,但对于大型或深层次的目录结构,同样存在着提前加载 FileInfo 的问题。

针对这个问题,在 Go1.16 版本也引入了基于 DirEntry 版的 filepath.WalkDir 函数。

filepath.WalkDir 的函数签名如下:

func WalkDir(root string, fn fs.WalkDirFunc) error

fs.WalkDirFunc 的定义如下:

type WalkDirFunc func(path string, d DirEntry, err error) error

新函数的遍历回调参数是 DirEntry,而非 FileInfo。现在,filepath.WalkDir 也有了延迟加载 FileInfo 的能力了。

现在,我们再来看下这张图。

请添加图片描述

总结

在本文中,我们系统介绍了 Go中多种遍历目录文件的方法。从传统的 ioutil.ReadDir,到 Go 1.16 引入的 os.ReadDiros.FileReadDir 方法。每种方法适用于不同的场景,如何选择要取决于你的需求、Go 版本、性能。如果你需要递归遍历,也可以使用基于 DirEntryfilepath.WalkDir 实现,提高遍历的性能。

最后,感谢阅读,请持续关注我的更多文章。

博客地址:Go 中如何遍历目录?探索几种方法

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

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

相关文章

知识积累(二):损失函数正则化与权重衰减

文章目录 1. 欧氏距离与L2范数1.1 常用的相似性度量 2. 什么是正则化&#xff1f;参考资料 本文只介绍 L2 正则化。 1. 欧氏距离与L2范数 欧氏距离也就是L2范数 1.1 常用的相似性度量 1&#xff09;点积 2&#xff09;余弦相似度 3&#xff09;L1和L2 2. 什么是正则化&…

游戏史上五个定价最高的量产型游戏机

你买过微软和索尼的下一代游戏机PS5和XBOX吗&#xff1f; PS5光驱版售价499美元&#xff0c;无光驱版售价399美元&#xff0c;高端版售价499美元&#xff0c;入门版售价低至299美元。 对于这样的定价&#xff0c;你觉得贵还是便宜呢&#xff1f; 为什么不让我们垂直比较整个游戏…

前端数据可视化:ECharts使用

可视化介绍 ​  ​  应对现在数据可视化的趋势&#xff0c;越来越多企业需要在很多场景(营销数据&#xff0c;生产数据&#xff0c;用户数据)下使用&#xff0c;可视化图表来展示体现数据&#xff0c;让数据更加直观&#xff0c;数据特点更加突出。   ​  数据可视化主要目…

【论文精读】IBOT

摘要 掩码语言建模(MLM)是一种流行的语言模型预训练范式&#xff0c;在nlp领域取得了巨大的成功。然而&#xff0c;它对视觉Transformer (ViT)的潜力尚未得到充分开发。为在视觉领域延续MLM的成功&#xff0c;故而探索掩码图像建模(MIM)&#xff0c;以训练更好的视觉transforme…

ico图片怎么制作(图片怎么变成ico格式)

ico图片一般命名为favicon.ico&#xff0c;主要用于作为缩略的网站标志&#xff0c;显示在浏览器的地址栏或者在标签中&#xff0c;一般用网站logo来制作。那么ico图片怎么制作&#xff1f;Logo图片怎么变成ico格式&#xff1f;下面boke112百科就跟大家说一说ico图片制作步骤&a…

【C++】——模板初阶 | STL简介

前言: 模板初阶 | STL简介 文章目录 一、模板初阶1.1 函数模板1.2 类模板 二、STL简介 &#xff08;了解&#xff09; 一、模板初阶 泛式编程&#xff08;Generic Programming&#xff09;指的是一种编程范式&#xff0c;其核心思想是编写可以在不同数据类型上通用的代码&#…

真的是性能优化(压测)-纯思想

文章目录 概要优化指标-MD都是文字看看就行性能优化操作1、代码优化&#xff1a;2、系统配置与环境优化&#xff1a;3、架构与设计&#xff1a;4、~~实施与监控&#xff1a;~~5、~~开发流程和环境管理&#xff1a;~~ 总结 概要 性能优化是一个持续的过程&#xff0c;需要监控、…

如何管理Windows Server磁盘分区?

Windows Server系统内置的分区软件有哪些&#xff1f; 系统内置的Windows Server分区管理软件主要分为两个——磁盘管理工具和Diskpart命令。其中磁盘管理工具可以帮助人们进行基础的磁盘、分区或卷管理任务&#xff0c;Diskpart命令则是在命令提示符中使用命令行来管理磁盘分…

如何将新标注的三元组数据转换成unicoqe可以处理的格式

目录 问题描述: 问题解决: 问题描述: 原始的标注的三元组格式如下: 需要转换的格式如下: tips:有一个小的难点: 1. 针对多三元组的情况,需要额外考虑 2. 最后一个样本,也记得需要处理

QEMU之CPU虚拟化

概述 KVM是由以色列初创公司Qumranet在CPU推出硬件虚拟化之后开发的一个基于内核的虚拟机监控器。 KVM是一个虚拟化的统称方案&#xff0c;除了x86外&#xff0c;ARM等其他架构也有自己的方案&#xff0c;所以KVM的主体代码位于内核树virt/kvm目录下面&#xff0c;表示所有CP…

JUC并发编程最新面试题(持续更新)

JUC并发编程 1 并发编程的优缺点为什么要使用并发编程&#xff08;并发编程的优点&#xff09; 充分利用多核CPU的计算能力&#xff1a;通过并发编程的形式可以将多核CPU 的计算能力发挥到极致&#xff0c;性能得到提升方便进行业务拆分&#xff0c;提升系统并发能力和性能&a…

服务器被黑该如何查找入侵痕迹以及如何防御攻击

当公司的网站服务器被黑&#xff0c;被入侵导致整个网站&#xff0c;以及业务系统瘫痪&#xff0c;给企业带来的损失无法估量&#xff0c;但是当发生服务器被攻击的情况&#xff0c;作为服务器的维护人员应当在第一时间做好安全响应&#xff0c;对服务器以及网站应以最快的时间…

深入了解Git

1.1 Git 的工作流程简介 克隆 Git 资源作为工作目录 在克隆的资源上添加或修改文件 如果其他人修改了&#xff0c;你可以更新资源 在提交前查看修改 提交修改 在修改完成后&#xff0c;如果发现错误&#xff0c;可以撤回提交并再次修改并提交 1.2 Git 工作区、暂存区和版…

86、移除推理路径上的所有内存操作

动态申请内存的影响,前两节已经介绍过了,细心的朋友可能会发现,在使用 C++实现的 resnet50 代码中,还存在一处动态申请内存的操作。 那就是对于每一层的输入或输出 feature map 数据进行内存申请,比如在 3rd_preload/ops/conv2d.cc 文件中,卷积的计算中存在对于输出 fea…

postgresql矢量切片坐标转换的ST_AsMVTGeom函数使用

一、函数签名 geometry ST_AsMVTGeom(geometry geom, box2d bounds, integer extent4096, integer buffer256, boolean clip_geomtrue); 二、描述 将一个图层中位于参数box2d范围内的一个几何图形的所有坐标转换为MapBox VectorTile坐标空间里的坐标。 该函数会尽量保持、甚至纠…

Excel Ctrl + G快捷键快速标记不同

一. 需求 如下图所示&#xff0c;水果的预计销量和实际销量存在差异&#xff0c;现在想把与预计销量不同的实际销量标记出来。 二. Ctrl G 快捷键 ⏹如下图所示&#xff0c;先选中比较区域后&#xff0c;按下Ctrl G快捷键调出定位条件面板&#xff0c; 然后选择行内容差异…

PostgreSQL 的实体化视图介绍

PostgreSQL 实体化视图提供一个强大的机制&#xff0c;通过预先计算并将查询结果集存储为物理表来提高查询性能。本教程将使用 DVD Rental Database 数据库作为演示例子&#xff0c;指导你在 PostgreSQL中创建实体化视图。 了解实体化视图 实体化视图是查询结果集的快照&…

盘点全网好用的ai伪原创工具

在信息内容发展的今天&#xff0c;写作在我们每个人的生活当中息息相关。可能写作对于有的人来说很简单&#xff0c;但对于有些人来说可能也会很难&#xff0c;幸运的是&#xff0c;我们在这个技术发达的今天&#xff0c;对于很多难题都是可以迎刃而解的&#xff0c;即使对于那…

arcgisPro制图输出

1、设置地图底图 2、导入数据 3、 设置图形颜色&#xff0c;如下&#xff1a;右键“浙江省”数据层&#xff0c;选择符号系统 4、在右侧可看到打开的符号系统栏&#xff0c;进行如下设置: 5、移除“其他所有值”项&#xff0c;如下&#xff1a; 6、设置图形轮廓&#xff0c;如下…

300分钟吃透分布式缓存-11讲:MC如何淘汰冷key和失效key?

淘汰策略 Mc 作为缓存组件&#xff0c;意味着 Mc 中只能存储访问最频繁的热数据&#xff0c;一旦存入数据超过内存限制&#xff0c;就需要对 Mc 中的冷 key 进行淘汰工作。Mc 中的 key 基本都会有过期时间&#xff0c;在 key 过期后&#xff0c;出于性能考虑&#xff0c;Mc 并…