美文网首页
Go语言 随机数

Go语言 随机数

作者: 小杰的快乐时光 | 来源:发表于2018-08-18 22:55 被阅读0次

    Go语言标准库中关于随机函数提供了两种包,分别是“math/rand”和“crypto/rand”。

    math/rand:实现伪随机数生成器。
    crypto/rand:实现用于加解密的更安全的随机数生成器。

    关于伪随机数
    我们使用以下代码随机输出5个随机数

    import (
       "fmt"
       "math/rand"
    )
    func main() {
       for i := 0; i < 5; i++ {
          fmt.Println(rand.Int())
       }
    }
    -----output-----
    5577006791947779410
    8674665223082153551
    6129484611666145821
    4037200794235010051
    3916589616287113937
    

    从结果上来看没什么问题,但是如果多运行几次,就会发现每次输出的结果是相同的,这也就是说,虽然单次输出的结果是随机数,但是多次输出的结果却是相同的,这样很容易被找出规律,因此是伪随机数。
    那么出现上述情况的原因是什么呢?让我们来看看math/rand的源码
    rand.go源码中初始化过程

    func NewSource(seed int64) Source {
       var rng rngSource
       rng.Seed(seed) //使用随机种子seed初始化
       return &rng
    }
    

    rng.Seed(seed)在 rng.go 中源码如下:

    // Seed uses the provided seed value to initialize the generator to a deterministic state.
    func (rng *rngSource) Seed(seed int64) { //int64远大于int32,所以传入的seed很容易造成rngSource在初始化时,出现重复的.
       rng.tap = 0
       rng.feed = _LEN - _TAP
    
       seed = seed % _M  // 随机种子等于 对_M = (1 << 31) - 1求模,这里会产生大量同余整数
       if seed < 0 {
          seed += _M
       }
       if seed == 0 {
          seed = 89482311
       }
    
       x := int32(seed)
       for i := -20; i < _LEN; i++ {
          x = seedrand(x)
          if i >= 0 {
             var u int64
             u = int64(x) << 40
             x = seedrand(x)
             u ^= int64(x) << 20
             x = seedrand(x)
             u ^= int64(x)
             u ^= rng_cooked[i]
             rng.vec[i] = u
          }
       }
    }
    

    每次Int/intn/Uint32/Int31,其实都是调用Int63。该方法从池中获取内部两个索引指向的缓存数值相加(同时会更新其中一条,下次使用)。

    // Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
    func (rng *rngSource) Int63() int64 {
       return int64(rng.Uint64() & _MASK)
    }
    
    // Uint64 returns a non-negative pseudo-random 64-bit integer as an uint64.
    func (rng *rngSource) Uint64() uint64 {
       rng.tap--
       if rng.tap < 0 {
          rng.tap += _LEN
       }
    
       rng.feed--
       if rng.feed < 0 {
          rng.feed += _LEN
       }
    
       x := rng.vec[rng.feed] + rng.vec[rng.tap]
       rng.vec[rng.feed] = x
       return uint64(x)
    }
    

    因此相同的随机种子,在多次产生随机数下都会是相同的,因此称为伪随机数
    因此,我们使用设置一个随机种子

    import (
       "fmt"
       "math/rand"
    )
    func main() {
       rand.New(rand.NewSource(10))
       for i := 0; i < 5; i++ {
          fmt.Println(rand.Int())
       }
    }
    ----output----
    5577006791947779410
    8674665223082153551
    6129484611666145821
    4037200794235010051
    3916589616287113937
    

    然后经过多次输出后会发现,每次结果还是相同的,难道代码写错了?我们来看看 rand.Int() 的源码:rand.go 中 rand.Int() 返回 globalRand.Int()。

    func Int() int { return globalRand.Int() }
    

    我们再看看 globalRand 的源码,原来rand.Int()下的随机数都是以1为种子。

    var globalRand = New(&lockedSource{src: NewSource(1).(Source64)})
    

    因此我们来修改一下代码

    import (
       "fmt"
       "math/rand"
    )
    func main() {
       rand1 := rand.New(rand.NewSource(10))
       for i := 0; i < 5; i++ {
          fmt.Println(rand1.Int())
       }
    }
    -----output-----
    5221277731205826435
    3852159813000522384
    8532807521486154107
    3888302351045490779
    4512466281294657143
    

    这下终于跟之前的不一样了,但是多次运行后发现,第一次结果变了,但之后的结果还是一样的,那该怎么解决呢?

    这时候我们可以考虑使用时间作为随机种子,这样每次结果就绝对不一样了

    import (
       "fmt"
       "math/rand"
       "time"
    )
    func main() {
       rand1 := rand.New(rand.NewSource(time.Now().UnixNano()))
       for i := 0; i < 5; i++ {
          fmt.Println(rand1.Int())
       }
    }
    

    上面我们是使用 math/rand 包做随机数,如果我们改为 crypto/rand 包会不会有其他的变化呢?
    我们来看看 crypto 包下的rand源码

    //包rand实现了加密安全
    //伪随机数生成器。
    package rand
    
    import "io"
    
    // Reader是一个加密的全局共享实例
    //强伪随机生成器。
    //在Linux上,Reader使用getrandom(2)(如果可用),否则使用/ dev / urandom。
    //在OpenBSD上,Reader使用getentropy(2)。
    //在其他类Unix系统上,Reader从/ dev / urandom读取。
    //在Windows系统上,Reader使用CryptGenRandom API。
    var Reader io.Reader
    

    crypto/rand 包中的随机数是利用当前系统的一些特征,比如内存的使用,文件的使用量,不同类型的进程数量等等来进行计算随机数,因此可能重复的几率很低。

    下面是使用 crypto/rand 包 产生随机数的代码实例

    import (
       "fmt"
       "crypto/rand"
    )
    func main() {
       b := make([]byte, 10)
       n, err := rand.Read(b) //在byte切片中随机写入元素
       fmt.Println(n, err, b)
    }
    ---output---
    10 <nil> [114 240 66 169 137 70 27 41 148 202]
    

    注意:crypto/rand 包 产生随机数的速度要比 math/rand 产生随机数的速度慢很多

    参考文章:

    https://blog.csdn.net/libra412/article/details/41925051

    https://blog.csdn.net/qq_26981997/article/details/56837947

    相关文章

      网友评论

          本文标题:Go语言 随机数

          本文链接:https://www.haomeiwen.com/subject/dpsqiftx.html