使用Go语言获取目录磁盘占用时计算结果异常的问题
Go获取目录实际磁盘占用大小问题排查与解决
问题说明
尝试用Go获取目录的实际磁盘占用,编写的程序返回结果全部为90GB,与du -sh的实际结果不符。不想使用filepath.Walk,之前尝试os.Stat()也无法得到目录内容的总大小。
现有代码
const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) type DiskStatus struct { All uint64 Free uint64 Used uint64 } func DiskUsage(path string) (ds DiskStatus) { fs := syscall.Statfs_t{} err := syscall.Statfs(path, &fs) if err != nil { return } ds.All = fs.Blocks * uint64(fs.Bsize) ds.Free = fs.Bfree * uint64(fs.Bsize) ds.Used = ds.All - ds.Free fmt.Println(path, ds.Used) return ds } func GetDir(destination_directory string) *[]model.Directory { var dir []model.Directory items, err := ioutil.ReadDir(destination_directory) if err != nil { log.Fatalln(err) } for _, item := range items { size := DiskUsage(destination_directory+item.Name()).Used / GB item := model.Directory{Path: item.Name(), TotalSize: size} dir = append(dir, item) } return &dir }
实际目录大小(du -sh *输出)
8.0K containers 2.0G testfolder 3.2M workspace
程序运行结果
containers --> 90 testfolder --> 90 workspace --> 90
错误原因
syscall.Statfs()获取的是**整个文件系统(挂载点)**的磁盘使用情况,而非单个目录的内容占用。只要传入的路径属于同一个挂载分区,返回的Used值都是整个分区的已用空间,这就是所有目录结果一致的原因。
而os.Stat()返回的是目录自身的元数据大小(比如存储目录项的空间),不是目录下所有文件和子目录的总大小,因此也无法满足需求。
解决方案
根据需求提供两种可行方案:
方案1:调用系统du命令(推荐,与du结果一致)
利用系统自带的du工具计算目录大小,它会自动处理递归、磁盘块占用、符号链接等场景,结果和用户预期完全一致。适合Linux/macOS环境:
import ( "bytes" "fmt" "log" "os" "os/exec" "strconv" "strings" "path/filepath" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) type Directory struct { Path string TotalSize uint64 } // GetDirSize 调用du命令获取目录实际磁盘占用(字节) func GetDirSize(path string) (uint64, error) { // -s:只返回总大小;-B1:以字节为单位输出 cmd := exec.Command("du", "-s", "-B1", path) var out bytes.Buffer cmd.Stdout = &out if err := cmd.Run(); err != nil { return 0, err } parts := strings.Fields(out.String()) if len(parts) < 1 { return 0, fmt.Errorf("invalid du output for path: %s", path) } size, err := strconv.ParseUint(parts[0], 10, 64) if err != nil { return 0, err } return size, nil } func GetDir(destinationDirectory string) *[]Directory { var dirs []Directory items, err := os.ReadDir(destinationDirectory) if err != nil { log.Fatalln(err) } for _, item := range items { fullPath := filepath.Join(destinationDirectory, item.Name()) sizeBytes, err := GetDirSize(fullPath) if err != nil { log.Printf("skip %s: failed to get size: %v", item.Name(), err) continue } dirs = append(dirs, Directory{ Path: item.Name(), TotalSize: sizeBytes / GB, }) } return &dirs }
方案2:纯Go递归遍历计算(跨平台)
自己实现目录递归遍历,计算所有文件的大小总和。注意:该方案计算的是文件实际字节数,和du结果可能有差异(du计算的是磁盘块占用,小文件会占满一个块),如果需要对齐du结果,还需结合文件系统块大小计算:
import ( "log" "os" "path/filepath" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) type Directory struct { Path string TotalSize uint64 } // CalculateDirSize 递归计算目录下所有文件的字节总和 func CalculateDirSize(path string) (uint64, error) { info, err := os.Stat(path) if err != nil { return 0, err } if !info.IsDir() { return uint64(info.Size()), nil } entries, err := os.ReadDir(path) if err != nil { return 0, err } var total uint64 for _, entry := range entries { fullPath := filepath.Join(path, entry.Name()) entryInfo, err := entry.Info() if err != nil { return 0, err } if entryInfo.IsDir() { subSize, err := CalculateDirSize(fullPath) if err != nil { return 0, err } total += subSize } else { total += uint64(entryInfo.Size()) } } return total, nil } func GetDir(destinationDirectory string) *[]Directory { var dirs []Directory items, err := os.ReadDir(destinationDirectory) if err != nil { log.Fatalln(err) } for _, item := range items { fullPath := filepath.Join(destinationDirectory, item.Name()) sizeBytes, err := CalculateDirSize(fullPath) if err != nil { log.Printf("skip %s: failed to get size: %v", item.Name(), err) continue } dirs = append(dirs, Directory{ Path: item.Name(), TotalSize: sizeBytes / GB, }) } return &dirs }
注意事项
- 路径拼接使用
filepath.Join,避免手动拼接导致的跨平台路径错误。 ioutil.ReadDir已废弃,改用os.ReadDir。- 方案1依赖系统命令,Windows环境需要替换为
dir或其他等效工具。
内容的提问来源于stack exchange,提问作者siwm
相关产品推荐
相关产品推荐

