diff --git a/.example/util/grand/grand.go b/.example/util/grand/grand.go index 9b2ffc736..579b9053f 100644 --- a/.example/util/grand/grand.go +++ b/.example/util/grand/grand.go @@ -8,6 +8,6 @@ import ( func main() { for i := 0; i < 100; i++ { - fmt.Println(grand.Rand(0, 99999)) + fmt.Println(grand.S(16)) } } diff --git a/.example/util/guid/guid.go b/.example/util/guid/guid.go new file mode 100644 index 000000000..39bbdc6b3 --- /dev/null +++ b/.example/util/guid/guid.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/util/guid" +) + +func main() { + for i := 0; i < 1000; i++ { + s := guid.S() + fmt.Println(s, len(s)) + } +} diff --git a/.example/util/guid/guid_unique.go b/.example/util/guid/guid_unique.go new file mode 100644 index 000000000..fc26b90be --- /dev/null +++ b/.example/util/guid/guid_unique.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/util/guid" +) + +func main() { + for i := 0; i < 10; i++ { + s := guid.New([]byte("123")) + fmt.Println(s, len(s)) + } + fmt.Println() + for i := 0; i < 10; i++ { + s := guid.New([]byte("123"), []byte("456")) + fmt.Println(s, len(s)) + } + fmt.Println() + for i := 0; i < 10; i++ { + s := guid.New([]byte("123"), []byte("456"), []byte("789")) + fmt.Println(s, len(s)) + } +} diff --git a/database/gredis/gredis_unit_test.go b/database/gredis/gredis_unit_test.go index 89665e9a8..4476f0736 100644 --- a/database/gredis/gredis_unit_test.go +++ b/database/gredis/gredis_unit_test.go @@ -8,7 +8,7 @@ package gredis_test import ( "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/util/guuid" + "github.com/gogf/gf/util/guid" "testing" "time" @@ -234,7 +234,7 @@ func Test_Bool(t *testing.T) { func Test_Int(t *testing.T) { gtest.C(t, func(t *gtest.T) { redis := gredis.New(config) - key := guuid.New() + key := guid.S() defer redis.Do("DEL", key) _, err := redis.Do("SET", key, 1) @@ -249,7 +249,7 @@ func Test_Int(t *testing.T) { func Test_HSet(t *testing.T) { gtest.C(t, func(t *gtest.T) { redis := gredis.New(config) - key := guuid.New() + key := guid.S() defer redis.Do("DEL", key) _, err := redis.Do("HSET", key, "name", "john") @@ -265,7 +265,7 @@ func Test_HGetAll(t *testing.T) { gtest.C(t, func(t *gtest.T) { var err error redis := gredis.New(config) - key := guuid.New() + key := guid.S() defer redis.Do("DEL", key) _, err = redis.Do("HSET", key, "id", "100") diff --git a/encoding/ghash/ghash.go b/encoding/ghash/ghash.go index 4ee86329f..ede93117f 100644 --- a/encoding/ghash/ghash.go +++ b/encoding/ghash/ghash.go @@ -4,7 +4,7 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package ghash provides some popular hash functions(uint32/uint64) in go. +// Package ghash provides some classic hash functions(uint32/uint64) in go. package ghash // BKDR Hash Function @@ -160,7 +160,7 @@ func DJBHash(str []byte) uint32 { return hash } -// DJB Hash Function 64 +// DJB Hash Function 64. func DJBHash64(str []byte) uint64 { var hash uint64 = 5381 for i := 0; i < len(str); i++ { diff --git a/util/grand/grand.go b/util/grand/grand.go index 44d2e7427..c39dea46f 100644 --- a/util/grand/grand.go +++ b/util/grand/grand.go @@ -19,14 +19,21 @@ var ( characters = letters + digits + symbols // 94 ) -// Meet randomly calculate whether the given probability / is met. -func Meet(num, total int) bool { - return Intn(total) < num -} - -// MeetProb randomly calculate whether the given probability is met. -func MeetProb(prob float32) bool { - return Intn(1e7) < int(prob*1e7) +// Intn returns a int number which is between 0 and max: [0, max). +// +// Note that: +// 1. The can only be greater than 0, or else it returns directly; +// 2. The result is greater than or equal to 0, but less than ; +// 3. The result number is 32bit and less than math.MaxUint32. +func Intn(max int) int { + if max <= 0 { + return max + } + n := int(binary.LittleEndian.Uint32(<-bufferChan)) % max + if (max > 0 && n < 0) || (max < 0 && n > 0) { + return -n + } + return n } // B retrieves and returns random bytes of given length . @@ -73,12 +80,18 @@ func N(min, max int) int { // The optional parameter specifies whether the result could contain symbols, // which is false in default. func S(n int, symbols ...bool) string { - b := make([]byte, n) + if n <= 0 { + return "" + } + var ( + b = make([]byte, n) + numberBytes = B(n) + ) for i := range b { if len(symbols) > 0 && symbols[0] { - b[i] = characters[Intn(94)] + b[i] = characters[numberBytes[i]%94] } else { - b[i] = characters[Intn(62)] + b[i] = characters[numberBytes[i]%62] } } return *(*string)(unsafe.Pointer(&b)) @@ -87,37 +100,67 @@ func S(n int, symbols ...bool) string { // Str randomly picks and returns count of chars from given string . // It also supports unicode string like Chinese/Russian/Japanese, etc. func Str(s string, n int) string { - b := make([]rune, n) - runes := []rune(s) - for i := range b { - b[i] = runes[Intn(len(runes))] + if n <= 0 { + return "" + } + var ( + b = make([]rune, n) + runes = []rune(s) + ) + if len(runes) <= 255 { + numberBytes := B(n) + for i := range b { + b[i] = runes[int(numberBytes[i])%len(runes)] + } + } else { + for i := range b { + b[i] = runes[Intn(len(runes))] + } } return string(b) } // Digits returns a random string which contains only digits, and its length is . func Digits(n int) string { - b := make([]byte, n) + if n <= 0 { + return "" + } + var ( + b = make([]byte, n) + numberBytes = B(n) + ) for i := range b { - b[i] = digits[Intn(10)] + b[i] = digits[numberBytes[i]%10] } return *(*string)(unsafe.Pointer(&b)) } // Letters returns a random string which contains only letters, and its length is . func Letters(n int) string { - b := make([]byte, n) + if n <= 0 { + return "" + } + var ( + b = make([]byte, n) + numberBytes = B(n) + ) for i := range b { - b[i] = letters[Intn(52)] + b[i] = letters[numberBytes[i]%52] } return *(*string)(unsafe.Pointer(&b)) } // Symbols returns a random string which contains only symbols, and its length is . func Symbols(n int) string { - b := make([]byte, n) + if n <= 0 { + return "" + } + var ( + b = make([]byte, n) + numberBytes = B(n) + ) for i := range b { - b[i] = symbols[Intn(52)] + b[i] = symbols[numberBytes[i]%32] } return *(*string)(unsafe.Pointer(&b)) } @@ -134,19 +177,12 @@ func Perm(n int) []int { return m } -// Intn returns a int number which is between 0 and max: [0, max). -// -// Note that: -// 1. The can only be greater than 0, or else it returns directly; -// 2. The result is greater than or equal to 0, but less than ; -// 3. The result number is 32bit and less than math.MaxUint32. -func Intn(max int) int { - if max <= 0 { - return max - } - n := int(binary.LittleEndian.Uint32(<-bufferChan)) % max - if (max > 0 && n < 0) || (max < 0 && n > 0) { - return -n - } - return n +// Meet randomly calculate whether the given probability / is met. +func Meet(num, total int) bool { + return Intn(total) < num +} + +// MeetProb randomly calculate whether the given probability is met. +func MeetProb(prob float32) bool { + return Intn(1e7) < int(prob*1e7) } diff --git a/util/grand/grand_buffer.go b/util/grand/grand_buffer.go index fc871742a..ab6bd391c 100644 --- a/util/grand/grand_buffer.go +++ b/util/grand/grand_buffer.go @@ -29,33 +29,13 @@ func init() { // to produce the random bytes, and a buffer chan to store the random bytes. // So it has high performance to generate random numbers. func asyncProducingRandomBufferBytesLoop() { - var ( - step = 0 - buffer = make([]byte, 1024) - ) + buffer := make([]byte, 1024) for { if n, err := rand.Read(buffer); err != nil { panic(err) } else { - for i := 0; i < n-4; { + for i := 0; i < n-4; i += 4 { bufferChan <- buffer[i : i+4] - i++ - } - // Reuse the rand buffer. - for i := 0; i < n; i++ { - step = int(buffer[0]) % 10 - if step != 0 { - break - } - } - // The step cannot be 0, - // as it will produce the same random number as previous. - if step == 0 { - step = 2 - } - for i := 0; i < n-4; { - bufferChan <- buffer[i : i+4] - i += step } } } diff --git a/util/grand/grand_z_bench_test.go b/util/grand/grand_z_bench_test.go index b659a8dfe..08a09cd52 100644 --- a/util/grand/grand_z_bench_test.go +++ b/util/grand/grand_z_bench_test.go @@ -16,7 +16,10 @@ import ( "github.com/gogf/gf/util/grand" ) -var buffer = make([]byte, 8) +var ( + buffer = make([]byte, 8) + strForStr = "我爱GoFrame" +) func Benchmark_Rand_Intn(b *testing.B) { for i := 0; i < b.N; i++ { @@ -48,18 +51,36 @@ func Benchmark_Rand_N2(b *testing.B) { } } -func Benchmark_Str(b *testing.B) { +func Benchmark_B(b *testing.B) { + for i := 0; i < b.N; i++ { + grand.B(16) + } +} + +func Benchmark_S(b *testing.B) { for i := 0; i < b.N; i++ { grand.S(16) } } -func Benchmark_StrSymbols(b *testing.B) { +func Benchmark_S_Symbols(b *testing.B) { for i := 0; i < b.N; i++ { grand.S(16, true) } } +func Benchmark_Str(b *testing.B) { + for i := 0; i < b.N; i++ { + grand.Str(strForStr, 16) + } +} + +func Benchmark_Symbols(b *testing.B) { + for i := 0; i < b.N; i++ { + grand.Symbols(16) + } +} + func Benchmark_Uint32Converting(b *testing.B) { for i := 0; i < b.N; i++ { binary.LittleEndian.Uint32([]byte{1, 1, 1, 1}) diff --git a/util/guid/guid.go b/util/guid/guid.go new file mode 100644 index 000000000..76db77b56 --- /dev/null +++ b/util/guid/guid.go @@ -0,0 +1,134 @@ +// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package guid provides simple and high performance unique id generation functionality. +// +// PLEASE VERY NOTE: +// This package only provides unique number generation for simple, convenient and most common +// usage purpose, but does not provide strict global unique number generation. Please refer +// to UUID algorithm for global unique number generation if necessary. +package guid + +import ( + "github.com/gogf/gf/container/gtype" + "github.com/gogf/gf/encoding/ghash" + "github.com/gogf/gf/net/gipv4" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/grand" + "os" + "strconv" + "time" +) + +var ( + sequence gtype.Uint32 // Sequence for unique purpose of current process. + macAddrStr string // MAC addresses hash result in 7 bytes. + processIdStr string // Process id in 4 bytes. + randomStrBase = "0123456789abcdefghijklmnopqrstuvwxyz" // 36 +) + +func init() { + // MAC addresses hash result in 7 bytes. + macs, _ := gipv4.MacArray() + if len(macs) > 0 { + var b []byte + for _, mac := range macs { + b = append(b, []byte(mac)...) + } + macAddrStr = strconv.FormatUint(uint64(ghash.DJBHash(b)), 36) + if n := 7 - len(macAddrStr); n > 0 { + for i := 0; i < n; i++ { + macAddrStr = "0" + macAddrStr + } + } + if n := len(macAddrStr) - 7; n > 0 { + macAddrStr = macAddrStr[n:] + } + } + // Process id in 4 bytes. + processIdStr = strconv.FormatInt(int64(os.Getpid()), 36) + if n := 4 - len(processIdStr); n > 0 { + for i := 0; i < n; i++ { + processIdStr = "0" + processIdStr + } + } + if n := len(processIdStr) - 4; n > 0 { + processIdStr = processIdStr[n:] + } +} + +// S creates and returns an unique string in 36 bytes that meets most +// common usages without strict UUID algorithm. +// The returned string is composed with: +// MAC(7) + PID(4) + Sequence(7) + TimestampNano(12) + RandomString(6) +func S() string { + b := make([]byte, 36) + copy(b, macAddrStr) + copy(b[7:], processIdStr) + copy(b[11:], getSequence()) + copy(b[18:], strconv.FormatInt(time.Now().UnixNano(), 36)) + copy(b[30:], randomStr(randomStrBase, 6)) + return gconv.UnsafeBytesToStr(b) +} + +// New creates and returns an unique string in 36 bytes using custom data. +// The returned string is composed with: +// Data...(7 - 21) + TimestampNano(12) + RandomString(3 - 17) +// +// The specified can be count of 1 to 3. No matter how long each of the +// size is, each of them will be hashed into 7 bytes as part of the result. If given +// count is less than 3, the leftover size of the result bytes will be token by +// random string. +// +// Note that the can not be empty. +func New(data ...[]byte) string { + if len(data) == 0 { + panic("data cannot be empty") + } + if len(data) > 3 { + panic("data count too long, no more than 3") + } + b := make([]byte, 36) + n := 0 + for i, v := range data { + copy(b[i*7:], getDataHashStr(v)) + n += 7 + } + copy(b[n:], strconv.FormatInt(time.Now().UnixNano(), 36)) + copy(b[n+12:], randomStr(randomStrBase, 36-n-12)) + return gconv.UnsafeBytesToStr(b) +} + +// getSequence increases and returns the sequence string in 7 bytes. +func getSequence() string { + b := make([]byte, 7) + copy(b, []byte{'0', '0', '0', '0', '0', '0', '0'}) + s := strconv.FormatUint(uint64(sequence.Add(1)), 36) + copy(b[7-len(s):], s) + return gconv.UnsafeBytesToStr(b) +} + +// Str randomly picks and returns count of chars from given string . +// It also supports unicode string like Chinese/Russian/Japanese, etc. +func randomStr(s string, n int) string { + var ( + b = make([]byte, n) + numberBytes = grand.B(n) + ) + for i := range b { + b[i] = s[numberBytes[i]%36] + } + return gconv.UnsafeBytesToStr(b) +} + +// getDataHashStr creates and returns hash bytes in 7 bytes with given data bytes. +func getDataHashStr(data []byte) string { + b := make([]byte, 7) + copy(b, []byte{'0', '0', '0', '0', '0', '0', '0'}) + s := strconv.FormatUint(uint64(ghash.DJBHash(data)), 36) + copy(b[7-len(s):], s) + return gconv.UnsafeBytesToStr(b) +} diff --git a/util/guid/guid_z_bench_test.go b/util/guid/guid_z_bench_test.go new file mode 100644 index 000000000..4a70f5cee --- /dev/null +++ b/util/guid/guid_z_bench_test.go @@ -0,0 +1,38 @@ +// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// go test *.go -bench=".*" + +package guid_test + +import ( + "github.com/gogf/gf/util/guid" + "testing" +) + +func Benchmark_S(b *testing.B) { + for i := 0; i < b.N; i++ { + guid.S() + } +} + +func Benchmark_New1(b *testing.B) { + for i := 0; i < b.N; i++ { + guid.New([]byte("123")) + } +} + +func Benchmark_New2(b *testing.B) { + for i := 0; i < b.N; i++ { + guid.New([]byte("123"), []byte("456")) + } +} + +func Benchmark_New3(b *testing.B) { + for i := 0; i < b.N; i++ { + guid.New([]byte("123"), []byte("456"), []byte("789")) + } +} diff --git a/util/guid/guid_z_unit_test.go b/util/guid/guid_z_unit_test.go new file mode 100644 index 000000000..a9669d8c6 --- /dev/null +++ b/util/guid/guid_z_unit_test.go @@ -0,0 +1,34 @@ +// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// go test *.go -bench=".*" + +package guid_test + +import ( + "github.com/gogf/gf/container/gset" + "github.com/gogf/gf/util/guid" + "testing" + + "github.com/gogf/gf/test/gtest" +) + +func Test_S(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + set := gset.NewStrSet() + for i := 0; i < 1000000; i++ { + s := guid.S() + t.Assert(set.AddIfNotExist(s), true) + t.Assert(len(s), 36) + } + }) +} + +func Test_New(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(len(guid.New([]byte("123"))), 36) + }) +}