如果想在Go語言中生成隻有隨機字符(大寫或小寫)、沒有數字的定長字符串。什麽方法最快最簡單?
這個問題要求“最快最簡單的方法”。我們將由淺入深,以循序漸進的方式給出最終最快的代碼。(可以在答案的最後找到每次迭代的基準測試。)
XX_test.gogo test -bench .
一,逐步改進的方法
1.起步(字符)
我們需要改進的原始通用解決方案是:
func init() {
rand.Seed(time.Now().UnixNano())
}
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func RandStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
2.字節
如果要選擇和匯總的字符來自隨機字符串(隻包含英文字母的大寫和小寫字母),我們可以使用字節,因為英文字母映射到UTF-8編碼中的字節是1對1(是如何存儲字符串)。
所以代替:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
我們可以用:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
甚至更好的:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
conststring常量len(letters)constslen(s)
string
所以我們的下一個方法如下:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
3.求餘數
Rand.Intn()rand.Intn()Rand.Int31n()
rand.Int63()
rand.Int63()len(letterBytes)
func RandStringBytesRmndr(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
}
return string(b)
}
rand.Int63()521<<63 - 1
0..50..12..50..16/322..55/32
4.掩碼
52 = 110100brand.Int63()0..len(letterBytes)-1
len(letterBytes)0.50.25n次pow(0.5, n)(64-52)/64 = 0.191e-8
所以新的解決方案如下:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)
func RandStringBytesMask(n int) string {
b := make([]byte, n)
for i := 0; i < n; {
if idx := int(rand.Int63() & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i++
}
}
return string(b)
}
5.掩碼改進
rand.Int63()
63/6 = 10
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
func RandStringBytesMaskImpr(n int) string {
b := make([]byte, n)
// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = rand.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
6.來源
掩碼改進非常好,我們還可以改進它,但過於複雜以至於不值得這麽做了。
現在讓我們找到其他改進的點——看看隨機數的來源。
crypto/randRead(b []byte)crypto/rand
math/randrand.Randrand.Sourcerand.SourceInt63() int64
rand.Randrandrand.Source
var src = rand.NewSource(time.Now().UnixNano())
func RandStringBytesMaskImprSrc(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
math/randRandrand.Source
math/rand
The default Source is safe for concurrent use by multiple goroutines.
rand.NewSource()Sourcerand.NewSource()Source可以
二、評測
好吧,讓我們對不同的解決方案進行基準測試。
BenchmarkRunes 1000000 1703 ns/op
BenchmarkBytes 1000000 1328 ns/op
BenchmarkBytesRmndr 1000000 1012 ns/op
BenchmarkBytesMask 1000000 1214 ns/op
BenchmarkBytesMaskImpr 5000000 395 ns/op
BenchmarkBytesMaskImprSrc 5000000 303 ns/op
隻需從字符串切換到字節,我們立即獲得22%的性能提升。
rand.Intn()rand.Int63()
掩碼(並且在大索引的情況下重複)減慢一點(由於重複調用):-20%……
rand.Int63()
rand.Sourcerand.Rand
RandStringBytesMaskImprSrc()RandStringRunes()
參考資料