如果我们想生成一个固定长度的随机字符串,包含大小写字母,不包含数字,不包含特殊字符串,我们应该怎么做呢?需要做哪些优化,让它更简单、更高效?在最终解决方案之前,先看看最常见的写法是什么,然后如何一步步演化到最终的高效解决方案。好吧,还是先看看最原始的方案吧。常见做法(Runes)]rune,n)fori:=rangeb{b[i]=letterRunes[rand.Intn(len(letterRunes))]}returnstring(b)}**这个实现比较简单,二十六个字母(upperandlowercase),然后随机取数得到一个随机字符串。字节改进我们在一开始就假设我们的随机字符串只包含大写和小写字母。这样一来,我们发现没必要使用rune类型存储,因为在Golang(Go语言)UTF-8编码中,英文字母和bytebyte是一一对应的。byte本质是uint8类型,rune本质是int32类型。我们改进后的代码如下:len(letterBytes))]}returnstring(b)}Copy仔细看上面的代码,我们不仅改进了符文类型,还把原来的字母变量变成了一个常量,所以len(letterBytes)也是一个常量,代码效率会大大提高。余项改进我们之前的方案都是通过调用rand.Intn()生成的随机字符。这个rand.Intn()其实就是delegate调用的Rand.Intn(),Rand.Intn()最终调用的是Rand。Int31n()实现。与直接调用rand.Int63()相比,rand.Intn()慢得多。所以我们可以把rand.Intn()换成rand.Int63()来提高效率。为了不超过letterBytes的索引范围,我们用余数来保证。funcRandStringBytesRmndr(nint)string{b:=*ke([]byte,n)fori:=rangeb{b[i]=letterBytes[rand.Int63()%int*(len(letterBytes))]}返回字符串(b)}这种**方式虽然快,但是有一个缺点,就是每个字母出现的概率可能不一样,但是52个字母相对于1<<63-1来说太小了,所以在这种情况下,这个缺点可以忽略。*sking掩码是在前面方案的基础上进一步改进的,我们可以通过使用随机数的最低位来保证字母的均等分布,这就是*sking的方式。我们现在有52个字母,52在二进制中是52==110100b,所以我们可以直接使用rand.Int63()返回最低的6位。为了保证平均分,如果返回值只大于len(letterBytes)-1,则丢弃。constletterBytes="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"const(letterIdxBits=6//表示字母索引的6位letterIdx*sk=1<=0;{ifre*in==0{cache,re*in=rand.Int63(),letterIdx*x}ifidx:=int(cache&letterIdx*sk);idx>=letterIdxBitsre*in--}returnstring(b)}将生成的63位随机数**成10份,每份可以被我们使用,这样我们调用rand.Int63()的次数就会大大减少,从而提高效率。rand源优化rand.Rand实际上使用rand.Source作为生成随机数的源。这个rand.Source是一个接口,恰好有一个funcInt63()int*方法。//一个Source代表一个均匀分布的来源//伪随机int*值在范围[0,1<<63).typeSourceinte***ce{Int63()int*Seed(seedint*)}Copy这正是我们需要,对我们来说已经足够了。改进后的代码如下:varsrc=rand.NewSource(time.Now().UnixNano())funcRandStringByte*askImprSrc(nint)string{b:=*ke([]byte,n)//Asrc.Int63()生成63个随机位,足够letterIdx*x个字符!对于i,缓存,保留:=n-1,src.Int63(),letterIdx*x;我>=0;{ifre*in==0{cache,re*in=src.Int63(),letterIdx*x}ifidx:=int(cache&letterIdx*sk);idx>=letterIdxBitsre*in--}returnstring(b)}Copy原来的rand.Int63()对整个rand包是全局的,并且支持安全高并发,所以速度比较慢。现在我们自己创建的src只是自己用的,所以效率比较高。strings.Builder改进这是G01.10的新功能,提高了字符串拼接的效率。这方面大家可以参考我之前写的三篇文章,这里就不过多介绍了。Go语言字符串高效拼接(一)Go语言字符串高效拼接(二)Go语言字符串高效拼接(三)改进后代码如下:funcRandStringByte*askImprSrc*(nint)string{*:=strings.Builder{}*.Grow(n)//src.Int63()生成63个随机位,足够letterIdx*x个字符!对于i,缓存,保留:=n-1,src.Int63(),letterIdx*x;我>=0;{ifre*in==0{cache,re*in=src.Int63(),letterIdx*x}ifidx:=int(cache&letterIdx*sk);idx>=letterIdxBitsre*in--}return*.String()}Copy使用unsafe包模拟strings.Builderstrings的原理。Builder其实很简单。它内置了一个[]byte来存储字符,最后将它们转换为字符串以避免**,使用unsafe包。//String返回累加的string.func(b*Builder)String()string{return*(*string)(unsafe.Pointer(&b.buf))}把上面的**过来,我们自己做,看重写在代码之后。funcRandStringByte*askImprSrcUnsafe(nint)string{b:=*ke([]byte,n)//Asrc.Int63()生成63个随机位,足够letterIdx*x个字符!对于i,缓存,保留:=n-1,src.Int63(),letterIdx*x;我>=0;{ifre*in==0{cache,re*in=src.Int63(),letterIdx*x}ifidx:=int(cache&letterIdx*sk);idx