原文地址:https://benhoyt.com/writings/go-readdir/

原文作者:Ben Hoyt

本文永久链接:

https://github.com/gocn/translator/blob/

master/2021/w6_coming_in_go_1.16_readdir_

and_direntry.md

译者:cvley

校对:guzzsek

2021年1月

作为Python中的 os.scandir 和 PEP 471 (scandir的首次提案)的主要作者,我很开心看到将在2021年2月下旬发布的Go 1.16版本中将增加类似的函数。

在Go中,这个函数叫做 os.ReadDir,是在去年九月提出的提案 。在100多个评论和对设计进行多次细微调整后,Russ Cox 在10月提交了对应的代码。这次提交也包含了一个不感知文件系统的版本,是位于新的io/fs 包中 fs.ReadDir的函数。

为什么需要ReadDir?

简短的答案是:性能。

当调用读取文件夹路径的系统函数时,操作系统一般会返回文件名_和_它的类型(在Windows下,还包括如文件大小和最后修改时间等的stat信息)。然而,原始版本的Go和Python接口会丢掉这些额外信息,这就需要在读取每个路径时再多调用一个stat。系统调用的性能较差 ,stat 可能从磁盘、或至少从磁盘缓存读取信息。

在循环遍历目录树时,你需要知道一个路径是文件还是文件夹,这样才可以知道循环遍历的方式。因此即使一个简单的目录树遍历,也需要读取文件夹路径并获取每个路径的stat信息。但如果使用操作系统提供的文件类型信息,就可以避免那些stat系统调用,同时遍历目录的速度也将提高几倍(在网络文件系统上甚至可以快十几倍)。具体信息可以参考Python版本的基准测试。

不幸的是,两种语言中读取文件夹的最初实现都不是最优的设计,不使用额外的系统调用stat就无法获取类型信息:Python中的os.listdir和Go中的 ioutil.ReadDir

我在2012年首次想到Python的scandir背后的原理,并为2015年发布的Python 3.5实现了这个函数(从这里可以了解更多这个过程的信息)。此后这个函数不断地被改进完善:比如,增加with控制语句和文件描述符的支持。

对于Go语言,除了基于Python版本的经验提出一些改进建议的评论外,我没有参与这个提案或实现。

Python vs Go

我们看下新的“读取文件夹”的接口,尤其关注下它们在Python和Go中有多么的相似。

在Python中调用os.scandir(path),会返回一个os.DirEntry的迭代器,如下所示:

class DirEntry:# This entry's filename.name: str# This entry's full path: os.path.join(scandir_path, entry.name).path: str# Return inode or file ID for this entry.def inode(self) -> int: ...# Return True if this entry is a directory.def is_dir(self, follow_symlinks=True) -> bool: ...# Return True if this entry is a regular file.def is_file(self, follow_symlinks=True) -> bool: ...# Return True if this entry is a symbolic link.def is_symlink(self) -> bool: ...# Return stat information for this entry.def stat(self, follow_symlinks=True) -> stat_result: ...

访问namepath属性将不会抛出异常,但根据操作系统和文件系统,以及路径是否为符号链接,方法的调用可能会抛出OSError异常。比如,在Linux下,stat总是会进行一次系统调用,因此可能会抛出异常,但is_X的方法一般不会这样。

在Go语言中,调用os.ReadDir(path),将会返回一个os.DirEntry对象的切片,如下所示:

type DirEntry interface {// Returns the name of this entry's file (or subdirectory).Name() string// Reports whether the entry describes a directory.IsDir() bool// Returns the type bits for the entry (a subset of FileMode).Type() FileMode// Returns the FileInfo (stat information) for this entry.Info() (FileInfo, error)
}

尽管在真正的Go风格下,Go版本更加简单,但你一眼就可以看出二者之间多么相似。实际上,如果重新来写Python的scandir,我很可能会选择一个更简单的接口——尤其是要去掉follow_symlinks参数,不让它默认跟随处理符号链接。

下面是一个使用os.scandir的例子——一个循环计算文件夹及其子文件夹中文件的总大小的函数:

def get_tree_size(path):total = 0with os.scandir(path) as entries:for entry in entries:if entry.is_dir(follow_symlinks=False):total += get_tree_size(entry.path)else:total += entry.stat(follow_symlinks=False).st_sizereturn total

在Go中(一旦1.16发布),对应的函数如下所示:

