Goで固定長のランダム文字列を生成する方法は?


300

Goで、数字ではなく、文字のみのランダムな文字列(大文字または小文字)が必要です。これを行う最も速くて簡単な方法は何ですか?


2
@VinceEmigh:基本的な質問を議論するメタトピックです。 meta.stackoverflow.com/q/274645/395461個人的には、基本的な質問は、上手に書かれていてトピックに沿っていれば大丈夫だと思います。以下の答えを見てください、彼らは新しい人が行くのに役立つだろうことの多くを示しています。ループの場合は、型キャスト、make()などを入力します
Shannon Matthews

2
@シャノン「この質問は研究努力を示していません」(あなたのリンクの最初の非常に賛成された答え)-それは私が言及していたものです。彼は研究努力を示さない。まったく努力はしていません(試み、または彼がオンラインで見たと述べていても、明らかにそうではありません)。それは誰かのために有用であろうが、新しい、このサイトは、新しい人に教えるに焦点を当てていません。チュートリアルやガイドではなく、特定のプログラミングの問題や質問に答えることに重点を置いています。後者に使用することもできますが、これは焦点ではないため、この質問はクローズする必要があります。代わりに、スプーンフィーディングされた/:
Vince Emigh

9
@VinceEmigh私は一年前にこの質問をしました。私はランダムな文字列をオンラインで検索し、ドキュメントも読んでいました。しかし、それは役に立ちませんでした。質問に書いていない場合でも、調査していないという意味ではありません。
Anish Shah

回答:


809

Paulのソリューションは、シンプルで一般的ソリューションを提供します。

質問は「最速かつ最も簡単な方法」を求めています。最速の部分にも取り組みましょう。最終的な最速のコードに反復的に到達します。各反復のベンチマークは、回答の最後にあります。

すべてのソリューションとベンチマークコードはGo Playgroundにあります。Playgroundのコードはテストファイルであり、実行可能ファイルではありません。あなたはそれをという名前のファイルに保存し、XX_test.goそれを実行する必要があります

go test -bench . -benchmem

序文

ランダムな文字列だけが必要な場合、最速のソリューションは頼りになるソリューションではありません。そのため、ポールの解決策は完璧です。これは、パフォーマンスが重要な場合です。最初の2つのステップ(BytesRemainder)は許容できる妥協かもしれませんが、パフォーマンスは50%ほど向上し(II。ベンチマークセクションの正確な数値を参照)、複雑さを大幅に増加させることはありません。

とはいえ、最速のソリューションが必要ない場合でも、この答えを読むことは冒険的で教育的なものになる可能性があります。

I.改善

1.ジェネシス(ルーン文字)

注意として、私たちが改善している元の一般的な解決策はこれです:

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.バイト

ランダム文字列から選択してアセンブルする文字に英語のアルファベットの大文字と小文字のみが含まれている場合、英語のアルファベット文字はUTF-8エンコーディングのバイト1から1にマッピングされるため、バイトのみを処理できます(これはGoが文字列を格納する方法です)。

だから代わりに:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

私たちは使用できます:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

またはさらに良い:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

今、これはすでに大きな改善です:aになるように達成できましたconststring定数はありますが、スライス定数はありません)。追加のゲインとして、式len(letters)const!(文字列定数のlen(s)場合、式は定数ですs。)

そして、どんな費用で?何もありません。stringsにインデックスを付けることができます。これにより、バイトに完全にインデックスを付けることができます。

次の目的地は次のようになります。

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.残り

以前のソリューションは、rand.Intn()どのデリゲートにRand.Intn()どのデリゲートを呼び出すかによってランダムな文字を指定する乱数を取得しますRand.Int31n()ます。

これはrand.Int63()、63個のランダムビットを持つ乱数を生成する場合に比べてはるかに低速です。

したがってrand.Int63()、次のように除算した後、残りを呼び出して使用することができますlen(letterBytes)

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倍の確率で数値を生成します2..5。5つのランダムビットを使用して、範囲内の数字は0..1で起こる6/32範囲の確率と数字2..55/32近い所望に今である確率。ビット数を増やしても重要性は低くなり、63ビットに達しても無視できます。

