生成 Go 随机字串
在这篇文章中,我们将介绍如何创建一个函数,它将允许我们在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-z
, A-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 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: