几种常见的字符转数字方式
假设我们要将以下字符转换为数字
const s = "123456789"
ParseInt
这是一种将字符转换为int64的方式, 优点是可以将多种格式(比如二进制)的字符串转换成int64
func strconvConvert(s string) {
strconv.ParseInt(s, 10, 64)
}
Atoi
与ParseInt(s, 10, 0)行为一致, 返回int
func atoiConvert(s string) {
strconv.Atoi(s)
}
ASCII 值
必须确定字符串为可转换的格式
func asciiConvert(s string) {
n := 0
for _, ch := range []byte(s) {
ch -= '0'
n = n*10 + int(ch)
}
}
测试
goos: darwin
goarch: amd64
pkg: daemon
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkStrconvConvert
BenchmarkStrconvConvert-12 61132210 18.36 ns/op
BenchmarkAsciiConvert
BenchmarkAsciiConvert-12 362531002 3.327 ns/op
BenchmarkAtoiConvert
BenchmarkAtoiConvert-12 154894201 7.748 ns/op
可以看到, ascii值计算的方式效率最高。 所以在明确传入参数的格式情况下, 用这种方式是最优的。
原因
查看ParseInt
源码, 发现它为了兼容多种进制格式的转换,进行多次计算,与字符串判断,所以效率最低。
func ParseInt(s string, base int, bitSize int) (i int64, err error) {
const fnParseInt = "ParseInt"
if s == "" {
return 0, syntaxError(fnParseInt, s)
}
// 摘掉符号
s0 := s
neg := false
if s[0] == '+' {
s = s[1:]
} else if s[0] == '-' {
neg = true
s = s[1:]
}
// 转换为无符号并计算范围
var un uint64
// ParseUint中兼容多种进制格式, 还有多个判断
un, err = ParseUint(s, base, bitSize)
if err != nil && err.(*NumError).Err != ErrRange {
err.(*NumError).Func = fnParseInt
err.(*NumError).Num = s0
return 0, err
}
if bitSize == 0 {
bitSize = IntSize
}
cutoff := uint64(1 << uint(bitSize-1))
if !neg && un >= cutoff {
return int64(cutoff - 1), rangeError(fnParseInt, s0)
}
if neg && un > cutoff {
return -int64(cutoff), rangeError(fnParseInt, s0)
}
n := int64(un)
if neg {
n = -n
}
return n, nil
}
Atoi
中也是一样使用了Ascii
计算的方式, 并且逻辑更完善。
func Atoi(s string) (int, error) {
const fnAtoi = "Atoi"
sLen := len(s)
// 范围内的直接转换
if intSize == 32 && (0 < sLen && sLen < 10) ||
intSize == 64 && (0 < sLen && sLen < 19) {
// Fast path for small integers that fit int type.
s0 := s
if s[0] == '-' || s[0] == '+' {
s = s[1:]
if len(s) < 1 {
return 0, &NumError{fnAtoi, s0, ErrSyntax}
}
}
n := 0
for _, ch := range []byte(s) {
ch -= '0'
if ch > 9 {
return 0, &NumError{fnAtoi, s0, ErrSyntax}
}
n = n*10 + int(ch)
}
if s0[0] == '-' {
n = -n
}
return n, nil
}
// 上面代码无法转换时才会走到这个慢路径
i64, err := ParseInt(s, 10, 0)
if nerr, ok := err.(*NumError); ok {
nerr.Func = fnAtoi
}
return int(i64), err
}
而我们直接转换的方式快的原因其实是舍弃了完整的逻辑判断。