Gorandom教程展示了如何在Golang中生成随机值。
$ go version go version go1.18.1 linux/amd64
我们使用Go版本1.18。
随机数生成器
随机数生成器(RNG)生成一组值,这些值在外观上不显示任何可区分的模式。随机数发生器分为两类:硬件随机数发生器和伪随机数发生器。硬件随机数生成器被认为可以产生真正的随机数。伪随机数生成器根据软件算法生成值。它们产生看起来随机的值。但是这些值是确定性的并且可以重现,如果算法是已知的。
在计算中,随机生成器用于赌博、游戏、模拟或密码学。
为了提高伪随机数生成器的质量,操作系统使用从设备驱动程序、用户输入延迟或一个或多个硬件组件的抖动收集的环境噪声。这是加密安全伪随机数生成器的核心。
Go包含实现伪随机数生成器的math/random
包,以及实现加密安全随机数生成器的crypto/rand
包。
种子
种子是初始化随机数生成器的值。随机数生成器通过对先前值执行某些操作来生成值。当算法开始时,种子是生成器运行的初始值。生成器最重要和最困难的部分是提供接近真正随机数的种子。
在Go中,种子值由rand.Seed
函数提供。如果未调用Seed
,则生成器的行为就像通过Seed(1)
播种一样。
随机选择相同的种子
在下面的例子中,我们使用相同的种子。
package main import ( "fmt" "math/rand" ) func main() { rand.Seed(20) fmt.Printf("%d ", rand.Intn(100)) fmt.Printf("%d ", rand.Intn(100)) fmt.Printf("%d \n", rand.Intn(100)) rand.Seed(20) fmt.Printf("%d ", rand.Intn(100)) fmt.Printf("%d ", rand.Intn(100)) fmt.Printf("%d \n", rand.Intn(100)) fmt.Println() }
相同的种子值产生相同的伪随机值。
$ go run same_seed.go 30 48 40 30 48 40
去rand.Intn
rand.Intn
函数以int形式从默认源返回[0,n)中的非负伪随机数。
package main import ( "fmt" "math/rand" "time" ) func init() { rand.Seed(time.Now().UnixNano()) } func main() { for i := 0; i < 5; i++ { fmt.Printf("%d ", rand.Intn(20)) } fmt.Println() }
该示例打印五个随机整数。
rand.Seed(time.Now().UnixNano())
为了获得不同的伪随机值,我们使用time.Now().UnixNano()
为随机生成器播种。
随机字符串
以下示例生成随机字符串。
package main import ( "fmt" "math/rand" "time" ) func main() { rand.Seed(time.Now().UTC().UnixNano()) fmt.Println(randomString(12)) } func randomString(len int) string { bytes := make([]byte, len) for i := 0; i < len; i++ { bytes[i] = byte(randInt(97, 122)) } return string(bytes) } func randInt(min int, max int) int { return min + rand.Intn(max-min) }
该示例创建了一个包含十二个字符的随机字符串。
$ go run random_string.go gqvqyybfuhxl $ go run random_string.go rrwmqaqkrslu $ go run random_string.go axhhrkwyhnxm
我们运行示例3次。
随机整数数组
以下示例创建一个随机整数值数组。
package main import ( "fmt" "math/rand" "time" ) func randArray(len int) []int { a := make([]int, len) for i := 0; i <= len-1; i++ { a[i] = rand.Intn(len) } return a } func main() { rand.Seed(time.Now().UnixNano()) len := 12 fmt.Println(randArray(len)) }
该示例创建了一个包含十二个整数值的数组。
$ go run rand_array.go [5 6 3 5 3 4 7 3 5 6 5 0] $ go run rand_array.go [2 5 10 9 1 5 1 4 11 7 6 3]
去随机元素
以下示例选择一个随机元素。
package main import ( "fmt" "math/rand" "time" ) func init() { rand.Seed(time.Now().UnixNano()) } func main() { runes := []rune("Äervená Äiara") myrune := runes[rand.Intn(len(runes))] fmt.Println(string(myrune)) }
我们有一片符文。从这个切片中,我们随机选择一个值。
$ go run random_element.go Ä $ go run random_element.go á
我们运行了两次示例并获得了这些字符。
从池中随机生成字符串
以下示例从字符池中随机选取字母。
package main import ( "fmt" "math/rand" "time" ) var pool = "abcdefghijklmnopqrstuvwxyzABCEFGHIJKLMNOPQRSTUVWXYZ:|?$%@][{}#&/()*" func randomString(l int) string { bytes := make([]byte, l) for i := 0; i < l; i++ { bytes[i] = pool[rand.Intn(len(pool))] } return string(bytes) } func main() { rand.Seed(time.Now().UnixNano()) fmt.Println(randomString(12)) }
该示例从各种字符的预定义池中打印随机字符串。
var pool = "abcdefghijklmnopqrstuvwxyzABCEFGHIJKLMNOPQRSTUVWXYZ:|?$%@][{}#&/()*"
这是一组预定义的字符。
for i := 0; i < l; i++ { bytes[i] = pool[rand.Intn(len(pool))] }
我们通过生成字符串的随机索引来选择一个随机字母。
$ go run rand_pool.go FFFZW(sHvE:a $ go run rand_pool.go (my%Tmf&qOVs $ go run rand_pool.go /{GqgkhRVOfi
我们运行示例3次。
Go加密安全随机值
Go在标准库包crypto/rand
中提供了加密安全的伪随机数生成器。虽然math/random
快得多,但crypto/rand
适用于安全性至关重要的程序。例如,在生成强密码、CSRF令牌或会话密钥时。
在Linux和FreeBSD上,crypto/rand
如果可用则使用getrandom
,否则使用/dev/urandom
。在OpenBSD上,它使用getentropy
。类Unix系统,它从/dev/urandom
读取。在Windows系统上,它使用CryptGenRandom
函数。在Wasm上,它使用WebCryptoAPI。
package main import ( "crypto/rand" "fmt" "log" ) func main() { data, err := generateRandomBytes(16) if err != nil { log.Fatal(err) } fmt.Println(data) } func generateRandomBytes(n int) ([]byte, error) { b := make([]byte, n) _, err := rand.Read(b) if err != nil { return nil, err } return b, nil }
在代码示例中,我们创建了16个安全生成的随机字节。
b := make([]byte, n) _, err := rand.Read(b)
我们读取n
个加密安全伪随机数并将它们写入字节片。
$ go run crypto_rand.go [151 0 67 88 199 60 220 50 34 198 169 158 18 162 85 61]
在本教程中,我们使用了Golang中的随机值。
列出所有Go教程。