如何获取目录总大小?

问题描述 投票:0回答:4

所以我尝试使用 Go 获取目录的总大小。到目前为止我有这个:

var dirSize int64 = 0

func readSize(path string, file os.FileInfo, err error) error {
    if !file.IsDir() {
        dirSize += file.Size()
    }
    return nil
} 

func DirSizeMB(path string) float64 {
    dirSize = 0
    filepath.Walk(path, readSize)
    sizeMB := float64(dirSize) / 1024.0 / 1024.0
    sizeMB = Round(sizeMB, .5, 2)
    return sizeMB
}

问题是

dirSize
全局变量是否会导致问题,如果会,如何将其移至
DirSizeMB
函数的作用域?

go
4个回答
43
投票

使用这样的全局充其量是不好的做法。 如果同时调用

DirSizeMB
,这也是一场竞赛。

简单的解决方案是使用闭包,例如:

func DirSize(path string) (int64, error) {
    var size int64
    err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        if !info.IsDir() {
            size += info.Size()
        }
        return err
    })
    return size, err
}

游乐场

如果您认为看起来更好,可以将闭包分配给变量。


0
投票

如果你想使用变量,你可以这样做:

func DirSizeMB(path string) float64 {
    var dirSize int64 = 0

    readSize := func(path string, file os.FileInfo, err error) error {
        if !file.IsDir() {
            dirSize += file.Size()
        }

        return nil
    }

    filepath.Walk(path, readSize)    

    sizeMB := float64(dirSize) / 1024.0 / 1024.0

    return sizeMB
}

0
投票

根据@Dave C的回答,这是一个改进的函数,以获得更好的性能。我想说这快了 2 倍。

func DirSize(path string) (int64, error) {
    var size int64
    var mu sync.Mutex

    // Function to calculate size for a given path
    var calculateSize func(string) error
    calculateSize = func(p string) error {
        fileInfo, err := os.Lstat(p)
        if err != nil {
            return err
        }

        // Skip symbolic links to avoid counting them multiple times
        if fileInfo.Mode()&os.ModeSymlink != 0 {
            return nil
        }

        if fileInfo.IsDir() {
            entries, err := os.ReadDir(p)
            if err != nil {
                return err
            }
            for _, entry := range entries {
                if err := calculateSize(filepath.Join(p, entry.Name())); err != nil {
                    return err
                }
            }
        } else {
            mu.Lock()
            size += fileInfo.Size()
            mu.Unlock()
        }
        return nil
    }

    // Start calculation from the root path
    if err := calculateSize(path); err != nil {
        return 0, err
    }

    return size, nil
}

-1
投票

您可以做的一件事是在

DirSizeMB
内部定义一个通道,并在该函数内部定义
readSize
,以便它将通道作为闭包。然后将所有尺寸发送到频道外,并在收到时将其求和。

func DirSizeMB(path string) float64 {
    sizes := make(chan int64)
    readSize := func(path string, file os.FileInfo, err error) error {
        if err != nil || file == nil {
            return nil // Ignore errors
        }
        if !file.IsDir() {
            sizes <- file.Size()
        }
        return nil
    }

    go func() {
        filepath.Walk(path, readSize)
        close(sizes)
    }()

    size := int64(0)
    for s := range sizes {
        size += s
    }

    sizeMB := float64(size) / 1024.0 / 1024.0

    sizeMB = Round(sizeMB, 0.5, 2)

    return sizeMB
}

http://play.golang.org/p/zzKZu0cm9n

为什么要使用渠道?

除非您阅读过底层代码,否则您实际上并不知道

filepath.Walk
如何调用 readSize 函数。虽然它可能在给定路径上的所有文件上顺序调用它,但理论上该实现可以在单独的 goroutine 上同时调用其中的多个调用(如果确实如此,文档可能会提到这一点)。无论如何,在为并发设计的语言中,确保代码安全是很好的做法。

@DaveC 给出的答案显示了如何通过在局部变量上使用闭包来解决具有全局变量的问题,因此对 DirSize 的多个同时调用将是安全的。 Walk 的文档明确指出 walk 函数以确定的顺序运行文件,因此他的解决方案足以解决这个问题,但我将以此作为示例,说明如何安全地同时运行内部函数。

© www.soinside.com 2019 - 2024. All rights reserved.