Golang: как получить общий размер каталога?

поэтому я пытаюсь получить общий размер каталога с помощью golang, пока у меня есть это

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?

3 ответов


использование глобального, как это в лучшем случае плохая практика. Это также гонка, если DirSizeMB называется одновременно.

простое решение - использовать закрытие, например:

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

площадка

вы можете назначить закрытие переменной, если считаете, что это выглядит лучше.


Если вы хотите использовать переменную, вы можете сделать это:

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
}

одна вещь, которую вы могли бы сделать, это определить канал внутри 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. Хотя вероятно, вызывает его последовательно по всем файлам на данном пути, реализация теоретически может вызвать несколько из этих вызовов одновременно на отдельных goroutines (документы, вероятно, упоминали бы об этом, если бы это было так). В любом случае, на языке, предназначенном для параллелизма, рекомендуется убедиться, что ваш код безопасен.

ответ, который дает @DaveC, показывает, как это сделать, используя закрытие над локальной переменной, решает проблему наличия глобальной переменной, поэтому несколько одновременных вызовов DirSize были бы безопасны. В документах для Walk явно указано, что функция walk работает над файлами в детерминированном порядке, поэтому его решение достаточно для этой проблемы, но я оставлю это в качестве примера того, как сделать безопасным одновременное выполнение внутренней функции.