@VinceEmigh: Hier ist ein Meta-Thema, in dem grundlegende Fragen diskutiert werden. meta.stackoverflow.com/q/274645/395461 Persönlich denke ich, dass grundlegende Fragen in Ordnung sind, wenn sie gut geschrieben und themenbezogen sind. Schauen Sie sich die Antworten unten an, sie veranschaulichen eine Reihe von Dingen, die für jemanden nützlich wären, der neu ist. Für Loops geben Sie Casting, make () usw. ein
Shannon Matthews
2
@Shannon " Diese Frage zeigt keinen Forschungsaufwand " (erste hoch bewertete Antwort in Ihrem Link) - Darauf habe ich mich bezogen. Er zeigt keinen Forschungsaufwand. Überhaupt keine Anstrengung (ein Versuch oder sogar die Aussage, dass er online geschaut hat, was er offensichtlich nicht getan hat). Obwohl es für jemanden, der neu ist , nützlich wäre , konzentriert sich diese Seite nicht darauf, neue Leute zu unterrichten. Es konzentriert sich auf die Beantwortung spezifischer Programmierprobleme / -fragen, nicht auf Tutorials / Anleitungen. Obwohl es für letzteres verwendet werden könnte, ist dies nicht der Schwerpunkt, und daher sollte diese Frage geschlossen werden. Stattdessen ist sein Löffel gefüttert /:
Vince Emigh
9
@VinceEmigh Ich habe diese Frage vor einem Jahr gestellt. Ich hatte online nach zufälligen Zeichenfolgen gesucht und auch Dokumente gelesen. Aber es war nicht hilfreich. Wenn ich nicht in die Frage geschrieben habe, heißt das nicht, dass ich nicht recherchiert habe.
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.gound 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.
I. Verbesserungen
1. Genesis (Runen)
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))]}returnstring(b)}
2. Bytes
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")
Dies ist bereits eine große Verbesserung: Wir könnten es als a erreichen const(es gibt stringKonstanten, aber keine Slice-Konstanten ). Als zusätzlichen Gewinn wird der Ausdruck len(letters)auch ein const! (Der Ausdruck len(s)ist konstant, wenn ses sich um eine Zeichenfolgenkonstante handelt.)
Und zu welchem Preis? Gar nichts. strings 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))]}returnstring(b)}
3. Rest
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))]}returnstring(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 52viel, 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..1mit doppelter Wahrscheinlichkeit als aus dem Bereich erzeugen 2..5. Bei Verwendung von 5 Zufallsbits 0..1würden Zahlen im Bereich mit der 6/32Wahrscheinlichkeit und Zahlen im Bereich 2..5mit der 5/32Wahrscheinlichkeit 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.
4. Maskierung
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.5im Allgemeinen ( 0.25im 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 nWiederholung 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++}}returnstring(b)}
5. Maskierung verbessert
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 = 10unterschiedliche 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--}returnstring(b)}
6. Quelle
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/randPaket, 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/randein kryptografisch sicherer Pseudozufallszahlengenerator implementiert wird, sodass er viel langsamer ist.
Bleiben wir also beim math/randPaket. Das rand.Randverwendet a rand.Sourceals Quelle für zufällige Bits. rand.Sourceist eine Schnittstelle, die eine Int63() int64Methode 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.Sourceist 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--}returnstring(b)}
Beachten Sie auch, dass Sie bei dieser letzten Lösung nicht das Global Randdes math/randPakets initialisieren (Startwert) müssen , da dieser nicht verwendet wird (und unser Paket rand.Sourceordnungsgemäß initialisiert / Startwert ist).
Eine weitere Sache, die Sie hier beachten sollten: Paket doc of math/randstate:
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 Sourcevon ihr zurückgegebene Quelle eher schneller).
7. Verwenden strings.Builder
Alle vorherigen Lösungen geben a zurück, stringdessen Inhalt zuerst in einem Slice ( []runein Genesis und []bytein 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 stringWerte 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.BuilderEin 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 stringWert 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.Buidlerwir 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).
8. "Nachahmen" strings.Buildermit Paketunsafe
strings.Builderbaut den String in einem internen auf []byte, genau wie wir es selbst getan haben. Wenn Sie dies also über a tun, strings.Builderist der Overhead etwas, auf das wir umgestellt haben strings.Builder, um das endgültige Kopieren des Slice zu vermeiden.
strings.Buildervermeidet 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. []byteWenn wir fertig sind, konvertieren Sie sie nicht stringin return, sondern führen Sie eine unsichere Konvertierung durch: Erhalten Sie eine, stringdie 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))}
(9. Verwenden 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 randVorteil "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 letterIdxBitsBits, und wir brauchen nBuchstaben, also brauchen wir n * letterIdxBits / 8.0Bytes, 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.SourceGegensatz 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).
II. Benchmark
In Ordnung, es ist Zeit für ein Benchmarking der verschiedenen Lösungen.
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.Sourcestattdessen 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 unsafeanstelle 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.
@RobbieV Yup, weil ein Shared rand.Sourceverwendet wird. Eine bessere Problemumgehung wäre die Übergabe von a rand.Sourcean 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.
icza
113
@icza, das ist eine der besten Antworten, die ich seit langem auf SO gesehen habe!
@ZanLynx Danke für den Tipp; Obwohl deferes 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.
Mike Atlas
1
@RobbieV Es sieht so aus, als ob dieser Code thread- / goroutine-sicher ist, da die zugrunde liegende gemeinsam genutzte Quelle bereits eine LockedSource ist, die den Mutex implementiert ( golang.org/src/math/rand/rand.go:259 ).
Adityajones
130
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))]}returnstring(b)}
func main(){
rand.Seed(time.Now().UnixNano())
fmt.Println(randSeq(10))}
Vergessen Sie nicht den rand.Seed (), sonst haben Sie beim ersten Start dieselbe Zeichenfolge ... rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin
2
Evans Hinzufügung ist korrekt, es gibt jedoch andere ähnliche Optionen: rand.Seed(time.Now().Unix())oderrand.Seed(time.Now().UnixNano())
openwonk
7
Für ein schwer zu erratendes Geheimnis - ein Passwort, einen Kryptoschlüssel usw. - niemals verwenden math/rand; Verwenden Sie crypto/randstattdessen (wie bei Option 1 von @ Not_A_Golfer).
Zwei
1
@EvanLin Wird das nicht erraten? Wenn ich den Generator säen muss, kann der Angreifer die Zeit erraten, mit der ich ihn säe, und die gleiche Ausgabe vorhersagen, die ich generiere.
Matej
4
Beachten Sie, dass, wenn Sie das oben genannte Programm mit Seed auf dem Spielplatz ausprobieren, immer das gleiche Ergebnis zurückgegeben wird. Ich habe es auf dem Spielplatz ausprobiert und nach einiger Zeit gemerkt. Ansonsten hat es bei mir gut funktioniert. Hoffe, es spart jemandem Zeit :)
Gaurav Sinha
18
Verwenden Sie das Paket uniuri , das kryptografisch sichere einheitliche (unverzerrte) Zeichenfolgen generiert.
Nebenbei: Der Autor, dchest, ist ein ausgezeichneter Entwickler und hat eine Reihe kleiner, nützlicher Pakete wie dieses erstellt.
Roshambo
16
Zwei mögliche Optionen (es könnte natürlich mehr geben):
Sie können das crypto/randPaket 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'swunderbar erklärten Lösung finden Sie hier eine Modifikation, die crypto/randanstelle 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++}}returnstring(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 bytevar bitMask bytefor 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 sliceif idx :=int(randomBytes[j%length]& bitMask); idx < availableCharLength {
result[i]= availableCharBytes[idx]
i++}}returnstring(result)}
Wenn Sie Ihre eigene Zufallsquelle weitergeben möchten, ist es trivial, die obigen Angaben zu ändern, um eine zu akzeptieren, io.Readeranstatt 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.
Erwähnenswert ist, dass Go Playground immer dieselbe Zufallszahl zurückgibt, sodass Sie dort keine unterschiedlichen Zufallszeichenfolgen bei unterschiedlichen Ausführungen dieses Codes sehen
Hier ist mein Weg) Verwenden Sie Math Rand oder Crypto Rand, wie Sie möchten.
func randStr(len int)string{
buff := make([]byte, len)
rand.Read(buff)
str := base64.StdEncoding.EncodeToString(buff)// Base 64 can be longer than lenreturn str[:len]}
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 outputfor 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
}
Weil len(encodeURL) == 64. Wenn random % 64dies nicht getan wird, randomPoskönnte> = 64 sein und zur Laufzeit eine Panik außerhalb der Grenzen verursachen.
0xcaff
-1
/*
korzhao
*/package rand
import(
crand "crypto/rand""math/rand""sync""time""unsafe")// 不全局共用rand库,减少锁竞争
type Randstruct{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))}
Hallo! Willkommen bei StackOverflow. Obwohl Sie ein Code-Snippet hinzugefügt haben, enthält Ihre Antwort keinen Kontext darüber, wie es funktioniert oder warum dies so gemacht wird. Bitte denken Sie auch daran, dass die Frage auf Englisch gestellt wird, sodass Ihre Kommentare auch auf Englisch sein sollten.
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ührenVorwort :
Selbst wenn Sie nicht die schnellste Lösung benötigen, kann das Lesen dieser Antwort abenteuerlich und lehrreich sein.
I. Verbesserungen
1. Genesis (Runen)
Zur Erinnerung: Die ursprüngliche, allgemeine Lösung, die wir verbessern, lautet:
2. Bytes
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:
wir können benutzen:
Oder noch besser:
Dies ist bereits eine große Verbesserung: Wir könnten es als a erreichen
const
(es gibtstring
Konstanten, aber keine Slice-Konstanten ). Als zusätzlichen Gewinn wird der Ausdrucklen(letters)
auch einconst
! (Der Ausdrucklen(s)
ist konstant, wenns
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:
3. Rest
Frühere Lösungen erhalten eine Zufallszahl, um einen zufälligen Buchstaben zu bestimmen, indem sie anrufen, an
rand.Intn()
welche DelegiertenRand.Intn()
welche Delegierten sendenRand.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 durchlen(letterBytes)
: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 Buchstaben52
viel, viel kleiner ist als1<<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 Zahlen0..1
mit doppelter Wahrscheinlichkeit als aus dem Bereich erzeugen2..5
. Bei Verwendung von 5 Zufallsbits0..1
würden Zahlen im Bereich mit der6/32
Wahrscheinlichkeit und Zahlen im Bereich2..5
mit der5/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.4. Maskierung
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 verwendenrand.Int63()
. Und um eine gleichmäßige Verteilung der Buchstaben zu gewährleisten, "akzeptieren" wir die Zahl nur, wenn sie in den Bereich fällt0..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 als0.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 dern
Wiederholung ist die Wahrscheinlichkeit, dass wir noch keinen guten Index haben, viel geringer alspow(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 haben1e-8
.Hier ist also die Lösung:
5. Maskierung verbessert
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:6. Quelle
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 eineRead(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, dacrypto/rand
ein kryptografisch sicherer Pseudozufallszahlengenerator implementiert wird, sodass er viel langsamer ist.Bleiben wir also beim
math/rand
Paket. Dasrand.Rand
verwendet arand.Source
als Quelle für zufällige Bits.rand.Source
ist eine Schnittstelle, die eineInt63() 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 genutztesrand
) Paket, arand.Source
ist perfekt genug für uns:Beachten Sie auch, dass Sie bei dieser letzten Lösung nicht das Global
Rand
desmath/rand
Pakets initialisieren (Startwert) müssen , da dieser nicht verwendet wird (und unser Paketrand.Source
ordnungsgemäß initialisiert / Startwert ist).Eine weitere Sache, die Sie hier beachten sollten: Paket doc of
math/rand
state:Die Standardquelle ist also langsamer als eine
Source
, die von erhalten werden kannrand.NewSource()
, da die Standardquelle Sicherheit bei gleichzeitigem Zugriff / gleichzeitiger Verwendungrand.NewSource()
bieten muss, dies jedoch nicht bietet (und daher ist dieSource
von ihr zurückgegebene Quelle eher schneller).7. Verwenden
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 wirdstring
. Bei dieser endgültigen Konvertierung muss eine Kopie des Slice-Inhalts erstellt werden, da diestring
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 wirstring
ähnliche Inhalte erstellen könnenbytes.Buffer
. Es macht es intern mit a[]byte
, und wenn wir fertig sind, können wir den endgültigenstring
Wert mit seinerBuilder.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.Beachten Sie, dass
strings.Buidler
wir nach dem Erstellen einer neuenBuilder.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).8. "Nachahmen"
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 habenstrings.Builder
, um das endgültige Kopieren des Slice zu vermeiden.strings.Builder
vermeidet die endgültige Kopie durch Verwendung von Paketunsafe
: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 nichtstring
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:
(9. Verwenden
rand.Read()
)Go 1.7 fügte eine
rand.Read()
Funktion und eineRand.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ötigtletterIdxBits
Bits, und wir brauchenn
Buchstaben, also brauchen wirn * 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 dieRandStringBytesMaskImprSrc()
Lösung tut, ohne den Zwischenpuffer und ohne die zusätzliche Komplexität. DeshalbRandStringBytesMaskImprSrc()
bleibt auf dem Thron. Ja,RandStringBytesMaskImprSrc()
verwendet imrand.Source
Gegensatz zu nicht synchronisiertrand.Read()
. Aber die Argumentation gilt immer noch; und was bewiesen ist, wenn wirRand.Read()
statt verwendenrand.Read()
(ersteres ist auch nicht synchronisiert).II. Benchmark
In Ordnung, es ist Zeit für ein Benchmarking der verschiedenen Lösungen.
Moment der Wahrheit:
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 Sierand.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 gebenrand.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 verwendenstrings.Builder
, erhalten wir wieder schöne 14% .Vergleich der endgültigen mit der ursprünglichen Lösung:
RandStringBytesMaskImprSrcUnsafe()
ist 6,3-mal schneller alsRandStringRunes()
, verwendet einen sechsten Speicher und halb so wenige Zuordnungen . Mission erfüllt.quelle
rand.Source
verwendet wird. Eine bessere Problemumgehung wäre die Übergabe von arand.Source
an dieRandStringBytesMaskImprSrc()
Funktion. Auf diese Weise ist keine Verriegelung erforderlich, und daher wird die Leistung / Effizienz nicht beeinträchtigt. Jede Goroutine könnte ihre eigene habenSource
.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.
quelle
rand.Seed(time.Now().Unix())
oderrand.Seed(time.Now().UnixNano())
math/rand
; Verwenden Siecrypto/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
quelle
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.
quelle
Nach einer
icza's
wunderbar erklärten Lösung finden Sie hier eine Modifikation, diecrypto/rand
anstelle von verwendet wirdmath/rand
.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:
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 verwendencrypto/rand
.quelle
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)
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)
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
quelle
quelle
[]byte
?Hier ist mein Weg) Verwenden Sie Math Rand oder Crypto Rand, wie Sie möchten.
quelle
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
.quelle
random % 64
notwendiglen(encodeURL) == 64
. Wennrandom % 64
dies nicht getan wird,randomPos
könnte> = 64 sein und zur Laufzeit eine Panik außerhalb der Grenzen verursachen.24,0 ns / op 16 B / op 1 weist / zu
quelle
BenchmarkRandStr16-8 20000000 68,1 ns / op 16 B / op 1 Allokationen / op
quelle