应逐个检查rune是否为ASCII数字并用strings.Builder拼接,避免误删非数字字符导致数字连缀;预分配容量可提升长文本处理性能。

用 strings.Builder 遍历拼接最稳妥
直接用 strings.ReplaceAll 或正则替换非数字字符,看似简单,但会把连续数字中间的非数字(比如空格、逗号)全抹掉,导致“12a34”变成“1234”,这通常不是你想要的——你只想保留原始字符串中**已经是数字字符**的部分,顺序不变。所以得逐个检查每个 rune 是否为 ASCII 数字。
推荐用 strings.Builder,它比反复 += 字符串快得多,尤其对长文本:
func extractDigits(s string) string {
var b strings.Builder
b.Grow(len(s)) // 预分配,避免多次扩容
for _, r := range s {
if r >= '0' && r <= '9' {
b.WriteRune(r)
}
}
return b.String()
}
-
b.Grow(len(s))不是必须,但能避免小字符串反复扩容,实测提升 10–20% - 用
r >= '0' && r 比 <code>unicode.IsDigit(r)更严格也更快:后者会匹配全角数字(如“1”)、阿拉伯数字等,而多数场景只要 ASCII ‘0’–‘9’ - 别用
strconv.Atoi或类似函数在循环里转换——你只是要字符,不是数值
正则方案只适合简单场景且有隐含开销
如果坚持用正则,regexp.MustCompile(`\d`) 可以提取所有 Unicode 数字字符(包括全角、阿拉伯数字),但要注意两点:
- 它比纯遍历慢 3–5 倍,因为要编译、匹配状态机、生成子匹配切片
-
\d在 Go 的regexp包里等价于[\p{Nd}],会匹配“٠١٢٣”或“0123”,如果你只想要 ASCII 数字,得写成[0-9] - 别用
FindAllString,它返回[]string,每个数字一个字符串,再strings.Join拼接,内存和时间都浪费
更合理的正则写法(仅当真需要 Unicode 数字支持时):
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
var digitRe = regexp.MustCompile(`[0-9]`)
func extractDigitsRegex(s string) string {
return digitRe.ReplaceAllString(s, "")
// 注意:这是删除非数字,不是提取;若要用提取,得 ReplaceAllStringFunc + filter,更慢
}
注意中文/Unicode 字符串里的 rune 和 byte 区别
Go 中 range s 迭代的是 rune(Unicode 码点),不是 byte。这对中文字符串很关键:如果误用 for i := 0; i ,遇到中文会取到 UTF-8 字节碎片,<code>s[i] 可能是 0xC3 这类无效值,'0' 判断永远 false,结果为空串。
- 永远优先用
for _, r := range s处理字符串内容 - 只有当你明确要按字节处理(比如解析二进制协议头),才用
[]byte(s)和索引 - 如果输入可能含 BOM 或控制字符,
rune判断依然安全;unicode.IsNumber(r)范围太宽,不建议用于此场景
性能差异在 10KB 以上字符串才明显
对短字符串(
- Builder 方案:O(n),常数小,内存局部性好
- 正则方案:O(n),但常数大,且每次调用都要查表、状态跳转
-
result += string(r):O(n²),因为每次+=都新建字符串,长度每增 1 就复制一次前面所有内容
真正容易被忽略的是:很多人写完发现“功能对了”,就不再看输入边界——比如空字符串、全是非数字、超长字符串(1MB+)。这些 case 下,Builder 的 Grow 和 rune 循环稳定性优势才会实实在在起作用。

















