Q:怎样在Go语言中简单并快速地生成固定长度的随机字符串?
A:
问题是“最快和最简单的方式”,接下来我们会一步步迭代,最终实现最快的方式。每次迭代的基准测试代码放在了答案的末尾。
XX_test.go
前言
如果您只需要一个随机字符串,最快的解决方案不是首选解决方案。Paul的解决方案(下面的第一种方法)就很好。如果很关注性能,那么前两个方法可能是可接受的折中方案:它们把性能提升了50%,而且也没有显著增加复杂性。
话虽如此,但就算你不需要最快生成随机字符串的方法,通读这篇回答相信你也应该会有所收获。
Improvements
1. Genesis (Runes)
提醒一下,下面这个方法是我们用来改进的原始通用解决方案:
2. Bytes
如果要生成的随机字符串只包含大小写英文字母,那么我们可以只使用英文字母字节,因为英文字母和UTF8编码的字节是一一对应的(这就是Go存储字符串的方式)
所以可以这么写:
或者更好的写法是:
len(letters)len(s)
所以我们的第二种方法是这样的:
3. Remainder
rand.Intn()rand.Intn()Rand.Intn()Rand.Int31n()
rand.Int63()
rand.Int63()lenletterBytes)
rand.Int63()1<<63 - 1
解释下上面字母出现概率不相等的现象:假设你要生成一个0..5之间的随机数,如果使用3个随机位,那么会导致产生数字0..1范围内的概率是2..5的两倍;如果使用5个随机位,那么产生0..1范围的数字概率是6/32, 2..5范围的概率位5/32,这已经很接近了。增加位数可以使概率差异越来越小,当达到63位时,差异已经可以忽略不计了。
4. Masking
52=110100brand.Int63()0...len(letterBytes)-1
len(letterBytes)pow(0.5, n)
所以,这个解决方法是这样的:
5. Masking Improved
前面的解决方案只使用 rand.Int63() 返回的 63 个随机位中的最低 6 位。这是一种浪费,因为获取随机位是我们算法中最慢的部分。
因为我们有52个字母,可以用6位来编码一个字母索引。所以63个随机位可以生成63/6=10个不同的索引:
6. Source
上面的 Masking Improved 方法已经非常好了,我们几乎没办法做更多的改进。可以但没必要。
现在让我们找找其他可以改进的地方:随机数的来源。
crypto/randRead(b []byte)
math/randrand.Randrand.SourceInt63() int64
rand.Randrand.Source
math/randRand
还有就是:math/rand 的包文档说明
The default Source is safe for concurrent use by multiple goroutines.(协程安全)
rand.NewSource()rand.NewSource()
7. Utilizing strings.Builder
[]rune[]bytestring
strings.Builderbytes.Buffer[]byteBuilder.String()
strings.Builder
8. "Mimicing" strings.Builder with package unsafe
strings.Builder
strings.Builder
[]bytestring
Benchmark
BenchmarkRunes-4 2000000 723 ns/op 96 B/op 2 allocs/op
BenchmarkBytes-4 3000000 550 ns/op 32 B/op 2 allocs/op
BenchmarkBytesRmndr-4 3000000 438 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMask-4 3000000 534 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImpr-4 10000000 176 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrc-4 10000000 139 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrcSB-4 10000000 134 ns/op 16 B/op 1 allocs/op
BenchmarkBytesMaskImprSrcUnsafe-4 10000000 115 ns/op 16 B/op 1 allocs/op