4.マスキング

前のソリューションに基づいて、文字の数を表すために必要な数の乱数の最下位ビットだけを使用することにより、文字の均等な分布を維持できます。したがって、たとえば52文字の場合、それを表すには6ビットが必要です52 = 110100b。したがって、から返される数値の最下位6ビットのみを使用しrand.Int63()ます。また、文字の均等な分布を維持するために、範囲内にある場合にのみ、数字を「受け入れ」ます0..len(letterBytes)-1。最下位ビットの方が大きい場合は、それを破棄し、新しい乱数を照会します。

最下位ビットがそれ以上になる可能性は一般に(平均して)len(letterBytes)小さいことに注意してください。つまり、これが当てはまる場合でも、この「まれな」ケースを繰り返すと、適切なものが見つからない可能性が低くなります。数。繰り返しの後、私たちが良いインデックスを持っていない可能性はよりもはるかに少なく、これは単なる上限の推定です。52文字の場合、最下位の6ビットが適切でない可能性はわずかです。つまり、たとえば、10回繰り返しても適切な数にならない可能性があります。0.50.25npow(0.5, n)(64-52)/64 = 0.191e-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.マスキングの改善

前のソリューションでは、から返された63個のランダムビットのうち、最下位の6ビットのみを使用していrand.Int63()ます。ランダムビットを取得することがアルゴリズムの最も遅い部分であるため、これは無駄です。

52文字の場合、それは6ビットが文字インデックスをコード化することを意味します。したがって、63個のランダムビットで63/6 = 10異なる文字インデックスを指定できます。それらすべての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.ソース

改善されたマスキングはずっと我々はそれを改善することができない、かなり良いです。複雑にすることはできますが、価値はありません。

次に、他に改善すべき点を見つけましょう。乱数のソース。

関数crypto/randを提供するパッケージがあるRead(b []byte)ので、これを使用して、1回の呼び出しで必要な数のバイトを取得できます。crypto/rand暗号的に安全な疑似乱数ジェネレータを実装しているため、これはパフォーマンスの点で役立ちません。

だからmath/randパッケージに固執しましょう。rand.Rand使用するrand.Sourceランダムビットのソースとして。メソッドrand.Sourceを指定するインターフェースInt63() int64です。最新のソリューションで必要かつ使用した、正確かつ唯一のもの。

したがって、rand.Rand(明示的でもグローバルでも、randパッケージの1つを共有する)は本当に必要ありません。これで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)
}

また、この最後のソリューションではRandmath/randパッケージのグローバルが使用されていない(そしてrand.Source適切に初期化/シードされている)ため、パッケージのグローバルを初期化(シード)する必要がないことにも注意してください。

ここでもう1つ注意する必要があるのmath/randは、状態のパッケージドキュメントです。

デフォルトのソースは、複数のゴルーチンで同時に使用しても安全です。

したがって、デフォルトのソースは、Sourceが取得できるものよりも低速です。デフォルトのソースはrand.NewSource()、同時アクセス/使用時に安全性を提供する必要があるため、これを提供rand.NewSource()しないため、Source返される速度が高くなる可能性があります。

7.活用 strings.Builder

以前のすべてのソリューションは戻りstring、そのコンテンツ最初のスライスに内蔵されている([]runeジェネシス、および[]byteその後の溶液で)、その後に変換しますstringstring値は不変であるため、この最終変換ではスライスのコンテンツのコピーを作成する必要があります。変換でコピーが作成されない場合、文字列のコンテンツが元のスライスを介して変更されないことは保証できません。詳細については、utf8文字列を[] byteに変換する方法を参照してくださいおよびgolang:[] byte(string)vs [] byte(* string)

Go 1.10が導入されましたstrings.Builder strings.Builderstring類似したコンテンツを構築するために使用できる新しいタイプbytes.Buffer。これはを使用して内部的に[]byte行われ、完了したら、stringその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()
}

新しいを作成した後strings.Buidler、そのBuilder.Grow()メソッドを呼び出して、十分に大きな内部スライスを割り当てることを確認します(ランダムな文字を追加する際の再割り当てを回避するため)。

