Goで、数字ではなく、文字のみのランダムな文字列(大文字または小文字)が必要です。これを行う最も速くて簡単な方法は何ですか?
Goで、数字ではなく、文字のみのランダムな文字列(大文字または小文字)が必要です。これを行う最も速くて簡単な方法は何ですか?
回答:
Paulのソリューションは、シンプルで一般的なソリューションを提供します。
質問は「最速かつ最も簡単な方法」を求めています。最速の部分にも取り組みましょう。最終的な最速のコードに反復的に到達します。各反復のベンチマークは、回答の最後にあります。
すべてのソリューションとベンチマークコードはGo Playgroundにあります。Playgroundのコードはテストファイルであり、実行可能ファイルではありません。あなたはそれをという名前のファイルに保存し、XX_test.go
それを実行する必要があります
go test -bench . -benchmem
序文:
ランダムな文字列だけが必要な場合、最速のソリューションは頼りになるソリューションではありません。そのため、ポールの解決策は完璧です。これは、パフォーマンスが重要な場合です。最初の2つのステップ(BytesとRemainder)は許容できる妥協かもしれませんが、パフォーマンスは50%ほど向上し(II。ベンチマークセクションの正確な数値を参照)、複雑さを大幅に増加させることはありません。
とはいえ、最速のソリューションが必要ない場合でも、この答えを読むことは冒険的で教育的なものになる可能性があります。
注意として、私たちが改善している元の一般的な解決策はこれです:
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)
}
ランダム文字列から選択してアセンブルする文字に英語のアルファベットの大文字と小文字のみが含まれている場合、英語のアルファベット文字はUTF-8エンコーディングのバイト1から1にマッピングされるため、バイトのみを処理できます(これはGoが文字列を格納する方法です)。
だから代わりに:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
私たちは使用できます:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
またはさらに良い:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
今、これはすでに大きな改善です:aになるように達成できましたconst
(string
定数はありますが、スライス定数はありません)。追加のゲインとして、式len(letters)
もconst
!(文字列定数のlen(s)
場合、式は定数ですs
。)
そして、どんな費用で?何もありません。string
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)
}
以前のソリューションは、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..5
と5/32
近い所望に今である確率。ビット数を増やしても重要性は低くなり、63ビットに達しても無視できます。
前のソリューションに基づいて、文字の数を表すために必要な数の乱数の最下位ビットだけを使用することにより、文字の均等な分布を維持できます。したがって、たとえば52文字の場合、それを表すには6ビットが必要です52 = 110100b
。したがって、から返される数値の最下位6ビットのみを使用しrand.Int63()
ます。また、文字の均等な分布を維持するために、範囲内にある場合にのみ、数字を「受け入れ」ます0..len(letterBytes)-1
。最下位ビットの方が大きい場合は、それを破棄し、新しい乱数を照会します。
最下位ビットがそれ以上になる可能性は一般に(平均して)len(letterBytes)
小さいことに注意してください。つまり、これが当てはまる場合でも、この「まれな」ケースを繰り返すと、適切なものが見つからない可能性が低くなります。数。繰り返しの後、私たちが良いインデックスを持っていない可能性はよりもはるかに少なく、これは単なる上限の推定です。52文字の場合、最下位の6ビットが適切でない可能性はわずかです。つまり、たとえば、10回繰り返しても適切な数にならない可能性があります。0.5
0.25
n
pow(0.5, n)
(64-52)/64 = 0.19
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)
}
前のソリューションでは、から返された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)
}
改善されたマスキングはずっと我々はそれを改善することができない、かなり良いです。複雑にすることはできますが、価値はありません。
次に、他に改善すべき点を見つけましょう。乱数のソース。
関数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)
}
また、この最後のソリューションではRand
、math/rand
パッケージのグローバルが使用されていない(そしてrand.Source
適切に初期化/シードされている)ため、パッケージのグローバルを初期化(シード)する必要がないことにも注意してください。
ここでもう1つ注意する必要があるのmath/rand
は、状態のパッケージドキュメントです。
デフォルトのソースは、複数のゴルーチンで同時に使用しても安全です。
したがって、デフォルトのソースは、Source
が取得できるものよりも低速です。デフォルトのソースはrand.NewSource()
、同時アクセス/使用時に安全性を提供する必要があるため、これを提供rand.NewSource()
しないため、Source
返される速度が高くなる可能性があります。
strings.Builder
以前のすべてのソリューションは戻りstring
、そのコンテンツ最初のスライスに内蔵されている([]rune
にジェネシス、および[]byte
その後の溶液で)、その後に変換しますstring
。string
値は不変であるため、この最終変換ではスライスのコンテンツのコピーを作成する必要があります。変換でコピーが作成されない場合、文字列のコンテンツが元のスライスを介して変更されないことは保証できません。詳細については、utf8文字列を[] byteに変換する方法を参照してください。およびgolang:[] byte(string)vs [] byte(* string)。
Go 1.10が導入されましたstrings.Builder
。 strings.Builder
にstring
類似したコンテンツを構築するために使用できる新しいタイプ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()
メソッドを呼び出して、十分に大きな内部スライスを割り当てることを確認します(ランダムな文字を追加する際の再割り当てを回避するため)。
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))
}
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()
(前者も非同期です)。
では、さまざまなソリューションのベンチマークを実施します。
真実の瞬間:
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メモリと少ない割り当ての半分を。任務完了。
rand.Source
が使われているから。より良い回避策はrand.Source
、RandStringBytesMaskImprSrc()
関数にaを渡すことです。その方法では、ロックは必要ないため、パフォーマンス/効率は影響を受けません。各goroutineは独自のものを持つことができますSource
。
defer
不要であることが明らかな場合は使用しないでください。grokbase.com/t/gg/golang-nuts/158zz5p42w/…を
defer
前またはロックを呼び出した後、すぐにどちらかのミューテックスのロックを解除することはIMOで、主に非常に良いアイデア。ロックを解除することを忘れずに、致命的でないパニックミッドファンクションでもロックを解除することが保証されます。
そのためのコードを書くだけです。このコードは、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))
}
rand.Seed(time.Now().Unix())
またはrand.Seed(time.Now().UnixNano())
math/rand
、決して使用しないでください。使用crypto/rand
の代わりに(Not_A_Golferのオプション1 @のように)。
暗号的に安全な均一(不偏)文字列を生成するパッケージuniuriを使用します。
免責事項:私はパッケージの作成者です
2つの可能なオプション(もちろんもっとあるかもしれません):
crypto/rand
(/ dev / urandomからの)ランダムバイト配列の読み取りをサポートし、暗号化ランダム生成を対象としたパッケージを使用できます。http://golang.org/pkg/crypto/rand/#example_Readを参照してください。ただし、通常の疑似乱数の生成よりも遅い場合があります。
乱数を取り、md5またはこのようなものを使用してハッシュします。
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
です。
暗号化された安全な乱数が必要で、正確な文字セットが柔軟な場合(たとえば、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
許可された文字のプールにいくつかの文字を追加したい場合は、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
必要なのですか?
len(encodeURL) == 64
。実行random % 64
されなかった場合、randomPos
64以上になり、実行時に範囲外のパニックを引き起こす可能性があります。
/*
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 /
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