Skip to content

Commit 778a092

Browse files
authored
feat: 优化生成随机字符串 (#154)
see issues #121
1 parent 4ea333f commit 778a092

File tree

1 file changed

+73
-42
lines changed

1 file changed

+73
-42
lines changed

strutil/random.go

+73-42
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package strutil
22

33
import (
4-
mRand "math/rand"
4+
"math"
5+
"math/rand"
56
"time"
7+
"unsafe"
68

79
"github.com/gookit/goutil/byteutil"
810
"github.com/gookit/goutil/encodes"
911
)
1012

1113
// some consts string chars
1214
const (
13-
Numbers = "0123456789"
14-
HexChars = "0123456789abcdef" // base16
15+
MaximumCapacity = 1 << 31
16+
Numbers = "0123456789"
17+
HexChars = "0123456789abcdef" // base16
1518

1619
AlphaBet = "abcdefghijklmnopqrstuvwxyz"
1720
AlphaBet1 = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
@@ -21,64 +24,92 @@ const (
2124
AlphaNum3 = "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
2225
)
2326

24-
func newRand() *mRand.Rand {
25-
return mRand.New(mRand.NewSource(time.Now().UnixNano()))
27+
var rn = rand.NewSource(time.Now().UnixNano())
28+
29+
// nearestPowerOfTwo 返回一个大于等于cap的最近的2的整数次幂,参考java8的hashmap的tableSizeFor函数
30+
// cap 输入参数
31+
// 返回一个大于等于cap的最近的2的整数次幂
32+
func nearestPowerOfTwo(cap int) int {
33+
n := cap - 1
34+
n |= n >> 1
35+
n |= n >> 2
36+
n |= n >> 4
37+
n |= n >> 8
38+
n |= n >> 16
39+
if n < 0 {
40+
return 1
41+
} else if n >= MaximumCapacity {
42+
return MaximumCapacity
43+
}
44+
return n + 1
2645
}
2746

28-
// RandomChars generate give length random chars at `a-z`
29-
func RandomChars(ln int) string {
30-
cs := make([]byte, ln)
47+
// buildRandomString 生成随机字符串
48+
// letters 字符串模板
49+
// length 生成长度
50+
// 返回一个指定长度的随机字符串
51+
func buildRandomString(letters string, length int) string {
52+
// 仿照strings.Builder
53+
// 创建一个长度为 length 的字节切片
54+
bytes := make([]byte, length)
55+
strLength := len(letters)
56+
if strLength <= 0 {
57+
return ""
58+
} else if strLength == 1 {
59+
for i := 0; i < length; i++ {
60+
bytes[i] = letters[0]
61+
}
62+
return *(*string)(unsafe.Pointer(&bytes))
63+
}
64+
// letters的字符需要使用多少个比特位数才能表示完
65+
// letterIdBits := int(math.Ceil(math.Log2(strLength))),下面比上面的代码快
66+
letterIdBits := int(math.Log2(float64(nearestPowerOfTwo(strLength))))
67+
// 最大的字母id掩码
68+
var letterIdMask int64 = 1<<letterIdBits - 1
69+
// 可用次数的最大值
70+
letterIdMax := 63 / letterIdBits
3171
// UnixNano: 1607400451937462000
32-
rn := newRand()
33-
34-
for i := 0; i < ln; i++ {
35-
// rand in 0 - 25
36-
cs[i] = AlphaBet[rn.Intn(25)]
72+
// 循环生成随机字符串
73+
for i, cache, remain := length-1, rn.Int63(), letterIdMax; i >= 0; {
74+
// 检查随机数生成器是否用尽所有随机数
75+
if remain == 0 {
76+
cache, remain = rn.Int63(), letterIdMax
77+
}
78+
// 从可用字符的字符串中随机选择一个字符
79+
if idx := int(cache & letterIdMask); idx < len(letters) {
80+
bytes[i] = letters[idx]
81+
i--
82+
}
83+
// 右移比特位数,为下次选择字符做准备
84+
cache >>= letterIdBits
85+
remain--
3786
}
38-
return string(cs)
87+
// 仿照strings.Builder用unsafe包返回一个字符串,避免拷贝
88+
// 将字节切片转换为字符串并返回
89+
return *(*string)(unsafe.Pointer(&bytes))
90+
}
91+
92+
// RandomChars generate give length random chars at `a-z`
93+
func RandomChars(ln int) string {
94+
return buildRandomString(AlphaBet, ln)
3995
}
4096

4197
// RandomCharsV2 generate give length random chars in `0-9a-z`
4298
func RandomCharsV2(ln int) string {
43-
cs := make([]byte, ln)
44-
// UnixNano: 1607400451937462000
45-
rn := newRand()
46-
47-
for i := 0; i < ln; i++ {
48-
// rand in 0 - 35
49-
cs[i] = AlphaNum[rn.Intn(35)]
50-
}
51-
return string(cs)
99+
return buildRandomString(AlphaNum, ln)
52100
}
53101

54102
// RandomCharsV3 generate give length random chars in `0-9a-zA-Z`
55103
func RandomCharsV3(ln int) string {
56-
cs := make([]byte, ln)
57-
// UnixNano: 1607400451937462000
58-
rn := newRand()
59-
60-
for i := 0; i < ln; i++ {
61-
// rand in 0 - 61
62-
cs[i] = AlphaNum2[rn.Intn(61)]
63-
}
64-
return string(cs)
104+
return buildRandomString(AlphaNum2, ln)
65105
}
66106

67107
// RandWithTpl generate random string with give template
68108
func RandWithTpl(n int, letters string) string {
69109
if len(letters) == 0 {
70110
letters = AlphaNum2
71111
}
72-
73-
ln := len(letters)
74-
cs := make([]byte, n)
75-
rn := newRand()
76-
77-
for i := 0; i < n; i++ {
78-
// rand in 0 - ln
79-
cs[i] = letters[rn.Intn(ln)]
80-
}
81-
return byteutil.String(cs)
112+
return buildRandomString(letters, n)
82113
}
83114

84115
// RandomString generate.

0 commit comments

Comments
 (0)