返回顶部
首页 > 资讯 > 后端开发 > GO >Go实现快速生成固定长度的随机字符串
  • 789
分享到

Go实现快速生成固定长度的随机字符串

2024-04-02 19:04:59 789人浏览 薄情痞子
摘要

目录前言Improvements1. Genesis (Runes)2. Bytes3. Remainder4. Masking5. Masking Improved6. Sourc

Q:怎样在Go语言中简单并快速地生成固定长度的随机字符串

A:

问题是“最快和最简单的方式”,接下来我们会一步步迭代,最终实现最快的方式。每次迭代的基准测试代码放在了答案的末尾。

所有解决方案和基准测试代码都可以在 Go Playground 上找到。Playground 上的代码是测试文件,不是可执行文件。你需要把它保存到文件中并命名为XX_test.go然后运行

go test -bench . -benchmem

前言

如果您只需要一个随机字符串,最快的解决方案不是首选解决方案。Paul的解决方案(下面的第一种方法)就很好。如果很关注性能,那么前两个方法可能是可接受的折中方案:它们把性能提升了50%,而且也没有显著增加复杂性。

话虽如此,但就算你不需要最快生成随机字符串的方法,通读这篇回答相信你也应该会有所收获。

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<<63 - 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<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func RandStringBytesMask(n int) string {
    b := make([]byte, n)
    for i := 0; i < n; {
        if idx := int(rand.Int63() & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i++
        }
    }
    return string(b)
}

5. Masking Improved

前面的解决方案只使用 rand.Int63() 返回的 63 个随机位中的最低 6 位。这是一种浪费,因为获取随机位是我们算法中最慢的部分。

因为我们有52个字母,可以用6位来编码一个字母索引。所以63个随机位可以生成63/6=10个不同的索引:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
    letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