8. strings.Builderパッケージの「模倣」unsafe

strings.Builder文字列を内部[]byteで作成します。これは、私たちが行ったのと同じです。したがって、基本的にはa strings.Builderを介してそれを行うとオーバーヘッドstrings.Builderが発生します。私たちが切り替えたのは、スライスの最終的なコピーを回避することだけです。

strings.Builderパッケージを使用して最終的なコピーを回避しますunsafe

// String returns the accumulated string.
func (b *Builder) String() string {
    return *(*string)(unsafe.Pointer(&b.buf))
}

実は、私たち自身もこれを行うことができます。したがって、ここでの考え方は、でラン​​ダムな文字列を構築するように切り替える[]byteことですが、完了したら、それを変換しstringて返さないで、安全でない変換を行い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))
}

(9.を使用するrand.Read()

Go 1.7ではrand.Read()関数とRand.Read()メソッドが追加されました。より良いパフォーマンスを達成するために、これらを使用して、1つのステップで必要なだけのバイトを読み取るように誘惑する必要があります。

これには小さな「問題」が1つあります。何バイトが必要ですか?出力文字の数だけ。レターインデックスは8ビット(1バイト)未満しか使用しないため、これは上位の見積もりであると思います。しかし、この時点で、ランダムビットを取得することは「難しい部分」であるため、既に状況は悪化しており、必要以上のものが得られています。

また、すべての文字インデックスの均等な分布を維持するために、使用できない「ガベージ」ランダムデータが存在する可能性があるため、一部のデータをスキップすることになるため、バイトスライス。「再帰的に」さらにランダムなバイトを取得する必要があります。そして今、私たちは「randパッケージへのシングルコール」の利点を失っています...

取得したランダムデータの使用を「ある程度」最適化できmath.Rand()ます。必要なバイト数(ビット)を見積もります。1文字にはletterIdxBitsビットが必要であり、n文字が必要であるため、n * letterIdxBits / 8.0バイトを切り上げる必要があります。ランダムインデックスが使用できない確率を計算できます(上記を参照)。これにより、「可能性が高い」だけで十分な数を要求できます(そうでない場合は、プロセスを繰り返します)。たとえば、バイトスライスを「ビットストリーム」として処理できます。これには、サードパーティの素晴らしいlibがありますgithub.com/icza/bitio(開示:私は作成者です)。

しかし、ベンチマークコードは、まだ勝っていないことを示しています。なぜそうなのですか?

最後の質問に対する答えrand.Read()は、ループを使用しSource.Int63()、渡されたスライスがいっぱいになるまで呼び出しを続けるためです。RandStringBytesMaskImprSrc()ソリューションが正確に行うこと、中間バッファなし、複雑さの追加なし。それRandStringBytesMaskImprSrc()が王座に残る理由です。はい、とRandStringBytesMaskImprSrc()rand.Source異なり、非同期を使用しrand.Read()ます。しかし、その理由は依然として当てはまります。Rand.Read()代わりに使用すると証明されますrand.Read()(前者も非同期です)。

II。基準

では、さまざまなソリューションのベンチマークを実施します。

真実の瞬間:

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

ルーン文字からバイト文字に切り替えるだけで、すぐにパフォーマンスが24%向上し、メモリ要件が3分の1に下がります。

代わりに、rand.Intn()それを取り除いて使用rand.Int63()すると、さらに20%向上します。

マスキング(および大きなインデックスの場合は繰り返し)が少し遅くなります(繰り返し呼び出しにより):- 22% ...

しかし、63個のランダムビット(1回のrand.Int63()呼び出しで10個のインデックス)のすべて(またはほとんど)を利用すると、3倍の高速化が実現します。

rand.Source代わりに(デフォルトではない、新しい)で解決すると、rand.Rand再び21%が得られます。

私たちが利用した場合strings.Builder、我々は小さな獲得3.5%スピードが、私たちも達成50%のメモリ使用量と配分の減少を!それはすばらしい!

最後に、のunsafe代わりにパッケージを使用するとstrings.Builder、再び14%が得られます。

初期溶液に、最終の比較:RandStringBytesMaskImprSrcUnsafe()6.3倍の速さよりもRandStringRunes()、使用する1/6メモリと少ない割り当ての半分を。任務完了。


8
@RobbieVうん、共有rand.Sourceが使われているから。より良い回避策はrand.SourceRandStringBytesMaskImprSrc()関数にaを渡すことです。その方法では、ロックは必要ないため、パフォーマンス/効率は影響を受けません。各goroutineは独自のものを持つことができますSource
icza

113
@icza、それは私がSOで長い間見た最良の答えの1つです!
アストロパニック2015

1
@MikeAtlas:defer不要であることが明らかな場合は使用しないでください。grokbase.com/t/gg/golang-nuts/158zz5p42w/…を
Zan Lynx

1
ヒントは@ZanLynx thx。が、defer前またはロックを呼び出した後、すぐにどちらかのミューテックスのロックを解除することはIMOで、主に非常に良いアイデア。ロックを解除することを忘れずに、致命的でないパニックミッドファンクションでもロックを解除することが保証されます。
Mike Atlas、

1
@RobbieV基になる共有ソースはすでにミューテックスを実装するLockedSource(golang.org/src/math/rand/rand.go:259)であるため、このコードはスレッド/ゴルーチン安全のようです。
adityajones 2016年

130

そのためのコードを書くだけです。このコードは、UTF-8でエンコードされたときに文字がすべて1バイトであることに依存したい場合、少し単純になる可能性があります。

package main

import (
    "fmt"
    "time"
    "math/rand"
)

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

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

func main() {
    rand.Seed(time.Now().UnixNano())

    fmt.Println(randSeq(10))
}

30
rand.Seed()について忘れないでください。そうしないと、最初に起動するたびに同じ文字列が取得されます... rand.Seed(time.Now()。UTC()。UnixNano())
Evan Lin

2
エヴァンの追加は正しいですが、他の同様のオプションがあります:rand.Seed(time.Now().Unix())またはrand.Seed(time.Now().UnixNano())
openwonk

7
推測しにくいシークレット(パスワード、暗号鍵など)の場合はmath/rand、決して使用しないでください。使用crypto/randの代わりに(Not_A_Golferのオプション1 @のように)。
twotwotwo 2015

1
@EvanLin推測できませんか?ジェネレータをシードする必要がある場合、攻撃者はそれをシードする時間を推測し、生成しているのと同じ出力を予測する可能性があります。
Matej 2015年

4
シードを使用して上記のプログラムを実行している場合、プレイグラウンドに移動すると、常に同じ結果が返されます。私はそれを遊び場で試していました、そしてしばらくして、これに気づきました。それ以外の場合は問題なく動作しました。それが誰かの時間を節約することを願っています:)
Gaurav Sinha

