用 go-units 解析 1.5GB、格式化 9000 秒和 Docker 资源限制
项目地址:github.com/docker/go-units
1. 为什么需要 go-units
用户输入 "1KB"、"1MB"、"1GB",需要转换为字节数时,常见做法是手写字符串处理。把秒数转成 "2 hours 30 minutes" 这样的可读格式,也要自己处理转换逻辑。做 Docker 相关项目时,还会碰到容器资源限制里的各种单位。
go-units 是 Docker 团队维护的单位转换库,用来处理大小、时间、字节转换。
先看一个真实场景:用户输入文件大小 "1.5GB",需要转换为字节数。用正则表达式:
func parseSize(size string) (int64, error) {
re := regexp.MustCompile(`^(\d+(?:\.\d+)?)\s*([KMGTPE]?)B?$`)
matches := re.FindStringSubmatch(size)
if len(matches) != 3 {
return 0, fmt.Errorf("invalid size format")
}
value, _ := strconv.ParseFloat(matches[1], 64)
unit := matches[2]
var multiplier int64 = 1
switch unit {
case "K":
multiplier = 1024
case "M":
multiplier = 1024 * 1024
case "G":
multiplier = 1024 * 1024 * 1024
case "T":
multiplier = 1024 * 1024 * 1024 * 1024
}
return int64(value * float64(multiplier)), nil
}
bytes, err := parseSize("1.5GB")
用 go-units:
import "github.com/docker/go-units"
bytes, err := units.RAMInBytes("1.5GB")
// bytes = 1610612736
一行代码。
2. go-units 是什么
go-units 是一个 Go 语言的单位转换库。官方描述是:
Unit conversion library for Docker.
它面向 Docker,支持大小、时间、字节等转换。
核心定位
go-units 不是数据处理库,也不是数据可视化库。它是一个单位转换库,专门用于处理各种单位的转换。
它做的事情:
- 字符串 → 字节数:
"1.5GB"→1610612736 - 字节数 → 字符串:
1610612736→"1.5 GiB" - 秒数 → 可读时间:
9000→"2 hours 30 minutes" - 时间 → 秒数:
"2 hours"→7200
功能不复杂,覆盖的是单位转换的常见需求。
3. 核心特性:大小、时间、字节
大小转换
import "github.com/docker/go-units"
// 字符串 → 字节
bytes, _ := units.RAMInBytes("1.5GB")
fmt.Println(bytes) // 1610612736
// 字节 → 字符串
size := units.BytesSize(1610612736)
fmt.Println(size) // 1.5 GiB
// SI 格式(1000 进制)
size = units.BytesSizeBase10(1500000000)
fmt.Println(size) // 1.5 GB
时间转换
// 秒数 → 可读时间
duration := units.HumanDuration(9000 * time.Second)
fmt.Println(duration) // 2 hours 30 minutes
// 字符串 → 时间
d, _ := time.ParseDuration("2h30m")
fmt.Println(d.Seconds()) // 9000
字节格式化
// 精确格式化
size := units.BytesSize(1234567)
fmt.Println(size) // 1.18 MiB
// 整数格式化
size = units.BytesSize(1024)
fmt.Println(size) // 1 KiB
4. 安装和使用
安装
go get github.com/docker/go-units
基本使用
1. 解析大小
package main
import (
"fmt"
"github.com/docker/go-units"
)
func main() {
// 解析各种格式
sizes := []string{
"1B",
"1KB",
"1MB",
"1GB",
"1.5GB",
"1GiB",
}
for _, s := range sizes {
bytes, err := units.RAMInBytes(s)
if err != nil {
fmt.Printf("Error parsing %s: %v\n", s, err)
continue
}
fmt.Printf("%s = %d bytes\n", s, bytes)
}
}
2. 格式化大小
func main() {
sizes := []int64{
1024,
1048576,
1073741824,
1610612736,
}
for _, s := range sizes {
fmt.Printf("%d bytes = %s\n", s, units.BytesSize(float64(s)))
}
}
3. 时间格式化
func main() {
durations := []time.Duration{
30 * time.Second,
5 * time.Minute,
2 * time.Hour,
2*time.Hour + 30*time.Minute,
25 * time.Hour,
}
for _, d := range durations {
fmt.Printf("%v = %s\n", d, units.HumanDuration(d))
}
}
4. 解析时间
func main() {
times := []string{
"30s",
"5m",
"2h",
"2h30m",
"25h",
}
for _, t := range times {
d, err := time.ParseDuration(t)
if err != nil {
fmt.Printf("Error parsing %s: %v\n", t, err)
continue
}
fmt.Printf("%s = %v (%s)\n", t, d, units.HumanDuration(d))
}
}
5. 核心功能
功能一:精确大小解析
func main() {
// 精确解析,支持各种格式
inputs := []string{
"1024",
"1KB",
"1 KiB",
"1.5 GB",
"1.5GiB",
}
for _, input := range inputs {
bytes, err := units.FromHumanSize(input)
if err != nil {
fmt.Printf("Error: %v\n", err)
continue
}
fmt.Printf("%s = %d bytes\n", input, bytes)
}
}
功能二:文件大小格式化
func main() {
sizes := []float64{
500,
1024,
1048576,
1073741824,
1099511627776,
}
for _, size := range sizes {
// IEC 格式(1024 进制)
fmt.Printf("%.0f bytes = %s\n", size, units.BytesSize(size))
// SI 格式(1000 进制)
fmt.Printf("%.0f bytes = %s\n", size, units.BytesSizeBase10(size))
}
}
功能三:时间格式化
func main() {
// 各种时间长度
durations := []time.Duration{
time.Second,
time.Minute,
time.Hour,
24 * time.Hour,
7 * 24 * time.Hour,
}
for _, d := range durations {
fmt.Printf("%v = %s\n", d, units.HumanDuration(d))
}
}
功能四:自定义格式化
func formatBytes(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %ciB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
func main() {
fmt.Println(formatBytes(1234567)) // 1.2 MiB
}
6. 实战场景
场景一:Docker 资源限制
问题:解析 Docker 容器的内存限制。
type ContainerConfig struct {
Name string
Memory string
CPUPeriod string
}
func ParseContainerConfig(config ContainerConfig) (int64, error) {
// 解析内存限制
memory, err := units.RAMInBytes(config.Memory)
if err != nil {
return 0, fmt.Errorf("invalid memory: %w", err)
}
fmt.Printf("Container %s: Memory = %d bytes (%s)\n",
config.Name, memory, units.BytesSize(float64(memory)))
return memory, nil
}
func main() {
configs := []ContainerConfig{
{"web-server", "512MB", "100000"},
{"database", "2GB", "200000"},
{"cache", "1GiB", "50000"},
}
for _, config := range configs {
memory, err := ParseContainerConfig(config)
if err != nil {
fmt.Printf("Error: %v\n", err)
continue
}
fmt.Printf("Parsed: %d bytes\n\n", memory)
}
}
效果:自动解析 "512MB"、"2GB"、"1GiB" 等格式。
场景二:磁盘空间显示
问题:显示磁盘使用情况,需要人类可读。
type DiskUsage struct {
Total int64
Used int64
Free int64
}
func DisplayDiskUsage(usage DiskUsage) {
usedPercent := float64(usage.Used) / float64(usage.Total) * 100
fmt.Println("=== 磁盘使用情况 ===")
fmt.Printf("总空间:%s\n", units.BytesSize(float64(usage.Total)))
fmt.Printf("已使用:%s (%.1f%%)\n",
units.BytesSize(float64(usage.Used)), usedPercent)
fmt.Printf("可用:%s\n", units.BytesSize(float64(usage.Free)))
}
func main() {
usage := DiskUsage{
Total: 500 * 1024 * 1024 * 1024, // 500 GB
Used: 250 * 1024 * 1024 * 1024, // 250 GB
Free: 250 * 1024 * 1024 * 1024, // 250 GB
}
DisplayDiskUsage(usage)
}
效果:显示 "465.7 GiB"、"232.8 GiB (50.0%)" 等可读格式。
场景三:运行时间显示
问题:显示程序运行时间,需要人类可读。
type ProcessInfo struct {
Name string
StartTime time.Time
}
func DisplayProcessInfo(info ProcessInfo) {
runtime := time.Since(info.StartTime)
fmt.Printf("进程:%s\n", info.Name)
fmt.Printf("启动时间:%s\n", info.StartTime.Format("2006-01-02 15:04:05"))
fmt.Printf("运行时间:%s\n", units.HumanDuration(runtime))
}
func main() {
info := ProcessInfo{
Name: "web-server",
StartTime: time.Now().Add(-2*time.Hour - 30*time.Minute),
}
DisplayProcessInfo(info)
}
效果:显示 "2 hours 30 minutes" 等可读格式。
7. 设计亮点
亮点一:Docker 出品
Docker 团队开发,经过大规模验证,稳定可靠,持续维护。
亮点二:格式全面
支持各种格式的大小、时间。一个库覆盖需求,减少依赖,统一风格。
亮点三:双向转换
支持格式化和解析。既能显示给用户,也能从用户输入解析,数据往返一致。
亮点四:IEC/SI 双标准
支持 1024 进制和 1000 进制。适应不同场景,符合行业标准,选择灵活。
亮点五:零依赖
纯 Go 实现,无外部依赖。易于集成,编译快,跨平台。
8. 和类似方案对比
vs. go-humanize
- go-units:约 5K Star,Docker 团队维护,专注单位转换,大小解析强。
- go-humanize:约 2.8K Star,个人项目,偏数据格式化,大小解析一般。
结论:go-units 更专业,go-humanize 更全面。
vs. 手动转换
- go-units:代码量少,可维护性高,正确性高,格式支持丰富。
- 手动转换:代码量多,可维护性低,容易出错,格式支持有限。
结论:go-units 更高效,手动转换更灵活。
vs. 其他单位库
- go-units:约 5K Star,原生支持 Docker,维护活跃。
- golang-units:约 1K Star,无 Docker 支持,维护较少。
结论:go-units 是 Docker 生态标准。
9. 局限性
go-units 也有局限:
- 功能单一:只做单位转换
- Docker 导向:主要服务于 Docker
- 格式有限:某些特殊格式不支持
- 维护较少:更新不频繁
- 文档简单:文档不够详细
- 测试覆盖:某些边缘情况未覆盖
10. 总结
从 go-units 这个项目可以看到几个点:
- Docker 生态重要:Docker 团队的库在容器场景下可复用
- 单位转换是刚需:大小、时间转换很常见
- 双向转换有价值:格式化和解析都需要
- 标准很重要:IEC/SI 双标准,适应不同场景
- 零依赖是优势:易于集成,无额外负担
go-units 解决的是单位转换这一具体问题。如果还在手动处理单位转换,可以看看它的 API 是否覆盖需求。也许有一天,Go 标准库会提供内置的单位转换功能。但在那之前,go-units 已经能承担这类工作。