Go 随机数
最后修改时间 2024 年 4 月 11 日
在本文中,我们将展示如何在 Golang 中生成随机值。
随机数生成器
随机数生成器 (RNG) 生成一组在其外观上不显示任何可区分模式的值。随机数生成器分为两类:硬件随机数生成器和伪随机数生成器。硬件随机数生成器被认为可以产生真正的随机数。伪随机数生成器基于软件算法生成值。它们生成看起来像随机的值。但这些值是确定的,如果算法已知,则可以重现。
在计算中,随机生成器用于赌博、游戏、模拟或密码学。
为了提高伪随机数生成器的质量,操作系统会使用从设备驱动程序收集的环境噪声、用户输入延迟或一个或多个硬件组件的抖动。这是密码学安全伪随机数生成器的核心。
Go 包含实现伪随机数生成器的 math/random 包,以及实现密码学安全随机数生成器的 crypto/rand 包。
种子
种子是初始化随机数生成器的值。随机数生成器通过对前一个值执行某些操作来生成值。当算法启动时,种子是生成器操作的初始值。生成器中最重要也是最难的部分是提供一个接近真正随机数的种子。
在 Go 中,种子值是通过 rand.Seed 函数提供的。如果未调用 Seed,则生成器行为就像使用 Seed(1) 进行播种一样。
Go 随机数相同种子
在下面的示例中,我们使用相同的种子。
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
Go rand.Intn
rand.Intn 函数返回一个非负的伪随机整数,范围在 [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() 为随机生成器设置种子。
Go 随机字符串
下面的示例生成随机字符串。
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
我们运行了三次该示例。
Go 随机整数数组
下面的示例创建一个随机整数值数组。
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]
Go 随机元素
下面的示例选择一个随机元素。
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))
}
我们有一个 rune 切片。从这个切片中,我们随机选择一个值。
$ go run random_element.go č $ go run random_element.go á
我们运行了两次该示例,得到了这些字符。
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
我们运行了三次该示例。
Go 加密安全随机值
Go 在标准库包 crypto/rand 中提供了加密安全的伪随机数生成器。虽然 math/random 速度快得多,但 crypto/rand 适用于安全性至关重要的程序。例如,在生成强密码、CSRF 令牌或会话密钥时。
在 Linux 和 FreeBSD 上,crypto/rand 如果可用则使用 getrandom,否则使用 /dev/urandom。在 OpenBSD 上,它使用 getentropy。Unix-like 系统,它从 /dev/urandom 读取。在 Windows 系统上,它使用 CryptGenRandom 函数。在 Wasm 上,它使用 Web Crypto API。
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 中的随机值。