生成 Go 随机字串

Golang

在这篇文章中,我们将介绍如何创建一个函数,它将允许我们在Go代码中生成任意长度的随机字符串。为此,我们将编写一个简短的rand包来包装math/rand包,并提供以下两个功能:

  • StringWithCharset()-此函数将接受一个字符集和一个长度,并使用该字符集生成一个随机字符串。
  • String() - String()-此函数只接受一个长度,并将使用默认字符集生成随机字符串。

这意味着我们将创建一个名为rand的自定义包,它将利用math/rand 包提供的功能来创建我们自己的函数,并屏蔽大部分实现细节。这样,我们剩下的代码就不需要关心生成随机字符串的实现细节,而是可以简单地调用类似rand.String(10)的函数来获取包含10个字符的随机字符串。

用相同的包名包装另一个包可能看起来很奇怪,但根据我的经验(以及以其他人的经验 )如果您想根据您正在构建的上下文包装一个包,这种模式可以很好地工作,或者如果您想隔离一些与应用程序的其余部分无关的细节。

如果您碰巧发现自己仍在应用程序的其他部分使用math/rand包,那么可以考虑将其中的一些代码移到新rand包中的函数中,以便更容易重用和单独测试。

附带说明:我以前写过关于生成随机字符串的文章,作为Go中使用字符串的技巧列表的一部分,但是我觉得这个主题应该有自己的文章,因为它本身是一个非常常见的请求

创建rand

我们要做的第一件事是创建一个目录来存储我们的新包。这将根据您的本地环境进行更改,但我建议在您工作的任何目录中创建一个名为“rand”的文件夹。完成后,在新创建的目录中创建一个名为strings.go的文件。

rand目录将存储我们正在创建的rand包中的所有代码。目前我们只提供与字符串相关的函数,但是随着项目需求的发展,欢迎您添加到包中。

rand/strings.go文件中,我们将存储与随机字符串相关的所有函数。到目前为止,这是我们的整个包,但您可能会发现自己随着时间的推移更新这个包,所以最好从这一点开始。

打开rand/strings.go,如果还没有。在它中,我们将首先编写一个Init()函数来处理math/rand包的种子设定。

package rand

import (
  "math/rand"
  "time"
)

var seededRand *rand.Rand = rand.New(
  rand.NewSource(time.Now().UnixNano()))

对于以前编写过Go代码的人来说,前几行应该很熟悉——我们首先声明包,然后再导入一些说明我们将在代码中使用的包的内容。

之后,我们声明一个名为“seedrand”的全局变量,类型为*rand.rand。这种类型几乎拥有math/rand包中的所有可用函数,但是我们能够将其隔离,这样其他代码就不会影响我们的种子。这一点很重要,因为我们导入的另一段代码也可能植入math/rand包,并导致我们所有的random函数都不是真正的随机函数。

例如,如果我们使用rand.Seed(time.Now().UnixNano())作为种子,然后另一个初始值设定项调用 rand.Seed(1),那么我们的种子将被重写,这绝对不是我们想要的。通过使用一个rand.Rand实例,我们能够防止这种情况发生在我们的随机数生成器上。

我们使用 rand.New()函数初始化此变量,该函数需要一个 rand.Source作为参数。源基本上只是一个对象,它帮助我们使用我们提供的种子来获得随机分布的数字。

如果您选择不在代码中创建rand.Rand对象,而是使用math/rand 包提供的方法,请注意默认种子值为“1”,因此如果您忘记对其进行种子设置,则会发现您的“random”包在每次运行应用程序时都会生成相同的数字序列。这也意味着,如果另一个包总是在math/rand 包中添加另一个数字(如“42”),那么每次重新启动应用程序时,您也会得到类似的可预测结果。

若要查看此操作,请尝试运行以下简单程序,而不设定math/rand 包的种子。

package main

import (
  "fmt"
  "math/rand"
)

func main() {
  fmt.Println("1:", rand.Int())
  fmt.Println("2:", rand.Int())
  fmt.Println("3:", rand.Int())
}

你的输出和下面的一样吗?

1: 5577006791947779410 
2: 8674665223082153551 
3: 6129484611666145821 

虽然它可能并不总是这样的感觉,但计算机所做的一切都不是随机的,因此创建一个真正的随机数生成器是一个具有挑战性的问题。相反,我们可以做的是种子随机数生成器的值,将改变我们每次运行我们的程序。这将给我们一个生成伪随机数的机会,而且由于每次程序运行时种子都会发生变化,所以我们不必担心它变得可预测。

如果您正在开发更敏感的代码,比如加密包,这可能不是最适合您的,但是我将假设,如果您正在构建一个您知道足够多的加密包来进行此调用

回到我们的代码,接下来我们要做的是创建StringWithCharset()函数。正如我们之前所说的,这需要一个整数来决定我们想要生成的随机字符串的长度,以及一个我们想要使用的字符集。

对于我们的字符集,我们只需要使用一个字符串变量。虽然您可以在代码中使用类似字节片的东西,但这非常有效,我发现在代码中创建字符串字符集更简单。写 charset := "abcABC123"很简单。

总之,我们可以用下面的代码编写函数。我们将很快讨论实现细节,但现在来看一下代码。

func StringWithCharset(length int, charset string) string {
  b := make([]byte, length)
  for i := range b {
    b[i] = charset[seededRand.Intn(len(charset))]
  }
  return string(b)
}

在第一行中,我们声明了一个大小为length的字节片;在接下来的几行中,我们使用它来构建字符串,方法是遍历字节片中的每个索引,并将字符集中的一个随机字节插入到字节片中。

我们使用 seededRand.Intn()方法来选择随机字节。此方法返回一个介于“0”和“n-1”之间的随机数,其中“n”是方法调用的输入,因此通过传入字符集的长度作为输入,它将返回一个随机数,该随机数表示应在随机字符串中使用的字节索引。

最后,将字节切片转为字符串返回。

接下来要做的是添加 String() 函数,此函数的作用与 StringWithCharset() 基本相同,只不过它将具有默认的字符集。与其重写所有的逻辑,我们真正需要做的是设置字符集,然后调用已有的StringWithCharset() 函数。

在用字符集之前,我们将用 a-zA-Z, 和 0-9 创建一个字符集常量。

const charset = "abcdefghijklmnopqrstuvwxyz" +
  "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

然后,创建 Strings()函数,且在其内部调用 StringWithCharset() 

func String(length int) string {
  return StringWithCharset(length, charset)
}

做完这些,以备不时之需,你最终的代码看起来应该像这样。

package rand

import (
  "math/rand"
  "time"
)

const charset = "abcdefghijklmnopqrstuvwxyz" +
  "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

var seededRand *rand.Rand = rand.New(
  rand.NewSource(time.Now().UnixNano()))

func StringWithCharset(length int, charset string) string {
  b := make([]byte, length)
  for i := range b {
    b[i] = charset[seededRand.Intn(len(charset))]
  }
  return string(b)
}

func String(length int) string {
  return StringWithCharset(length, charset)
}
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://www.calhoun.io/creating-random-s...

译文地址:https://learnku.com/go/t/36699

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!