18

暗号的に安全な均一(不偏)文字列を生成するパッケージuniuriを使用します。

免責事項:私はパッケージの作成者です


1
余談ですが、作者であるdchestは優れた開発者であり、このような小さな便利なパッケージを数多く作成しています。
Roshambo 2018年

16

2つの可能なオプション(もちろんもっとあるかもしれません):

  1. crypto/rand(/ dev / urandomからの)ランダムバイト配列の読み取りをサポートし、暗号化ランダム生成を対象としたパッケージを使用できます。http://golang.org/pkg/crypto/rand/#example_Readを参照してください。ただし、通常の疑似乱数の生成よりも遅い場合があります。

  2. 乱数を取り、md5またはこのようなものを使用してハッシュします。


4

icza's見事に説明されたソリューションに続いて、のcrypto/rand代わりにを使用する修正を次に示しますmath/rand

const (
    letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // 52 possibilities
    letterIdxBits = 6                    // 6 bits to represent 64 possibilities / indexes
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func SecureRandomAlphaString(length int) string {

    result := make([]byte, length)
    bufferSize := int(float64(length)*1.3)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            randomBytes = SecureRandomBytes(bufferSize)
        }
        if idx := int(randomBytes[j%length] & letterIdxMask); idx < len(letterBytes) {
            result[i] = letterBytes[idx]
            i++
        }
    }

    return string(result)
}

