mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
add package guid; improve performance of package grand
This commit is contained in:
@ -8,6 +8,6 @@ import (
|
||||
|
||||
func main() {
|
||||
for i := 0; i < 100; i++ {
|
||||
fmt.Println(grand.Rand(0, 99999))
|
||||
fmt.Println(grand.S(16))
|
||||
}
|
||||
}
|
||||
|
||||
13
.example/util/guid/guid.go
Normal file
13
.example/util/guid/guid.go
Normal file
@ -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))
|
||||
}
|
||||
}
|
||||
23
.example/util/guid/guid_unique.go
Normal file
23
.example/util/guid/guid_unique.go
Normal file
@ -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))
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
|
||||
@ -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++ {
|
||||
|
||||
@ -19,14 +19,21 @@ var (
|
||||
characters = letters + digits + symbols // 94
|
||||
)
|
||||
|
||||
// Meet randomly calculate whether the given probability <num>/<total> 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 <max> can only be greater than 0, or else it returns <max> directly;
|
||||
// 2. The result is greater than or equal to 0, but less than <max>;
|
||||
// 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 <n>.
|
||||
@ -73,12 +80,18 @@ func N(min, max int) int {
|
||||
// The optional parameter <symbols> 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 <n> count of chars from given string <s>.
|
||||
// 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 <n>.
|
||||
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 <n>.
|
||||
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 <n>.
|
||||
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 <max> can only be greater than 0, or else it returns <max> directly;
|
||||
// 2. The result is greater than or equal to 0, but less than <max>;
|
||||
// 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 <num>/<total> 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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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})
|
||||
|
||||
134
util/guid/guid.go
Normal file
134
util/guid/guid.go
Normal file
@ -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 <data> can be count of 1 to 3. No matter how long each of the <data>
|
||||
// size is, each of them will be hashed into 7 bytes as part of the result. If given
|
||||
// <data> count is less than 3, the leftover size of the result bytes will be token by
|
||||
// random string.
|
||||
//
|
||||
// Note that the <data> 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 <n> count of chars from given string <s>.
|
||||
// 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)
|
||||
}
|
||||
38
util/guid/guid_z_bench_test.go
Normal file
38
util/guid/guid_z_bench_test.go
Normal file
@ -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"))
|
||||
}
|
||||
}
|
||||
34
util/guid/guid_z_unit_test.go
Normal file
34
util/guid/guid_z_unit_test.go
Normal file
@ -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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user