自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go 中如何高效遍歷目錄?探索幾種方法

開發(fā) 前端
在本文中,我們系統(tǒng)介紹了 Go 中多種遍歷目錄文件的方法。從傳統(tǒng)的 ioutil.ReadDir?,到 Go 1.16 引入的 os.ReadDir,os.File? 的 ReadDir? 方法。

目錄遍歷是一個(gè)很常見的操作,它的使用場景有如文件目錄查看(最典型的應(yīng)用如 ls 命令)、文件系統(tǒng)清理、日志分析、項(xiàng)目構(gòu)建等。

本文將嘗試逐步介紹在 Go 中幾種遍歷目錄文件的方法,從傳統(tǒng)的 ioutil.ReadDir 函數(shù)開始,逐漸深入。

圖片圖片

文中也會(huì)提供示例代碼、提供一些性能剖析,以便于大家更好地理解。

ioutil.ReadDir

首先,Go 中目錄文件遍歷的第一種方式是 ioutil.ReadDir 函數(shù)。

在 Go 1.16 版本前,ioutil.ReadDir 就是遍歷目錄的標(biāo)準(zhǔn)方法,它的返回結(jié)構(gòu)是目錄中文件的 FileInfo 列表,簡單直接。

示例代碼:

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

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

但它的缺點(diǎn)也非常明顯,性能不高。導(dǎo)致它的主要原因有如下幾點(diǎn):

完全加載

這就導(dǎo)致了 ioutil.ReadDir 在返回結(jié)果前,會(huì)將目錄下所有文件的信息完全加載到內(nèi)存中。對(duì)于包含大量文件的目錄,它就需要在內(nèi)存中存儲(chǔ)大量的 FileInfo 對(duì)象,毫無疑問,這會(huì)增加內(nèi)存使用。

FileInfo 開銷

由于是完全加載,每個(gè) FileInfo 對(duì)象都包含了文件的詳細(xì)信息,如文件名、大小、修改時(shí)間等都會(huì)在返回之前都已經(jīng)加載完成。但獲取這些信息需進(jìn)行系統(tǒng)調(diào)用。而每個(gè)文件都要做這樣的調(diào)用,當(dāng)文件數(shù)量很多時(shí),這些系統(tǒng)調(diào)用的累積開銷可以變得不容忽視了。

無法分批處理

由于 ioutil.ReadDir 是一次性返回所有文件信息,沒有提供分批處理的能力。無論目錄中有多少文件,都要等待所有文件信息讀取完成,這在處理目錄中包含大量文件的場景中,也就無法提前并行處理,效率是可想而知的。

這一點(diǎn)其實(shí)和我們前面的一篇文章,介紹的 GO 中按行(或者說按塊)讀取文件的邏輯是類似的,一次加載全部內(nèi)容,有潛在的性能問題。

由于 ioutil.ReadDir 有這么多的缺點(diǎn),所以它在 Go 1.16 及更高版本已經(jīng)被棄用了。

那現(xiàn)在我們該用什么方法呢?

os.ReadDir

從 Go 1.16 版本起,標(biāo)準(zhǔn)庫針對(duì)目錄遍歷查看提供了新的函數(shù) os.ReadDir,以用來簡化和提高遍歷目錄文件的效率。

函數(shù)簽名如下:

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

os.ReadDir 函數(shù)返回一個(gè)按文件名排序的 DirEntry 類型切片。如果在讀取目錄項(xiàng)時(shí)遇到錯(cuò)誤,它也會(huì)盡量返回已讀取內(nèi)容。這種設(shè)計(jì)同時(shí)兼顧了效率和錯(cuò)誤處理的需要。

示例代碼:

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

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

os.ReadDir 相比于舊方法 ioutil.ReadDir 的有什么優(yōu)勢?為什么丟棄 ioutil.ReadDir 而引入這個(gè)新的 os.ReadDir。

如果對(duì)比兩者源碼,會(huì)發(fā)現(xiàn)差異主要在返回的類型上。os.ReadDir 返回的 []DirEntry 而非 []FileInfo。它還具有性能優(yōu)勢。

為什么?

因?yàn)?nbsp;DirEntry 允許按需獲取文件詳情,即懶加載,而非是遍歷目錄時(shí)立即加載所有文件屬性。很多場景下,我們并不需要

我在 MacOS 系統(tǒng)下測試的 DirEntry 接口的實(shí)際變量類型為 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)
}

我們只有在調(diào)用 Info 方法時(shí),才會(huì)真正通過 lstat 發(fā)起系統(tǒng)調(diào)用。

如果你有將舊代碼遷移到 DirEntry 的需求, Go 1.17 還引入了 fs.FileInfoToDirEntry 函數(shù),允許我們將 FileInfo 對(duì)象轉(zhuǎn)換為 DirEntry 對(duì)象。

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

看到這,對(duì)于認(rèn)真思考的朋友,或許已經(jīng)發(fā)現(xiàn)我們還有一個(gè)問題沒解決,即 os.ReadDir 不是也不支持分批處理的能力嗎?