func RandStringBytesMaskImpr(n int) string {
    b := make([]byte, n)
    // A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
    for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = rand.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= 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 < len(letterBytes) {
           b[i] = letterBytes[idx]
           i--
       }
       cache >>= 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 < len(letterBytes) {
            sb.WriteByte(letterBytes[idx])
            i--
        }
        cache >>= 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 < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= 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

以上就是Go实现快速生成固定长度的随机字符串的详细内容,更多关于Go生成固定长度随机字符串的资料请关注编程网其它相关文章!

您可能感兴趣的文档:

--结束END--

本文标题: Go实现快速生成固定长度的随机字符串

本文链接: https://lsjlt.com/news/121271.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Go实现快速生成固定长度的随机字符串
    目录前言Improvements1. Genesis (Runes)2. Bytes3. Remainder4. Masking5. Masking Improved6. Sourc...
    99+
    2024-04-02
  • go语言生成随机数和随机字符串的实现方法
    目录生成随机数生成随机字符串生成随机数 随机数的生成是计算机科学的一个研究领域,同时也是一种艺术。这是因为计算机是纯粹的逻辑机器,所以使用计算机生成随机数异常困难! 你可以用...
    99+
    2022-06-07
    GO 方法 字符串 go语言 随机数 字符
  • go语言如何生成随机数和随机字符串
    小编给大家分享一下go语言如何生成随机数和随机字符串,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!生成随机数随机数的生成是计算机科学的一个研究领域,同时也是一种艺...
    99+
    2023-06-22
  • python生成随机数、随机字符、随机字符串的方法示例
    本文是基于Windows 10系统环境,实现python生成随机数、随机字符、随机字符串: Windows 10 PyCharm 2018.3.5 for Windows ...
    99+
    2024-04-02
  • python怎么输出固定长度的字符串
    可以使用字符串的format方法来输出固定长度的字符串。具体步骤如下: 使用format方法来格式化字符串,并设置字符串的长度。 ...
    99+
    2023-10-23
    python
  • java生成随机字符串的方法
      今天我们来学习下随机数的生成,随机数就是不确定的数,它可以是任意一个整数或者字符串。下面就让我们一起来学习下随机字符串的生成吧。 首先,我们需要先定义一个名为 str的数组,并将它作为输入文件。 1、使用 Java中的 str作为输入...
    99+
    2023-10-25
    java python pandas
  • Java 生成随机字符串数组的实例详解
    Java 生成随机字符串数组的实例详解利用Collections.sort()方法对泛型为String的List 进行排序。具体要求:创建完List<String>之后,往其中添加十条随机字符串2.每条字符串的长度为10以内的随...
    99+
    2023-05-31
    java 随机 字符串
  • Java随机生成字符串的4种方式
    java.util.UUID 类可用于生成UUID, 它的static randomUUID方法返回一个32个字符的字符串。 import java.util.UUID;public class RandomStringGenerator ...
    99+
    2023-09-08
    java 开发语言 jvm
  • java生成随机字符串的两种方法
    本文实例为大家分享了java生成随机字符串的具体代码,供大家参考,具体内容如下import java.util.Random;public class CharacterUtils {//方法1:length为产生的位数 public s...
    99+
    2023-05-31
    java 字符串 ava
  • Linux Shell 生成随机数和随机字符串的方法示例
    日常生活中,会经常用到随机数,使用场景非常广泛,例如买彩票、丢骰子、抽签、年会抽奖等。 Shell 下如何生成随机数呢,米扑博客特意写了本文,总结 linux Shell 产生随机数的多种方法。 计算机产生的的只是“伪随...
    99+
    2022-06-04
    Linux Shell 生成随机数 Linux Shell 随机字符串
  • Python快速生成随机密码超简单实现
    目录知识点代码解析效果展示知识点 文件读写基础语法字符串处理字符拼接 代码解析 导入模块 import platform import string import random 将...
    99+
    2024-04-02
  • php中的字符串怎么生成随机密码
    本篇内容介绍了“php中的字符串怎么生成随机密码”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!首先定义一个password_generate...
    99+
    2023-06-20
  • python生成随机字符串的方法是什么
    Python中生成随机字符串有多种方法,以下是其中几种常用的方法:1. 使用`random`模块的`choice`函数结合字符串生成...
    99+
    2023-08-18
    python
  • php怎么生成不重复的随机字符串
    PHP中可以使用`uniqid()`函数生成不重复的随机字符串。```php$randomString = uniqid();```...
    99+
    2023-08-25
    php
  • PHP中怎么利用给定的字符串生成随机密码
    这期内容当中小编将会给大家带来有关PHP中怎么利用给定的字符串生成随机密码,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。直接上代码:<phpfunction password_genera...
    99+
    2023-06-20
  • JavaScript如何生成一个随机的数字字母字符串
    这篇文章主要介绍了JavaScript如何生成一个随机的数字字母字符串,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。生成一个随机的数字字母字...
    99+
    2024-04-02
  • JAVA删除字符串固定下标字串的实现
    目录需要修改的报文实现代码如下多存在多个不符合规定的数据然后你要删掉怎么操作呢?实现代码如下此解决方式在企业中有所应用,适合Java初级开发学习,参考。 需要修改的报文 当你拿到的报...
    99+
    2023-05-15
    JAVA删除下标字串 JAVA删除固定下标字串
  • 利用python3随机生成中文字符的实现方法
    前言 运行环境在Python3.6下,Python2的解决方案网上有很多.,想学习python2实现的朋友们可以参考这篇文章:http://www.lsjlt.com/article/34884.htm,下...
    99+
    2022-06-04
    中文 字符 方法
  • 实现MySQL产生随机数并连接字符串的方法
    这篇文章主要介绍了实现MySQL产生随机数并连接字符串的方法,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获。下面让小编带着大家一起了解一下。用到的方法:concat(&...
    99+
    2024-04-02
  • Java实现产生随机字符串主键的UUID工具类
    这篇文章将为大家详细讲解有关Java实现产生随机字符串主键的UUID工具类,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。本文实例讲述了Java实现产生随机字符串主键的UUID工具类。分享给大家供大家参考,...
    99+
    2023-05-30
    java 随机字符串 工具类
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作