Ich möchte nur eine zufällige Zeichenfolge (Groß- oder Kleinbuchstaben), keine Zahlen in Go. Was ist der schnellste und einfachste Weg, dies zu tun?
Ich möchte nur eine zufällige Zeichenfolge (Groß- oder Kleinbuchstaben), keine Zahlen in Go. Was ist der schnellste und einfachste Weg, dies zu tun?
Antworten:
Die Lösung von Paul bietet eine einfache , allgemeine Lösung.
Die Frage fragt nach dem "schnellsten und einfachsten Weg" . Lassen Sie uns auch den schnellsten Teil ansprechen . Wir werden iterativ zu unserem endgültigen, schnellsten Code gelangen. Das Benchmarking jeder Iteration finden Sie am Ende der Antwort.
Alle Lösungen und der Benchmarking-Code finden Sie auf dem Go Playground . Der Code auf dem Spielplatz ist eine Testdatei, keine ausführbare Datei. Sie müssen es in einer Datei mit dem Namen speichern XX_test.go
und mit ausführen
go test -bench . -benchmem
Vorwort :
Die schnellste Lösung ist keine Lösung, wenn Sie nur eine zufällige Zeichenfolge benötigen. Dafür ist Pauls Lösung perfekt. Dies ist der Fall, wenn die Leistung eine Rolle spielt. Obwohl die ersten beiden Schritte ( Bytes und Rest ) ein akzeptabler Kompromiss sein könnten: Sie verbessern die Leistung um etwa 50% (siehe genaue Zahlen im Abschnitt II. Benchmark ) und erhöhen die Komplexität nicht wesentlich.
Selbst wenn Sie nicht die schnellste Lösung benötigen, kann das Lesen dieser Antwort abenteuerlich und lehrreich sein.
Zur Erinnerung: Die ursprüngliche, allgemeine Lösung, die wir verbessern, lautet:
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)
}
Wenn die Zeichen, aus denen Sie auswählen und die zufällige Zeichenfolge zusammenstellen können, nur Groß- und Kleinbuchstaben des englischen Alphabets enthalten, können wir nur mit Bytes arbeiten, da die Buchstaben des englischen Alphabets in der UTF-8-Codierung den Bytes 1 zu 1 zugeordnet sind (welche So speichert Go Strings.
Also statt:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
wir können benutzen:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Oder noch besser:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Dies ist bereits eine große Verbesserung: Wir könnten es als a erreichen const
(es gibt string
Konstanten, aber keine Slice-Konstanten ). Als zusätzlichen Gewinn wird der Ausdruck len(letters)
auch ein const
! (Der Ausdruck len(s)
ist konstant, wenn s
es sich um eine Zeichenfolgenkonstante handelt.)
Und zu welchem Preis? Gar nichts. string
s kann indiziert werden, wodurch seine Bytes indiziert werden, perfekt, genau das, was wir wollen.
Unser nächstes Ziel sieht so aus:
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)
}
Frühere Lösungen erhalten eine Zufallszahl, um einen zufälligen Buchstaben zu bestimmen, indem sie anrufen, an rand.Intn()
welche Delegierten Rand.Intn()
welche Delegierten senden Rand.Int31n()
.
Dies ist viel langsamer als bei rand.Int63()
einer Zufallszahl mit 63 Zufallsbits.
Wir könnten also einfach rand.Int63()
den Rest aufrufen und verwenden, nachdem wir geteilt haben durch 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)
}
Dies funktioniert und ist erheblich schneller. Der Nachteil ist, dass die Wahrscheinlichkeit aller Buchstaben nicht exakt gleich ist (vorausgesetzt, es rand.Int63()
werden alle 63-Bit-Zahlen mit gleicher Wahrscheinlichkeit erzeugt). Obwohl die Verzerrung extrem gering ist, da die Anzahl der Buchstaben 52
viel, viel kleiner ist als 1<<63 - 1
, ist dies in der Praxis vollkommen in Ordnung.
Um dies verständlicher zu machen: Angenommen, Sie möchten eine Zufallszahl im Bereich von 0..5
. Unter Verwendung von 3 zufälligen Bits würde dies die Zahlen 0..1
mit doppelter Wahrscheinlichkeit als aus dem Bereich erzeugen 2..5
. Bei Verwendung von 5 Zufallsbits 0..1
würden Zahlen im Bereich mit der 6/32
Wahrscheinlichkeit und Zahlen im Bereich 2..5
mit der 5/32
Wahrscheinlichkeit auftreten, die jetzt näher am gewünschten Wert liegen. Das Erhöhen der Anzahl von Bits macht dies weniger bedeutsam, wenn es 63 Bits erreicht, ist es vernachlässigbar.
Aufbauend auf der vorherigen Lösung können wir die gleiche Verteilung der Buchstaben beibehalten, indem wir nur so viele der niedrigsten Bits der Zufallszahl verwenden, wie für die Darstellung der Anzahl der Buchstaben erforderlich sind. Wenn wir zum Beispiel 52 Buchstaben haben, sind 6 Bits erforderlich, um sie darzustellen : 52 = 110100b
. Wir werden also nur die niedrigsten 6 Bits der von zurückgegebenen Zahl verwenden rand.Int63()
. Und um eine gleichmäßige Verteilung der Buchstaben zu gewährleisten, "akzeptieren" wir die Zahl nur, wenn sie in den Bereich fällt 0..len(letterBytes)-1
. Wenn die niedrigsten Bits größer sind, verwerfen wir sie und fragen eine neue Zufallszahl ab.
Beachten Sie, dass die Wahrscheinlichkeit, dass die niedrigsten Bits größer oder gleich sind, len(letterBytes)
geringer ist als 0.5
im Allgemeinen ( 0.25
im Durchschnitt), was bedeutet, dass selbst wenn dies der Fall wäre, die Wiederholung dieses "seltenen" Falls die Wahrscheinlichkeit verringert, kein Gut zu finden Nummer. Nach der n
Wiederholung ist die Wahrscheinlichkeit, dass wir noch keinen guten Index haben, viel geringer als pow(0.5, n)
, und dies ist nur eine obere Schätzung. Bei 52 Buchstaben ist die Wahrscheinlichkeit, dass die 6 niedrigsten Bits nicht gut sind, nur (64-52)/64 = 0.19
; was zum Beispiel bedeutet, dass die Chance besteht, nach 10 Wiederholungen keine gute Zahl zu haben 1e-8
.
Hier ist also die Lösung:
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)
}
Die vorherige Lösung verwendet nur die niedrigsten 6 Bits der 63 von zurückgegebenen Zufallsbits rand.Int63()
. Dies ist eine Verschwendung, da das Abrufen der zufälligen Bits der langsamste Teil unseres Algorithmus ist.
Wenn wir 52 Buchstaben haben, bedeutet dies, dass 6 Bit einen Buchstabenindex codieren. So können 63 zufällige Bits 63/6 = 10
unterschiedliche Buchstabenindizes bezeichnen. Verwenden wir alle diese 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)
}
Die verbesserte Maskierung ist ziemlich gut, wir können nicht viel daran verbessern. Wir konnten, aber die Komplexität nicht wert.
Lassen Sie uns jetzt etwas anderes finden, um es zu verbessern. Die Quelle von Zufallszahlen.
Es gibt ein crypto/rand
Paket, das eine Read(b []byte)
Funktion bereitstellt , sodass wir damit mit einem einzigen Aufruf so viele Bytes abrufen können, wie wir benötigen. Dies würde in Bezug auf die Leistung nicht helfen, da crypto/rand
ein kryptografisch sicherer Pseudozufallszahlengenerator implementiert wird, sodass er viel langsamer ist.
Bleiben wir also beim math/rand
Paket. Das rand.Rand
verwendet a rand.Source
als Quelle für zufällige Bits. rand.Source
ist eine Schnittstelle, die eine Int63() int64
Methode spezifiziert : genau und das einzige, was wir in unserer neuesten Lösung brauchten und verwendeten.
Wir brauchen also nicht wirklich ein rand.Rand
(entweder explizites oder globales, gemeinsam genutztes rand
) Paket, a rand.Source
ist perfekt genug für uns:
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)
}
Beachten Sie auch, dass Sie bei dieser letzten Lösung nicht das Global Rand
des math/rand
Pakets initialisieren (Startwert) müssen , da dieser nicht verwendet wird (und unser Paket rand.Source
ordnungsgemäß initialisiert / Startwert ist).
Eine weitere Sache, die Sie hier beachten sollten: Paket doc of math/rand
state:
Die Standardquelle ist für die gleichzeitige Verwendung durch mehrere Goroutinen sicher.
Die Standardquelle ist also langsamer als eine Source
, die von erhalten werden kann rand.NewSource()
, da die Standardquelle Sicherheit bei gleichzeitigem Zugriff / gleichzeitiger Verwendung rand.NewSource()
bieten muss, dies jedoch nicht bietet (und daher ist die Source
von ihr zurückgegebene Quelle eher schneller).
strings.Builder
Alle vorherigen Lösungen geben a zurück, string
dessen Inhalt zuerst in einem Slice ( []rune
in Genesis und []byte
in nachfolgenden Lösungen) erstellt und dann in konvertiert wird string
. Bei dieser endgültigen Konvertierung muss eine Kopie des Slice-Inhalts erstellt werden, da die string
Werte unveränderlich sind. Wenn bei der Konvertierung keine Kopie erstellt wird, kann nicht garantiert werden, dass der Inhalt der Zeichenfolge nicht über das ursprüngliche Slice geändert wird. Weitere Informationen finden Sie unter Konvertieren der utf8-Zeichenfolge in [] Byte. und golang: [] Byte (Zeichenfolge) vs [] Byte (* Zeichenfolge) .
Go 1.10 eingeführt strings.Builder
. strings.Builder
Ein neuer Typ, mit dem wir string
ähnliche Inhalte erstellen können bytes.Buffer
. Es macht es intern mit a []byte
, und wenn wir fertig sind, können wir den endgültigen string
Wert mit seiner Builder.String()
Methode erhalten. Aber was daran cool ist, ist, dass es dies tut, ohne die Kopie auszuführen, über die wir gerade gesprochen haben. Dies wird gewagt, da das zum Erstellen des Inhalts der Zeichenfolge verwendete Byte-Slice nicht verfügbar ist. Daher wird garantiert, dass niemand es unbeabsichtigt oder böswillig ändern kann, um die erzeugte "unveränderliche" Zeichenfolge zu ändern.
Unsere nächste Idee ist es also, die zufällige Zeichenfolge nicht in einem Slice zu erstellen, sondern mit Hilfe von a strings.Builder
. Sobald wir fertig sind, können wir das Ergebnis erhalten und zurückgeben, ohne eine Kopie davon erstellen zu müssen. Dies kann in Bezug auf die Geschwindigkeit hilfreich sein, und es wird definitiv in Bezug auf die Speichernutzung und Zuweisung helfen.
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()
}
Beachten Sie, dass strings.Buidler
wir nach dem Erstellen einer neuen Builder.Grow()
Methode diese Methode aufgerufen haben , um sicherzustellen, dass ein ausreichend großes internes Slice zugewiesen wird (um Neuzuordnungen beim Hinzufügen der zufälligen Buchstaben zu vermeiden).
strings.Builder
mit Paketunsafe
strings.Builder
baut den String in einem internen auf []byte
, genau wie wir es selbst getan haben. Wenn Sie dies also über a tun, strings.Builder
ist der Overhead etwas, auf das wir umgestellt haben strings.Builder
, um das endgültige Kopieren des Slice zu vermeiden.
strings.Builder
vermeidet die endgültige Kopie durch Verwendung von Paket unsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Die Sache ist, wir können das auch selbst tun. Die Idee hier ist also, wieder zum Erstellen der zufälligen Zeichenfolge in a zu wechseln. []byte
Wenn wir fertig sind, konvertieren Sie sie nicht string
in return, sondern führen Sie eine unsichere Konvertierung durch: Erhalten Sie eine, string
die auf unser Byte-Slice als Zeichenfolgendaten verweist .
So kann es gemacht werden:
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 fügte eine rand.Read()
Funktion und eine Rand.Read()
Methode hinzu. Wir sollten versucht sein, diese zu verwenden, um in einem Schritt so viele Bytes zu lesen, wie wir benötigen, um eine bessere Leistung zu erzielen.
Hier gibt es ein kleines "Problem": Wie viele Bytes brauchen wir? Wir könnten sagen: so viele wie die Anzahl der ausgegebenen Buchstaben. Wir würden denken, dass dies eine obere Schätzung ist, da ein Buchstabenindex weniger als 8 Bits (1 Byte) verwendet. Aber zu diesem Zeitpunkt geht es uns bereits schlechter (da das Erhalten der zufälligen Bits der "schwierige Teil" ist), und wir bekommen mehr als nötig.
Beachten Sie auch, dass es zur Aufrechterhaltung einer gleichmäßigen Verteilung aller Buchstabenindizes möglicherweise zufällige "Müll" -Daten gibt, die wir nicht verwenden können, sodass wir am Ende einige Daten überspringen und daher kurz werden, wenn wir alle durchlaufen das Byte Slice. Wir müssten weitere zufällige Bytes "rekursiv" erhalten. Und jetzt verlieren wir sogar den rand
Vorteil "Single Call to Package" ...
Wir könnten die Verwendung der Zufallsdaten, aus denen wir gewinnen, "etwas" optimieren math.Rand()
. Wir können schätzen, wie viele Bytes (Bits) wir benötigen. 1 Buchstabe benötigt letterIdxBits
Bits, und wir brauchen n
Buchstaben, also brauchen wir n * letterIdxBits / 8.0
Bytes, die aufgerundet werden. Wir können die Wahrscheinlichkeit berechnen, dass ein Zufallsindex nicht verwendet werden kann (siehe oben), sodass wir mehr anfordern können, das "wahrscheinlicher" ausreicht (wenn sich herausstellt, dass dies nicht der Fall ist, wiederholen wir den Vorgang). Wir können das Byte-Slice zum Beispiel als "Bit-Stream" verarbeiten, für den wir eine nette Drittanbieter-Bibliothek haben: github.com/icza/bitio
(Offenlegung: Ich bin der Autor).
Aber der Benchmark-Code zeigt immer noch, dass wir nicht gewinnen. Wieso ist es so?
Die Antwort auf die letzte Frage lautet, weil rand.Read()
eine Schleife verwendet wird und so lange aufgerufen wird, Source.Int63()
bis das übergebene Slice gefüllt ist. Genau das, was die RandStringBytesMaskImprSrc()
Lösung tut, ohne den Zwischenpuffer und ohne die zusätzliche Komplexität. Deshalb RandStringBytesMaskImprSrc()
bleibt auf dem Thron. Ja, RandStringBytesMaskImprSrc()
verwendet im rand.Source
Gegensatz zu nicht synchronisiert rand.Read()
. Aber die Argumentation gilt immer noch; und was bewiesen ist, wenn wir Rand.Read()
statt verwenden rand.Read()
(ersteres ist auch nicht synchronisiert).
In Ordnung, es ist Zeit für ein Benchmarking der verschiedenen Lösungen.
Moment der Wahrheit:
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
Allein durch den Wechsel von Runen zu Bytes erzielen wir sofort einen Leistungszuwachs von 24% , und der Speicherbedarf sinkt auf ein Drittel .
Wenn Sie stattdessen loswerden rand.Intn()
und verwenden, erhalten Sie rand.Int63()
weitere 20% .
Das Maskieren (und Wiederholen bei großen Indizes) verlangsamt sich etwas (aufgrund von Wiederholungsaufrufen): -22% ...
Wenn wir jedoch alle (oder die meisten) der 63 Zufallsbits (10 Indizes aus einem rand.Int63()
Aufruf) verwenden, beschleunigt sich dies erheblich: dreimal .
Wenn wir uns rand.Source
stattdessen mit einem (nicht standardmäßigen, neuen) zufrieden geben rand.Rand
, gewinnen wir erneut 21%.
Wenn wir verwenden strings.Builder
, gewinnen wir winzige 3,5% an Geschwindigkeit , aber wir haben auch eine 50% ige Reduzierung der Speichernutzung und -zuweisung erreicht! Das ist schön!
Wenn wir es schließlich wagen, das Paket unsafe
anstelle von zu verwenden strings.Builder
, erhalten wir wieder schöne 14% .
Vergleich der endgültigen mit der ursprünglichen Lösung: RandStringBytesMaskImprSrcUnsafe()
ist 6,3-mal schneller als RandStringRunes()
, verwendet einen sechsten Speicher und halb so wenige Zuordnungen . Mission erfüllt.
rand.Source
verwendet wird. Eine bessere Problemumgehung wäre die Übergabe von a rand.Source
an die RandStringBytesMaskImprSrc()
Funktion. Auf diese Weise ist keine Verriegelung erforderlich, und daher wird die Leistung / Effizienz nicht beeinträchtigt. Jede Goroutine könnte ihre eigene haben Source
.
defer
wenn es offensichtlich ist, dass Sie es nicht benötigen. Siehe grokbase.com/t/gg/golang-nuts/158zz5p42w/…
defer
es meistens eine sehr gute Idee ist, einen Mutex entweder unmittelbar vor oder nach dem Aufrufen einer Sperre zu entsperren . Sie werden garantiert nicht vergessen, zu entsperren, sondern auch zu entsperren, selbst in einer nicht tödlichen Panik-Mid-Funktion.
Sie können einfach Code dafür schreiben. Dieser Code kann etwas einfacher sein, wenn Sie sich darauf verlassen möchten, dass die Buchstaben bei der Codierung in UTF-8 alle einzelne Bytes sind.
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())
oderrand.Seed(time.Now().UnixNano())
math/rand
; Verwenden Sie crypto/rand
stattdessen (wie bei Option 1 von @ Not_A_Golfer).
Verwenden Sie das Paket uniuri , das kryptografisch sichere einheitliche (unverzerrte) Zeichenfolgen generiert.
Haftungsausschluss: Ich bin der Autor des Pakets
Zwei mögliche Optionen (es könnte natürlich mehr geben):
Sie können das crypto/rand
Paket verwenden, das das Lesen von zufälligen Byte-Arrays (aus / dev / urandom) unterstützt und auf die kryptografische Zufallsgenerierung ausgerichtet ist. Siehe http://golang.org/pkg/crypto/rand/#example_Read . Es könnte jedoch langsamer sein als die normale Erzeugung von Pseudozufallszahlen.
Nehmen Sie eine Zufallszahl und hashen Sie sie mit md5 oder ähnlichem.
Nach einer icza's
wunderbar erklärten Lösung finden Sie hier eine Modifikation, die crypto/rand
anstelle von verwendet wird 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
}
Wenn Sie eine allgemeinere Lösung wünschen, mit der Sie das Stück Zeichenbytes übergeben können, um die Zeichenfolge daraus zu erstellen, können Sie Folgendes verwenden:
// 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)
}
Wenn Sie Ihre eigene Zufallsquelle weitergeben möchten, ist es trivial, die obigen Angaben zu ändern, um eine zu akzeptieren, io.Reader
anstatt sie zu verwenden crypto/rand
.
Wenn Sie kryptografisch sichere Zufallszahlen wünschen und der genaue Zeichensatz flexibel ist (z. B. base64 ist in Ordnung), können Sie aus der gewünschten Ausgabegröße genau die Länge der benötigten Zufallszeichen berechnen.
Der Text der Basis 64 ist 1/3 länger als der der Basis 256. (Verhältnis 2 ^ 8 vs 2 ^ 6; Verhältnis 8 Bit / 6 Bit = 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
}
Hinweis: Sie können RawStdEncoding auch verwenden, wenn Sie + und / Zeichen gegenüber - und _ bevorzugen
Wenn Sie hexadezimal wünschen, ist die Basis 16 2x länger als die Basis 256. (2 ^ 8 vs 2 ^ 4; Verhältnis 8 Bits / 4 Bits = 2x)
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
}
Sie können dies jedoch auf einen beliebigen Zeichensatz erweitern, wenn Sie einen Base256-BaseN-Encoder für Ihren Zeichensatz haben. Sie können dieselbe Größenberechnung mit der Anzahl der Bits durchführen, die zur Darstellung Ihres Zeichensatzes benötigt werden. Die Verhältnisberechnung für einen beliebigen Zeichensatz lautet :) ratio = 8 / log2(len(charset))
.
Obwohl beide Lösungen sicher und einfach sind, sollten sie schnell sein und Ihren Krypto-Entropie-Pool nicht verschwenden.
Hier ist der Spielplatz, der zeigt, dass er für jede Größe funktioniert. https://play.golang.org/p/i61WUVR8_3Z
func Rand(n int) (str string) {
b := make([]byte, n)
rand.Read(b)
str = fmt.Sprintf("%x", b)
return
}
[]byte
?
Wenn Sie bereit sind, Ihrem Pool zulässiger Zeichen einige Zeichen hinzuzufügen, können Sie den Code mit allem arbeiten lassen, das über einen io.Reader zufällige Bytes bereitstellt. Hier verwenden wir 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
notwendig
len(encodeURL) == 64
. Wenn random % 64
dies nicht getan wird, randomPos
könnte> = 64 sein und zur Laufzeit eine Panik außerhalb der Grenzen verursachen.
/*
korzhao
*/
package rand
import (
crand "crypto/rand"
"math/rand"
"sync"
"time"
"unsafe"
)
// 不全局共用rand库,减少锁竞争
type Rand struct {
Seed int64
Pool *sync.Pool
}
var (
MRand = NewRand()
randlist = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
)
// 初始化随机数发生器
func NewRand() *Rand {
p := &sync.Pool{New: func() interface{} {
return rand.New(rand.NewSource(getSeed()))
},
}
mrand := &Rand{
Pool: p,
}
return mrand
}
// 获取种子
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)
}
// 获取随机数
func (s *Rand) Intn(n int) int {
r := s.getrand()
defer s.putrand(r)
return r.Intn(n)
}
// 批量获取随机数
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 weist / zu
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 Allokationen / op