golang支持两种随机数生成方式:

math/rand          // 伪随机
crypto/rand        // 真随机

math/rand

伪随机生成的数字是确定的,不论在什么机器、什么时间,只要执行的随机代码一样,那么生成的随机数就一样,例如:

func main() {
   rand.Seed(2)
   for i := 0; i < 4; i++  {
      println(rand.Intn(100))
   }
}

// output
86
86
92
40

golang使用一个seed作为source来生成伪随机数字,默认seed为1,只有seed固定了,那么所有随机数就固定了:

func(seed, 100) => xx,yy,zz

这里有一个坑:如果seed固定,那么每次程序重启后重新生成随机数会重复上一次的随机数

为了尽量随机性,那么我们可以每次使用不同的seed来启动程序,就可以保证每次启动都产生新的随机数,聪明的你肯定想到了使用时间戳

func main() {
   rand.Seed(time.Now().UnixNano())
   for i := 0; i < 3; i++  {
      println(rand.Intn(100))
   }
}

使用ns时间戳可以保证每次重启seed都不一样,然后可以生成新的随机序列

crypto/rand

crypto/rand是为了提供更好的随机性满足密码对随机数的要求,在linux上已经有一个实现就是/dev/urandom,crypto/rand 就是从这个地方读“真随机”数字返回,但性能比较慢

func main() {
   for i := 0; i < 4; i++  {
      n, _ := rand.Int(rand.Reader, big.NewInt(100))
      println(n.Int64())
   }
}

性能区别

name    time/op
RandWithCrypto-8  272ns ± 3%
name    time/op
RandWithMath-8   22.8ns ± 4%

// 差10倍

基于随机数生成随机字符串

以上随机只能生成随机数,无法生成随机字符串,但我们实际开发中还是是字符串和数字混合为主,所以列出基于随机数字来生成随机字符串的方法:

var defaultLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")

// RandomString returns a random string with a fixed length
func RandomString(n int, allowedChars ...[]rune) string {
	var letters []rune

	if len(allowedChars) == 0 {
		letters = defaultLetters
	} else {
		letters = allowedChars[0]
	}

	b := make([]rune, n)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))]
	}

	return string(b)
}

总结

  • 对于不涉及到密码类的开发工作直接使用math/rand+基于时间戳的种子rand.Seed(time.Now().UnixNano())一般都能满足需求
  • 对于涉及密码类的开发工作一定要用crypto/rand
  • 如果想生成随机字符串,可以先列出字符串,然后基于随机数选字符的方式实现

参考