func GetTreeSize(path string) (int64, error) {entries, err := os.ReadDir(path)if err != nil {return 0, err}var total int64for _, entry := range entries {if entry.IsDir() {size, err := GetTreeSize(filepath.Join(path, entry.Name()))if err != nil {return 0, err}total += size} else {info, err := entry.Info()if err != nil {return 0, err}total += info.Size()}}return total, nil
}

高级结构很相似,当然有人可能会说:“看,Go的错误处理多么繁琐!”没错——Python代码非常简洁。在简短脚本的情况下这没有问题,而这也是Python的优势。

然而,在生产环境的代码中,或者在一个频繁使用的命令行工具库中,捕获stat调用的错误会更好,进而可以忽略权限错误或者记录日志。Go代码可以明确看到错误发生的情况,可以让你轻松添加日志或者打印的错误信息更好。

更高级的目录树遍历

另外,两个语言都有更高级的循环遍历目录的函数。在Python中,它是os.walk。Python中scandir的美妙之处在于os.walk的签名无需改变,因此所有os.walk的用户(有非常多)都可以自动得到加速。

比如,使用os.walk打印文件夹下所有非点的路径:

def list_non_dot(path):paths = []for root, dirs, files in os.walk(path):# Modify dirs to skip directories starting with '.'dirs[:] = [d for d in dirs if not d.startswith('.')]for f in files:if f.startswith('.'):continuepaths.append(os.path.join(root, f))return sorted(paths)

从Python3.5开始,os.walk底层使用scandir代替listdir,根据操作系统和文件系统,这可以显著提升1.5到20倍的速度。

Go (pre-1.16版本)语言中有一个相似的函数,filepath.Walk,但不幸的是 FileInfo 接口的设计无法支持各种方法调用时的错误报告。正如我们所知,有时函数会进行系统调用——比如,像Size这样的统计信息在Linux下总是需要一次系统调用。因此在Go语言中,这些方法需要返回错误(在Python中它们会抛出异常)。

是否要尝试去掉错误处理的逻辑来重复使用 FileInfo 接口,这样现有代码就可以显著提速。实际上,Russ Cox提出一个提案 issue 41188就是这个思路(提供了一些数据来表明这个想法并不像听起来那么不靠谱)。然而,stat 确实会返回错误,因此像文件大小这样潜在的属性应该在错误时返回0。这样对应的结果是,要把这个逻辑嵌入到现有的API中,需要大量需要推动改动的地方,最后Russ确认 无法就此达成共识,并提出 DirEntry 接口。

这表明,为了获得性能提升, filepath.Walk 的调用需要改成 filepath.WalkDir ——尽管非常相似,但遍历函数的参数是DirEntry 而不是 FileInfo

下面的代码是Go版本的使用现有filepath.Walk 函数的list_non_dot

func ListNonDot(path string) ([]string, error) {var paths []stringerr := filepath.Walk(path, func(p string, info os.FileInfo,err error) error {if strings.HasPrefix(info.Name(), ".") {if info.IsDir() {return filepath.SkipDir}return err}if !info.IsDir() {paths = append(paths, p)}return err})return paths, err
}

当然,在Go 1.16中这段代码也可以运行,但如果你想得到性能收益就需要做少许修改——在上面的代码中仅需要把 Walk 替换为 WalkDir,并把 os.FileInfo 替换成 os.DirEntry

