diff --git a/random/random.go b/random/random.go index 37f6289..6e0c8cf 100644 --- a/random/random.go +++ b/random/random.go @@ -8,20 +8,25 @@ import ( crand "crypto/rand" "fmt" "io" + "math" "math/rand" "time" + "unsafe" "github.com/duke-git/lancet/v2/mathutil" ) const ( - Numeral = "0123456789" - LowwerLetters = "abcdefghijklmnopqrstuvwxyz" - UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - SymbolChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?" + MaximumCapacity = 1 << 31 + Numeral = "0123456789" + LowwerLetters = "abcdefghijklmnopqrstuvwxyz" + UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + SymbolChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?" ) +var rn = rand.NewSource(time.Now().UnixNano()) + func init() { rand.Seed(time.Now().UnixNano()) } @@ -108,18 +113,61 @@ func RandSymbolChar(length int) string { return random(SymbolChars, length) } +// nearestPowerOfTwo 返回一个大于等于cap的最近的2的整数次幂,参考java8的hashmap的tableSizeFor函数 +func nearestPowerOfTwo(cap int) int { + n := cap - 1 + n |= n >> 1 + n |= n >> 2 + n |= n >> 4 + n |= n >> 8 + n |= n >> 16 + if n < 0 { + return 1 + } else if n >= MaximumCapacity { + return MaximumCapacity + } + return n + 1 +} + // random generate a random string based on given string range. func random(s string, length int) string { - b := make([]byte, length) - - // fix: https://github.com/duke-git/lancet/issues/75 - // r := rand.New(rand.NewSource(time.Now().UnixNano())) - - for i := range b { - b[i] = s[rand.Int63()%int64(len(s))] + // 仿照strings.Builder + // 创建一个长度为 length 的字节切片 + bytes := make([]byte, length) + strLength := len(s) + if strLength <= 0 { + return "" + } else if strLength == 1 { + for i := 0; i < length; i++ { + bytes[i] = s[0] + } + return *(*string)(unsafe.Pointer(&bytes)) } - - return string(b) + // s的字符需要使用多少个比特位数才能表示完 + // letterIdBits := int(math.Ceil(math.Log2(strLength))),下面比上面的代码快 + letterIdBits := int(math.Log2(float64(nearestPowerOfTwo(strLength)))) + // 最大的字母id掩码 + var letterIdMask int64 = 1<= 0; { + // 检查随机数生成器是否用尽所有随机数 + if remain == 0 { + cache, remain = rn.Int63(), letterIdMax + } + // 从可用字符的字符串中随机选择一个字符 + if idx := int(cache & letterIdMask); idx < len(s) { + bytes[i] = s[idx] + i-- + } + // 右移比特位数,为下次选择字符做准备 + cache >>= letterIdBits + remain-- + } + // 仿照strings.Builder用unsafe包返回一个字符串,避免拷贝 + // 将字节切片转换为字符串并返回 + return *(*string)(unsafe.Pointer(&bytes)) } // UUIdV4 generate a random UUID of version 4 according to RFC 4122.