概述
上篇文章中提到Strconv包的三类功能——将字符串转不同类型(布尔、整形、浮点、字符),其他类型转字符换以及字符串转义、引号等处理;在字符串转不同类型的功能中,我们知道了**Aoti有快慢路径,Aoti = ParseInt(s, 10, 0),ParseInt能通过进制和位宽限制实现有符号数的转换,实际上处理符号后最后由ParseUint无符号转换函数通过前缀进制识别、遍历字符换解析等方法得到数字**。这篇文章中咱们继续了解Strconv将不同类型转字符串的实现方法和思路。
不同类型转字符串
布尔类型转字符串
属于是镜像函数了,ParseBool将字符串转为布尔值,FormatBool是将布尔值转换为字符串,从函数源码看到就是硬编码,返回小写的true和false;
// FormatBool returns "true" or "false" according to the value of b.func FormatBool(b bool) string { if b { return "true" } return "false"}整形类型转字符串类型
Strconv对外暴露了Itoa、FormatInt、FormatUint、AppendInt和AppendUint几个方法。其中前三个方法功能是整形转字符串,后两个方法功能是将整数以不同进制追加到字节切片中。
首先看Itoa方法,跟前文Atoi类似,Itoa is equivalent to [FormatInt](int64(i), 10).调用FormatInt(int64(i),10)将工作交给FormatInt函数就早早下班,大多数是十进制转字符串的场景下,相比FormatInt函数少写了int64和进制参数(还可能写错),更方便使用。同时Go编译器会将函数内仅调用函数的做法进行内联优化,使得编译后Itoa(i)在机器码层面等同于Format(int64(i),10),如此做法没有带来额外的性能开销,又赢了。
// Itoa is equivalent to [FormatInt](int64(i), 10).func Itoa(i int) string { return FormatInt(int64(i), 10)}Itoa函数将工作交给FormatInt后看看是怎么处理的,注释表明FormatInt返回整数i在指定进制base下的字符串表示,进制范围2<=base<=36。对于大于等于10的数字值,结果使用小写字母'a'到'z'。
const fastSmalls = true // enable fast path for small integersconst nSmalls = 100// FormatInt returns the string representation of i in the given base,// for 2 <= base <= 36. The result uses the lower-case letters 'a' to 'z'// for digit values >= 10.func FormatInt(i int64, base int) string { if fastSmalls && 0 <= i && i < nSmalls && base == 10 { return small(int(i)) } _, s := formatBits(nil, uint64(i), base, i < 0, false) return s}经常看Go源码的朋友都知道,Go官方库很喜欢搞快速路径和通用路径,上面fastSmalls固定为ture打开(改不了的),在同时满足fastSmalls为true,在nSmalls范围内(100)且参数是十进制时走快速路径,转交small函数处理;否则交给formatBits函数处理。为什么small是100啊?往下看就知道了。
// small returns the string for an i with 0 <= i < nSmalls.func small(i int) string { if i < 10 { return digits[i : i+1] } return smallsString[i*2 : i*2+2]}const smallsString = "00010203040506070809" + "10111213141516171819" + "20212223242526272829" + "30313233343536373839" + "40414243444546474849" + "50515253545556575859" + "60616263646566676869" + "70717273747576777879" + "80818283848586878889" + "90919293949596979899"const digits = "0123456789abcdefghijklmnopqrstuvwxyz"digits字符串存储了数字0-9和字母a-z;smallsString存储了一个连续的从00-99的字符串;在small函数中,如果取10以内的数,通过digitsp[i:i+1]获取,若取大于10的数从smallsString中通过smallsString[i*2:i*2+2]获取,例如我想转拿到字符串20,即通过索引[40:42]拿到。至于为什么digits后面还有字母,那也是为其他进制转换准备的(不是2~36进制都能转嘛,16进制有字母了),而smallsStrings为什么真正取值是从10以后,前面还要保留000102...,我猜是保留队形吧。
那么100内的整形转字符串搞明白了,下面来看看formatBits是怎么处理的。注释表示formatBits计算u在给定进制下的字符串表示;如果neg为true,则将u视为负的int64值(即u是绝对值,最终会添加负号);如果append_为true,则将结果字符串追加到dst中,并将扩展后的字节切片作为第一个返回值;否则将字符串作为第二个返回值返回。
后半句是AppendInt等函数也用到了formatBits函数,AppendInt是将整数以不同进制追加到字节切片函数,所以会看到将结果字符串追加到dst这句描述。
formatBits是最底层的整形转字符串函数,处理转换分为三条路径:
• 十进制转换:十进制里也分情况,首先判断 if host32bit是否为32位平台需要特殊处理,然后是十进制正常处理,里面也分了us是否<100的情况。
// u guaranteed to fit into a uint us := uint(u) for us >= 100 { is := us % 100 * 2 us /= 100 i -= 2 a[i+1] = smallsString[is+1] a[i+0] = smallsString[is+0] }// us < 100is := us * 2 i-- a[i] = smallsString[is+1]if us >= 10 { i-- a[i] = smallsString[is]}• 基数是2的幂转换( else if isPowerOfTwo(base)),注释表明使用移位和掩码代替除法和取模,base是2的幂,且 2 <= base <= len(digits),其中 len(digits)为36。小于或等于 36 的最大的 2 的幂是 32,即 1 << 5;也就是说,最大的可能移位数为5。通过将该值与常数7进行&操作,我们告诉编译器移位计数始终小于8,而8小于任何寄存器宽度这使得编译器能够为移位操作生成更好的代码。• 剩余其他进制,使用通用的方案(如下源码)
b := uint64(base) for u >= b { i-- // Avoid using r = a%b in addition to q = a/b // since 64bit division and modulo operations // are calculated by runtime functions on 32bit machines. q := u / b a[i] = digits[uint(u-q*b)] u = q } // u < base i-- a[i] = digits[uint(u)]最后看看AppendInt方法,注释所示AppendInt将整数i的字符串类型(由FormatInt)生成,追加到dis中。AppendInt方法中也分了快速路径和通用路径。如果fastSmalls为true(已默认固定开启),数字在0-100之间且转换进制为十进制,使用append(dst, small(int(i))...)处理,其实对比上面FormatInt就多了个append将dst追加到结尾,其他都是一样的。否则走通用路径 dst, _ = formatBits(dst, uint64(i), base, i < 0, true),formatBits就是上面解析的转换最底层的函数。
// AppendInt appends the string form of the integer i,// as generated by [FormatInt], to dst and returns the extended buffer.func AppendInt(dst []byte, i int64, base int) []byte { if fastSmalls && 0 <= i && i < nSmalls && base == 10 { return append(dst, small(int(i))...) } dst, _ = formatBits(dst, uint64(i), base, i < 0, true) return dst}写在最后
Itoa是为十进制int转字符串提供的快捷方法,Go编译器通过内联优化直接调用FormatInt。FormatInt执行时分为快速路径,处理0~99的十进制整数,通过digits和smallsString简单运算取值返回;否则通过formatBits通用路径处理。AppendInt则是在此基础上提供了追加到字节切片的功能。formatBits作为转换的底层函数,通过十进制处理、基数是2的幂转换和其他通用进制的兜底转换分层实现最终的功能。
夜雨聆风