繼續(xù)往下看吧,我將介紹一個(gè)更底層的方法。

os.File 的 ReadDir 方法

我們知道 os.Open 是用于打開文件的,但其實(shí)它也可用于打開目錄。如果 os.Open 打開的是目錄,我們在它返回的 os.File 上調(diào)用 ReadDir 以查看目錄內(nèi)容。

示例代碼:

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())
    }
}

如上的代碼其實(shí)類似于 os.ReadDir 內(nèi)容的實(shí)現(xiàn)代碼。

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
}

這種方法更底層,提供了更多的靈活性。我們就可以用它分批讀取目標(biāo)。

如何實(shí)現(xiàn)呢?

核心就是那句的 dir.ReadDir(-1),它的入?yún)⒅付嗣看巫x取文件的數(shù)量,而 -1 表示讀取目錄的所有內(nèi)容。我們只要將 -1 改為分批讀取的數(shù)量即可,多次循環(huán)即可。

示例代碼:

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

    for {
        files, err := dir.ReadDir(10) // 每批讀取10個(gè)條目
        if err == io.EOF {
            break // 遍歷完成
        }
        if err != nil {
            log.Fatal(err) // 處理其他錯(cuò)誤
        }

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

這段代碼演示了如何使用 File.ReadDir 分批處理目錄中的文件。通過這種方式,可以更有效地管理內(nèi)存使用。

補(bǔ)充一點(diǎn)

在寫這篇文章時(shí),我發(fā)現(xiàn) os.File 有兩個(gè)查看目錄的方法,分別是 Readdir 和 ReadDir。功能上的區(qū)別是新的 ReadDir 返回的是 []DirEntry,而 Readdir 返回的是 []FileInfo。

換句話說,ReadDir 本質(zhì)上是 Readdir 的升級(jí)版。

它們的函數(shù)簽名,如下所示:

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

這算是不支持可選參數(shù)和重載,但要解決兼容問題采取的措施嗎?真的是蚌埠住了。

目錄的遞歸遍歷

現(xiàn)在,還差最后一個(gè)內(nèi)容沒有介紹,那就是遞歸目錄遍歷。

針對(duì)目錄的遞歸遍歷,Go 中提供了一個(gè)專門的函數(shù),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)
    }
}

我們通過遍歷的回調(diào)函數(shù)中在處理每個(gè)文件。它簡化了目錄的遞歸遍歷,但對(duì)于大型或深層次的目錄結(jié)構(gòu),同樣存在著提前加載 FileInfo 的問題。

針對(duì)這個(gè)問題,在 Go1.16 版本也引入了基于 DirEntry 版的 filepath.WalkDir 函數(shù)。

filepath.WalkDir 的函數(shù)簽名如下:

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

fs.WalkDirFunc 的定義如下:

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

新函數(shù)的遍歷回調(diào)參數(shù)是 DirEntry,而非 FileInfo?,F(xiàn)在,filepath.WalkDir 也有了延遲加載 FileInfo 的能力了。

現(xiàn)在,我們再來看下這張圖。

圖片圖片

總結(jié)

在本文中,我們系統(tǒng)介紹了 Go 中多種遍歷目錄文件的方法。從傳統(tǒng)的 ioutil.ReadDir,到 Go 1.16 引入的 os.ReadDir,os.File 的 ReadDir 方法。每種方法適用于不同的場景,如何選擇要取決于你的需求、Go 版本、性能。如果你需要遞歸遍歷,也可以使用基于 DirEntry 的 filepath.WalkDir 實(shí)現(xiàn),提高遍歷的性能。

引用鏈接

[1] Go 中如何遍歷目錄?探索幾種方法: https://www.poloxue.com/2024-02-22-list-directory-in-golang/

責(zé)任編輯:武曉燕 來源: 碼途漫漫
相關(guān)推薦

2021-03-08 09:32:04

Python文件命令

2021-06-08 11:42:12

Pandas數(shù)據(jù)分析Python

2018-08-09 20:47:41

2020-10-16 18:35:53

JavaScript字符串正則表達(dá)式

2009-08-25 09:22:01

DataGridVie

2024-06-03 08:26:34

Android開發(fā)監(jiān)聽器

2013-08-21 11:31:21

iPhone圖片方法

2010-06-03 08:55:43

LINQ

2009-09-18 12:29:55

2010-05-17 15:17:06

MySQL常用操作

2024-10-07 09:03:15

2020-01-10 16:23:44

Springboot停止服務(wù)Java

2020-08-24 08:05:47

JavaScriptJavaScript 頁面

2009-08-31 09:19:31

c#隱藏窗口

2011-06-16 10:48:33

session

2013-02-25 14:46:49

2009-09-09 11:24:46

PHP實(shí)現(xiàn)MVC

2021-02-26 13:20:48

Shell空行Linux

2022-05-31 16:00:46

Go 編程語言復(fù)制文件Go 標(biāo)準(zhǔn)庫

2010-04-30 16:22:07

Unix終端
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)