err := filepath.WalkDir(path, func(p string, info os.DirEntry,

对于这么修改的价值,在我的Linux home文件夹下运行第一个函数,在缓存后花费约580ms。使用Go 1.16中的新版本花费约370ms——差不多快了1.5倍。差异并不大,但也是有意义的——在网络文件系统和Windows下将会得到更多的加速效果。

总结

新的ReadDir API易于使用,通过 fs.ReadDir可以便捷地集成新的文件系统。相比于加速现有的Walk调用,你所需要替换成WalkDir的改动微不足道。

API 的设计非常难。跨平台、操作系统相关的API设计更加困难。希望你在设计下一个编程语言的标准库时可以设计正确!:-)

无论如何,我很开心可以看到Go对于文件夹读取的支持将不在落后——或者说_努力_紧追——Python。

Go 1.16 即将到来的函数:ReadDir 和 DirEntry相关推荐

  1. 准备:新V8即将到来,Node.js的性能正在改变

    V8的Turbofan的性能特点将如何对我们优化的方式产生影响 审阅:来自V8团队的Franziska Hinkelmann和Benedikt Meurer. **更新:Node.js 8.3.0已经 ...

  2. JDK 16 即将发布,迎来重大改变,新特性速览!

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:硬刚一周,3W字总结,一年的经验告诉你如何准备校招! 个人原创100W+访问量博客:点击前往,查看更多 来源: ...

  3. java拦截器_Java工程师年底跳槽高潮即将到来,斩获满意offer的必备技巧(二)

    哈喽,大家好,相信看过小编Java工程师年底跳槽高潮即将到来,斩获满意offer的必备技巧(一)文章的朋友应该就比较的了解这里都是干货,希望这些干货能够助大家在这2019-2020年关跳槽季斩获满意的 ...

  4. 用OpenGL导演一场烟花盛会,迎接即将到来的新年

    忙碌了一年,今天终于放假了.原本打算好好休息一下,没成想只过了半天就觉得有点无聊.看家人和朋友们都在忙年,那我就用OpenGL导演一场烟花盛会,献给即将到来的新年吧. 一说到OpenGL,很多人都会觉 ...

  5. excel行列突出显示_在Excel中突出显示即将到来的日期

    excel行列突出显示 Do you use Excel to keep track of upcoming payments, or other dates? To make that list m ...

  6. 流行编曲宿主DAW的新版本FL Studio 21“即将到来”

    FL Studio 21即将推出 Image Line调侃道,在预览新功能时,流行DAW的新版本"即将到来" 流行编曲宿主DAW的新版本FL Studio 21"即将到来 ...

  7. PCIe 6.0时代即将到来 你准备好了吗?

    PCIe 1.0版本于2002年发布,当时我已经在IT行业了,有幸摸爬滚打过这些年头. PCIe 6.0的速率在5.0的32 GT/s基础上,又翻了一倍达到64 GT/s,信号调制从NRZ改为PAM4 ...

  8. 谁将主导世界货币?即将到来的新一轮全球危机

    <谁将主导世界货币?即将到来的新一轮全球危机> 基本信息 作者: 詹姆斯.理查兹 出版社:中信出版社 ISBN:9787508633541 上架时间:2012-6-4 出版日期:2012 ...

  9. JDK 16 即将发布!网友:新特性挺酸爽a...

    点击上方[全栈开发者社区]→右上角[...]→[设为星标⭐] 来源 | https://blog.csdn.net/csdnnews/article/details/110483909 你还能追上 J ...

最新文章

  1. 中山大学提出SimAM:无参Attention!助力分类/检测/分割涨点!
  2. ShapeMatching Using Fourier Descriptor
  3. 云给数据中心带来了什么特性?
  4. 图片怎么等比缩放_图片300kb,50kb压缩【方法笔记】
  5. 物理服务器转虚拟服务器,物理服务器转虚拟服务器
  6. input自适应_深度残差网络+自适应参数化ReLU(调参记录18)Cifar10~94.28%
  7. 查看windosw服务器序列号,型号
  8. 计算机教务管理系统ER图,图书管理系统er图 [2].doc
  9. App工程从Eclipse迁移到Android Studio的问题总结
  10. EDA实验课课程笔记(八 )——PT(Prime Time)简介(附录静态时序分析)
  11. 轻松获得卡巴斯基KEY
  12. javascript实现常用的设计模式
  13. R 语言数据处理入门-2(缺失值处理)
  14. 如何用计算机作图并求斜率,简单斜率分析以及作图
  15. link和import的区别,src和href的区别,css hark 以及HTML5及css3的新增特性
  16. 如果我有一颗私人卫星……|潮科技有奖问答评论精选 ②
  17. 一位二本毕业4年的java程序员
  18. [Android]如何做一个崩溃率少于千分之三噶应用app(7)-跨module交互
  19. win10的wsapp把电脑卡死
  20. IBM Websphere MQ 基础0:Linux下安装IBM MQ 7.5

热门文章

  1. 启动模拟器失败!检测到模拟器上次未完全关闭,请稍后重启电脑再次尝试!
  2. servlet中请求转发forword与重定向redirect区别
  3. 单纯形法只有两个约束条件_线性规划之单纯形法
  4. 慧点科技CEO吕翊:我们做SmartGo移动平台的两个理由
  5. 算法基础入门—求圆面积
  6. Linux 下安装skype
  7. 给自己心灵一杯下午茶
  8. 制作U盘启动盘,CentOS系统安装
  9. 径向基(Radial Basis Function:RBF)神经网络学习笔记
  10. Sonar介绍及使用