Go语言 随机数

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容