Go实现快速生成固定长度的随机字符串
IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《Go实现快速生成固定长度的随机字符串》,聊聊字符串、随机,我们一起来看看吧!
Q:怎样在Go语言中简单并快速地生成固定长度的随机字符串?
A:
问题是“最快和最简单的方式”,接下来我们会一步步迭代,最终实现最快的方式。每次迭代的基准测试代码放在了答案的末尾。
所有解决方案和基准测试代码都可以在 Go Playground 上找到。Playground 上的代码是测试文件,不是可执行文件。你需要把它保存到文件中并命名为XX_test.go
然后运行
go test -bench . -benchmem
话虽如此,但就算你不需要最快生成随机字符串的方法,通读这篇回答相信你也应该会有所收获。
Improvements
1. Genesis (Runes)
提醒一下,下面这个方法是我们用来改进的原始通用解决方案:
func init() { rand.Seed(time.Now().UnixNano()) } var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func RandStringRunes(n int) string { b := make([]rune, n) for i := range b { b[i] = letterRunes[rand.Intn(len(letterRunes))] } return string(b) }
2. Bytes
如果要生成的随机字符串只包含大小写英文字母,那么我们可以只使用英文字母字节,因为英文字母和UTF8编码的字节是一一对应的(这就是Go存储字符串的方式)
所以可以这么写:
// rune 替换为 byte var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
或者更好的写法是:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
在这里把它写成一个常量就已经是一个很大的改进了(有字符串常量但没有切片常量), 还有一点就是表达式len(letters)
也将是一个常量(如果s是字符串常量,那么len(s)
也就是个常量)
所以我们的第二种方法是这样的:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func RandStringBytes(n int) string { b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] } return string(b) }
3. Remainder
前面的方法都是通过调用rand.Intn()
来生成一个随机数从而选择一个随机的字母。rand.Intn()
来自于Rand.Intn()
, 而后者又来自于Rand.Int31n()
。
与生成一个具有 63 个随机位的随机数的rand.Int63()
相比上面生成随机数的方式要慢得多。
所以我们可以直接调用rand.Int63()
然后对lenletterBytes)
进行取余。
func RandStringBytesRmndr(n int) string { b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))] } return string(b) }
这么做是可以的并且要比上面的方法快很多,但是有个缺点就是所有的字母出现的概率不是完全相等的(假设 rand.Int63()
以相等的概率产生所有 63 位数字)。由于字母的长度52比 1要小得多,因此各个字母出现的概率的差异非常小,在实际使用中是完全没问题的。
解释下上面字母出现概率不相等的现象:假设你要生成一个0..5之间的随机数,如果使用3个随机位,那么会导致产生数字0..1范围内的概率是2..5的两倍;如果使用5个随机位,那么产生0..1范围的数字概率是6/32, 2..5范围的概率位5/32,这已经很接近了。增加位数可以使概率差异越来越小,当达到63位时,差异已经可以忽略不计了。
4. Masking
在前面的解决方案的基础上,我们可以通过只使用尽可能多的随机数的最低位来表示字母的数量从而保持字母的均匀分布。所以我们有52个字母,那么就需要6位来表示它:52=110100b
。因此我们就只使用rand.Int63()
返回的数的最低六位来表示。为了保持字母的均匀粉笔,我们只接受落在0...len(letterBytes)-1
范围内的数字。如果最低位的数字大于这个范围,那么就丢弃它并重新生成一个新的随机数。
注意,最低位大于等于len(letterBytes)
的概率通常小于0.5(平均为0.25),这意味着重复出现这种情况会降低我们找到能用的随机数字的概率。在 n 次重复后我们仍然没有找到一个能用的随机数的概率远小于pow(0.5, n)
,当然这是一个最坏的情况。在52个字母的情况下,最低6位不能用的可能性为 (64 - 52) / 64 = 0.19,也就是说在10次重复还没有遇到可以用的数字的概率为 1e-8。
所以,这个解决方法是这样的:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( letterIdxBits = 6 // 6 bits to represent a letter index letterIdxMask = 1 <h3>5. Masking Improved</h3> <p>前面的解决方案只使用 rand.Int63() 返回的 63 个随机位中的最低 6 位。这是一种浪费,因为获取随机位是我们算法中最慢的部分。</p> <p>因为我们有52个字母,可以用6位来编码一个字母索引。所以63个随机位可以生成63/6=10个不同的索引:</p> <pre class="brush:go;">const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( letterIdxBits = 6 // 6 bits to represent a letter index letterIdxMask = 1= 0; { if remain == 0 { cache, remain = rand.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx >= letterIdxBits remain-- } return string(b) }
6. Source
上面的 Masking Improved 方法已经非常好了,我们几乎没办法做更多的改进。可以但没必要。
现在让我们找找其他可以改进的地方:随机数的来源。
crypto/rand
包,它提供了一个 Read(b []byte)
函数,我们可以使用它来通过一次调用获得尽可能多的字节。这对性能没有帮助,因为 crypto/rand 实现了加密安全的伪随机数生成器,因此速度要慢得多。
所以我们还是使用math/rand
包。rand.Rand
使用的是rand.Source作为随机位来源。rand.Source
是一个指定 Int63() int64
方法的接口:这也正是我们在最新解决方案中唯一需要和使用的东西。
所以我们并不需要rand.Rand
, 可以使用rand.Source
:
var src = rand.NewSource(time.Now().UnixNano()) func RandStringBytesMaskImprSrc(n int) string { b := make([]byte, n) // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { if remain == 0 { cache, remain = src.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx >= letterIdxBits remain-- } return string(b) }
还要注意的是,这个方法不需要初始化(seed)math/rand
包里的全局Rand
,因为它没有被用到。
还有就是:math/rand 的包文档说明
The default Source is safe for concurrent use by multiple goroutines.(协程安全)
所以默认source要比rand.NewSource()
生成的source慢,是因为默认source保证了并发使用时是安全的。而 rand.NewSource()
不提供此功能(因此它返回的 Source 更有可能更快)。
7. Utilizing strings.Builder
上面所有的方法返回的字符串都是先创建一个切片的(第一个是使用[]rune
,后面的都是[]byte
),然后转换成string
。这个转换会对切片的内容做一次复制,因为字符串是不可变的,如果转换不进行复制,则不能保证字符串的内容不会被原始切边修改。详细内容可以看这里:How to convert utf8 string to []byte?和 golang: []byte(string) vs []byte(*string)。
Go 1.10 引入了 strings.Builder
。 strings.Builder 是一种新类型,我们可以使用它像 bytes.Buffer
那样来创建字符串内容。在内部,它使用 []byte
来构建内容,然后我们可以使用它的 Builder.String()
方法获得最终的字符串值。但是它的不同之处就是不会执行我们上面说到的复制操作。之所以可以这么做,是因为它用于构建的字符串字节切片没有暴露出来,所以可以保证不会有人无意或者恶意得修改它。
所以我们接下来要做的就是使用strings.Builder
而不是切片来创建字符串,最后我们可以再无需复制操作的情况下获得想要的结果。这在速度方面可能有所帮助,并且在内存使用和分配方面肯定会有所帮助。
func RandStringBytesMaskImprSrcSB(n int) string { sb := strings.Builder{} sb.Grow(n) // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { if remain == 0 { cache, remain = src.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx >= letterIdxBits remain-- } return sb.String() }
8. "Mimicing" strings.Builder with package unsafe
strings.Builder
本质上做得和我们上面做得一样,都是使用切片来创建字符串,我们使用它的唯一原因就是为了避免对切片的复制操作。
strings.Builder
通过unsafe避免复制操作:
// String returns the accumulated string. func (b *Builder) String() string { return *(*string)(unsafe.Pointer(&b.buf)) }
其实我们也可以自己来做这件事。回到之前我们使用[]byte
来创建随机字符串,但是在最后不是把它转换成string
然后返回,而是做一个不安全的转换:获取一个指向我们的字节切片的字符串作为字符串数据。
func RandStringBytesMaskImprSrcUnsafe(n int) string { b := make([]byte, n) // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { if remain == 0 { cache, remain = src.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx >= letterIdxBits remain-- } return *(*string)(unsafe.Pointer(&b)) }
Benchmark
BenchmarkRunes-4 2000000 723 ns/op 96 B/op 2 allocs/op
BenchmarkBytes-4 3000000 550 ns/op 32 B/op 2 allocs/op
BenchmarkBytesRmndr-4 3000000 438 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMask-4 3000000 534 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImpr-4 10000000 176 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrc-4 10000000 139 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrcSB-4 10000000 134 ns/op 16 B/op 1 allocs/op
BenchmarkBytesMaskImprSrcUnsafe-4 10000000 115 ns/op 16 B/op 1 allocs/op
文中关于golang的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《Go实现快速生成固定长度的随机字符串》文章吧,也可关注golang学习网公众号了解相关技术文章。

- 上一篇
- 浅析Golang中的内存逃逸

- 下一篇
- golang实现时间滑动窗口的示例代码
-
- 朴素的小懒猪
- 太给力了,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢师傅分享技术文章!
- 2023-05-27 11:11:10
-
- 威武的老鼠
- 太全面了,码起来,感谢作者大大的这篇技术贴,我会继续支持!
- 2023-05-17 12:03:56
-
- 体贴的绿茶
- 这篇技术贴出现的刚刚好,老哥加油!
- 2023-05-07 03:29:44
-
- 诚心的向日葵
- 这篇文章出现的刚刚好,好细啊,很有用,已收藏,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-03-03 02:21:47
-
- Golang · Go教程 | 6小时前 |
- TigervncDebian多用户共享桌面超简单教程
- 482浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Go语言新手必看!切片vs数组,一次搞定这两个核心知识点
- 472浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Docker在Debian上运行超简单教程(保姆级教学)
- 210浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Debian设置hostname踩坑记录:权限问题大揭秘
- 334浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Debian装SQLServer?这些问题你一定要注意!
- 284浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Debian系统下Jenkins自动化部署脚本教学
- 367浏览 收藏
-
- Golang · Go教程 | 1天前 |
- DebianSwap服务器应用实测,这些场景真的用得上!
- 319浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Debian跑TigerVNC实测!真香警告,快来看看性能咋样~
- 171浏览 收藏
-
- Golang · Go教程 | 1天前 |
- 在Debian上玩转SQLServer备份还原,手把手教你一步步操作
- 498浏览 收藏
-
- Golang · Go教程 | 1天前 |
- DebianOverlay不会玩?手把手教你轻松定制化安装
- 258浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Go语言实战:time.Ticker&time.After用法区别及避坑技巧
- 240浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Debian系统如何快速定位&干掉那些讨厌的僵尸进程
- 317浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 茅茅虫AIGC检测
- 茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
- 13次使用
-
- 赛林匹克平台(Challympics)
- 探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
- 47次使用
-
- 笔格AIPPT
- SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
- 55次使用
-
- 稿定PPT
- 告别PPT制作难题!稿定PPT提供海量模板、AI智能生成、在线协作,助您轻松制作专业演示文稿。职场办公、教育学习、企业服务全覆盖,降本增效,释放创意!
- 50次使用
-
- Suno苏诺中文版
- 探索Suno苏诺中文版,一款颠覆传统音乐创作的AI平台。无需专业技能,轻松创作个性化音乐。智能词曲生成、风格迁移、海量音效,释放您的音乐灵感!
- 55次使用
-
- 详解如何在Go语言中循环数据结构
- 2022-12-22 406浏览
-
- 详解Golang中字符串的使用
- 2023-01-01 370浏览
-
- 深度解密Go语言中字符串的使用
- 2022-12-24 160浏览
-
- Go如何实现json字符串与各类struct相互转换
- 2023-01-07 377浏览
-
- Go Java 算法之字符串解码示例详解
- 2023-01-07 479浏览