// SecureRandomBytes returns the requested number of bytes using crypto/rand
func SecureRandomBytes(length int) []byte {
    var randomBytes = make([]byte, length)
    _, err := rand.Read(randomBytes)
    if err != nil {
        log.Fatal("Unable to generate random bytes")
    }
    return randomBytes
}

文字列を作成するために文字バイトのスライスを渡すことができる、より一般的なソリューションが必要な場合は、これを使用してみてください。

// SecureRandomString returns a string of the requested length,
// made from the byte characters provided (only ASCII allowed).
// Uses crypto/rand for security. Will panic if len(availableCharBytes) > 256.
func SecureRandomString(availableCharBytes string, length int) string {

    // Compute bitMask
    availableCharLength := len(availableCharBytes)
    if availableCharLength == 0 || availableCharLength > 256 {
        panic("availableCharBytes length must be greater than 0 and less than or equal to 256")
    }
    var bitLength byte
    var bitMask byte
    for bits := availableCharLength - 1; bits != 0; {
        bits = bits >> 1
        bitLength++
    }
    bitMask = 1<<bitLength - 1

    // Compute bufferSize
    bufferSize := length + length / 3

    // Create random string
    result := make([]byte, length)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            // Random byte buffer is empty, get a new one
            randomBytes = SecureRandomBytes(bufferSize)
        }
        // Mask bytes to get an index into the character slice
        if idx := int(randomBytes[j%length] & bitMask); idx < availableCharLength {
            result[i] = availableCharBytes[idx]
            i++
        }
    }

    return string(result)
}

独自のランダム性のソースを渡したい場合は、上記を変更してio.Readerを使用する代わりに受け入れることは簡単crypto/randです。


2

暗号化された安全な乱数が必要で、正確な文字セットが柔軟な場合(たとえば、base64は問題ありません)、必要な出力サイズから必要な乱数文字の長さを正確に計算できます。

ベース64のテキストは、ベース256の1/3です(2 ^ 8対2 ^ 6、8ビット/ 6ビット= 1.333の比率)。

import (
    "crypto/rand"
    "encoding/base64"
    "math"
)

func randomBase64String(l int) string {
    buff := make([]byte, int(math.Round(float64(l)/float64(1.33333333333))))
    rand.Read(buff)
    str := base64.RawURLEncoding.EncodeToString(buff)
    return str[:l] // strip 1 extra character we get from odd length results
}

注:-および_よりも+および/文字を使用する場合は、RawStdEncodingを使用することもできます。

16進数が必要な場合、ベース16はベース256の2倍長くなります(2 ^ 8対2 ^ 4、8ビット/ 4ビット= 2倍の比率)。

import (
    "crypto/rand"
    "encoding/hex"
    "math"
)


func randomBase16String(l int) string {
    buff := make([]byte, int(math.Round(float64(l)/2)))
    rand.Read(buff)
    str := hex.EncodeToString(buff)
    return str[:l] // strip 1 extra character we get from odd length results
}

ただし、文字セットにbase256からbaseNのエンコーダーがある場合は、これを任意の文字セットに拡張できます。キャラクタセットを表すために必要なビット数を使用して、同じサイズの計算を実行できます。任意の文字セットの比率計算は次のとおりratio = 8 / log2(len(charset))です。

これらのソリューションはどちらも安全でシンプルですが、高速であり、暗号エントロピープールを無駄にしないでください。

これは、あらゆるサイズで機能する遊び場です。https://play.golang.org/p/i61WUVR8_3Z


あなたがそのコードの異なる実行であっ異なるランダム文字列で表示されませんので、行く遊び場は常に、同じ乱数を返すことに言及する価値
TPPZ

2
func Rand(n int) (str string) {
    b := make([]byte, n)
    rand.Read(b)
    str = fmt.Sprintf("%x", b)
    return
}

なぜn * 2を生成するの[]byteですか?
M.ロスタミ

1

これが私のやり方です)望みどおりに数学ランドまたは暗号ランドを使用してください。

func randStr(len int) string {
    buff := make([]byte, len)
    rand.Read(buff)
    str := base64.StdEncoding.EncodeToString(buff)
    // Base 64 can be longer than len
    return str[:len]
}

