← 中文资源

简体中文版本

格式化与解析人类可读字节大小

按 1024 进制格式化字节数,并把带单位的可读字符串解析回整数。

阅读英文原文

功能说明

这段代码解决两个方向的转换:把字节数格式化成 1.50 KB、10.0 MB 这样的可读文本,以及把用户输入的 1.5 MB 解析回精确的字节数。它适合命令行工具的 --size 参数、磁盘占用报表和日志展示。

格式化会根据数量级自动选择精度:小于 10 保留两位小数,小于 100 保留一位,更大的值不保留小数,避免输出过长又保留有效信息。

关键代码

formatBytes 对小于 1024 的值直接输出字节;否则不断除以 1024 并提升单位,直到数值小于 1024 或到达最大单位。strconv.FormatFloat 负责定点格式化,保证不会出现科学计数法。

parseBytes 先用 strings.Fields 拆分数字和单位,统一转大写,再用 strconv.ParseFloat 解析数字。单位通过遍历 units 找到下标,最后乘以 1024^index 并用 math.Round 四舍五入成整数。

使用注意

这里的 KB、MB 实际采用 1024 进制,语义上更接近 KiB、MiB;如果面向普通用户或需要十进制单位,应改成 1000 并调整文案。解析器不接受负数,也不接受 1,5 MB 这类本地化小数格式。

极大值乘以缩放因子后可能超出 uint64,生产代码应检查范围。格式化结果用于比较或排序时不可靠,应保留原始整数。解析只接受“数字 + 单位”两个字段,1.5MB 需要先用正则拆分。

验证方式

示例断言 0 输出 0 B、999 输出 999 B、1024 输出 1.00 KB、1536 输出 1.50 KB、10 MiB 输出 10.0 MB。parseBytes("1.5 MB") 得到 1572864 字节;单位写成 XB 会返回错误。可以再加入 1 PB 检查最大单位路径。

代码示例

package main

import (
	"fmt"
	"math"
	"strconv"
	"strings"
)

var units = []string{"B", "KB", "MB", "GB", "TB", "PB"}

func formatBytes(value uint64) string {
	if value < 1024 {
		return fmt.Sprintf("%d B", value)
	}
	amount := float64(value)
	unit := 0
	for amount >= 1024 && unit < len(units)-1 {
		amount /= 1024
		unit++
	}
	precision := 0
	if amount < 10 {
		precision = 2
	} else if amount < 100 {
		precision = 1
	}
	return strconv.FormatFloat(amount, 'f', precision, 64) + " " + units[unit]
}

func parseBytes(text string) (uint64, error) {
	fields := strings.Fields(strings.ToUpper(text))
	if len(fields) != 2 {
		return 0, fmt.Errorf("expected value and unit, got %q", text)
	}
	number, err := strconv.ParseFloat(fields[0], 64)
	if err != nil || number < 0 {
		return 0, fmt.Errorf("invalid size %q", fields[0])
	}
	index := -1
	for candidate, unit := range units {
		if unit == fields[1] {
			index = candidate
			break
		}
	}
	if index < 0 {
		return 0, fmt.Errorf("unknown unit %q", fields[1])
	}
	scale := math.Pow(1024, float64(index))
	return uint64(math.Round(number * scale)), nil
}

func main() {
	cases := []struct {
		value uint64
		want  string
	}{
		{0, "0 B"},
		{999, "999 B"},
		{1024, "1.00 KB"},
		{1536, "1.50 KB"},
		{10 * 1024 * 1024, "10.0 MB"},
	}
	for _, tc := range cases {
		got := formatBytes(tc.value)
		fmt.Printf("%d -> %s\n", tc.value, got)
		if got != tc.want {
			panic("unexpected formatting: " + got)
		}
	}
	parsed, err := parseBytes("1.5 MB")
	if err != nil || parsed != 1572864 {
		panic("unexpected parse result")
	}
	if _, err := parseBytes("12 XB"); err == nil {
		panic("unknown unit should fail")
	}
}