ZetCode

Go 随机数

最后修改时间 2024 年 4 月 11 日

在本文中,我们将展示如何在 Golang 中生成随机值。

随机数生成器

随机数生成器 (RNG) 生成一组在其外观上不显示任何可区分模式的值。随机数生成器分为两类:硬件随机数生成器和伪随机数生成器。硬件随机数生成器被认为可以产生真正的随机数。伪随机数生成器基于软件算法生成值。它们生成看起来像随机的值。但这些值是确定的,如果算法已知,则可以重现。

在计算中,随机生成器用于赌博、游戏、模拟或密码学。

注意: 出于安全目的,必须使用密码学安全的伪随机数生成器。

为了提高伪随机数生成器的质量,操作系统会使用从设备驱动程序收集的环境噪声、用户输入延迟或一个或多个硬件组件的抖动。这是密码学安全伪随机数生成器的核心。

Go 包含实现伪随机数生成器的 math/random 包,以及实现密码学安全随机数生成器的 crypto/rand 包。

种子

种子是初始化随机数生成器的值。随机数生成器通过对前一个值执行某些操作来生成值。当算法启动时,种子是生成器操作的初始值。生成器中最重要也是最难的部分是提供一个接近真正随机数的种子。

在 Go 中,种子值是通过 rand.Seed 函数提供的。如果未调用 Seed,则生成器行为就像使用 Seed(1) 进行播种一样。

注意: 相同的种子会产生相同的伪随机数集。

Go 随机数相同种子

在下面的示例中,我们使用相同的种子。

same_seed.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) 之间,该整数来自默认源。

five_random.go
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 随机字符串

下面的示例生成随机字符串。

random_string.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 随机整数数组

下面的示例创建一个随机整数值数组。

random_array.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 随机元素

下面的示例选择一个随机元素。

random_element.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 从池中生成随机字符串

下面的示例从字符池中随机选择字母。

rand_pool.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。

crypto_rand.go
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]

来源

Go math/rand 包 - 参考

在本文中,我们研究了 Golang 中的随机值。

作者

我叫 Jan Bodnar,我是一名热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。至今,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程