0

許可された文字のプールにいくつかの文字を追加したい場合は、io.Readerを介してランダムなバイトを提供するあらゆるものでコードを機能させることができます。ここではを使用していcrypto/randます。

// len(encodeURL) == 64. This allows (x <= 265) x % 64 to have an even
// distribution.
const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"

// A helper function create and fill a slice of length n with characters from
// a-zA-Z0-9_-. It panics if there are any problems getting random bytes.
func RandAsciiBytes(n int) []byte {
    output := make([]byte, n)

    // We will take n bytes, one byte for each character of output.
    randomness := make([]byte, n)

    // read all random
    _, err := rand.Read(randomness)
    if err != nil {
        panic(err)
    }

    // fill output
    for pos := range output {
        // get random item
        random := uint8(randomness[pos])

        // random % 64
        randomPos := random % uint8(len(encodeURL))

        // put into output
        output[pos] = encodeURL[randomPos]
    }

    return output
}

なぜrandom % 64必要なのですか?
Sung Cho

2
なぜならlen(encodeURL) == 64。実行random % 64されなかった場合、randomPos64以上になり、実行時に範囲外のパニックを引き起こす可能性があります。
0xcaff

-1
/*
    korzhao
*/

package rand

import (
    crand "crypto/rand"
    "math/rand"
    "sync"
    "time"
    "unsafe"
)

// Doesn't share the rand library globally, reducing lock contention
type Rand struct {
    Seed int64
    Pool *sync.Pool
}

var (
    MRand    = NewRand()
    randlist = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
)

// init random number generator
func NewRand() *Rand {
    p := &sync.Pool{New: func() interface{} {
        return rand.New(rand.NewSource(getSeed()))
    },
    }
    mrand := &Rand{
        Pool: p,
    }
    return mrand
}

// get the seed
func getSeed() int64 {
    return time.Now().UnixNano()
}

func (s *Rand) getrand() *rand.Rand {
    return s.Pool.Get().(*rand.Rand)
}
func (s *Rand) putrand(r *rand.Rand) {
    s.Pool.Put(r)
}

// get a random number
func (s *Rand) Intn(n int) int {
    r := s.getrand()
    defer s.putrand(r)

    return r.Intn(n)
}

//  bulk get random numbers
func (s *Rand) Read(p []byte) (int, error) {
    r := s.getrand()
    defer s.putrand(r)

    return r.Read(p)
}

func CreateRandomString(len int) string {
    b := make([]byte, len)
    _, err := MRand.Read(b)
    if err != nil {
        return ""
    }
    for i := 0; i < len; i++ {
        b[i] = randlist[b[i]%(62)]
    }
    return *(*string)(unsafe.Pointer(&b))
}

24.0 ns / op 16 B / op 1 allocs /


こんにちは!StackOverflowへようこそ。あなたはコードスニペットを追加しましたが、あなたの答えは「それがどのように機能するか」または「なぜこれがそのやり方であるのか」についての文脈を含みません。また、質問は英語で行われるため、コメントも英語で入力してください。
Cengiz Can

-2
const (
    chars       = "0123456789_abcdefghijkl-mnopqrstuvwxyz" //ABCDEFGHIJKLMNOPQRSTUVWXYZ
    charsLen    = len(chars)
    mask        = 1<<6 - 1
)

var rng = rand.NewSource(time.Now().UnixNano())

// RandStr 返回指定长度的随机字符串
func RandStr(ln int) string {
    /* chars 38个字符
     * rng.Int63() 每次产出64bit的随机数,每次我们使用6bit(2^6=64) 可以使用10次
     */
    buf := make([]byte, ln)
    for idx, cache, remain := ln-1, rng.Int63(), 10; idx >= 0; {
        if remain == 0 {
            cache, remain = rng.Int63(), 10
        }
        buf[idx] = chars[int(cache&mask)%charsLen]
        cache >>= 6
        remain--
        idx--
    }
    return *(*string)(unsafe.Pointer(&buf))
}

BenchmarkRandStr16-8 20000000 68.1 ns / op 16 B / op 1 allocs / op

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.