go 统计目录大小

时间:2021-10-25 08:28:53

文件大小获取

// 这里获取的是 FileInfo 对象
fi, _ := os.Stat(filepath)

FileInfo 定义如下:

type FileInfo interface {
Name() string // 文件的名字
Size() int64 // 普通文件返回值表示其大小;其他文件的返回值含义各系统不同
Mode() FileMode // 文件的模式位
ModTime() time.Time // 文件的修改时间
IsDir() bool // 等价于Mode().IsDir()
Sys() interface{} // 底层数据来源(可以返回nil)
}

标准库提供了 filepath.Walk 遍历目录,但是这个函数是串行的,当目录比较多时,性能很差,

这里建议自己实现,大致步骤如下:

  1. 读取给定目录下所有文件及目录;
  2. 遍历读取结果,如果是目录,创建协程重复 1;
  3. 如果是文件,获取大小,保存到 channel 中;

细节

  1. 用 channel 控制最大协程数量;
  2. WaitGroup 控制并发;
import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"time"
) //获取目录dir下的文件大小
func walkDir(dir string, wg *sync.WaitGroup, fileSizes chan<- int64) {
defer wg.Done()
for _, entry := range dirents(dir) {
if entry.IsDir() {//目录
wg.Add(1)
subDir := filepath.Join(dir, entry.Name())
go walkDir(subDir, wg, fileSizes)
} else {
fileSizes <- entry.Size()
}
}
} //sema is a counting semaphore for limiting concurrency in dirents
var sema = make(chan struct{}, 20) //读取目录dir下的文件信息
func dirents(dir string) []os.FileInfo {
sema <- struct{}{}
defer func() { <-sema }()
entries, err := ioutil.ReadDir(dir)
if err != nil {
fmt.Fprintf(os.Stderr, "du: %v\n", err)
return nil
}
return entries
} //输出文件数量的大小
func printDiskUsage(nfiles, nbytes int64) {
fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9)
} //提供-v 参数会显示程序进度信息
var verbose = flag.Bool("v", false, "show verbose progress messages") func Start() {
flag.Parse()
roots := flag.Args()//需要统计的目录
if len(roots) == 0 {
roots = []string{"."}
}
fileSizes := make(chan int64)
var wg sync.WaitGroup
for _, root := range roots {
wg.Add(1)
go walkDir(root, &wg, fileSizes)
}
go func() {
wg.Wait() //等待goroutine结束
close(fileSizes)
}()
var tick <-chan time.Time
if *verbose {
tick = time.Tick(100 * time.Millisecond) //输出时间间隔
}
var nfiles, nbytes int64
loop:
for {
select {
case size, ok := <-fileSizes:
if !ok {
break loop
}
nfiles++
nbytes += size
case <-tick:
printDiskUsage(nfiles, nbytes)
}
}
printDiskUsage(nfiles, nbytes)
}