it-swarm.com.de

Wie generiere ich einen zufälligen String mit fester Länge in Go?

Ich möchte nur eine beliebige Zeichenfolge (Groß- oder Kleinschreibung), keine Zahlen in Go Was ist der schnellste und einfachste Weg, dies zu tun?

218
Anish Shah

Die Lösung von Paulus liefert eine einfache allgemeine Lösung.

Die Frage fragt nach dem "der schnellste und einfachste Weg". Lassen Sie uns auch den Teil schnellste ansprechen. Wir werden iterativ zu unserem letzten, schnellsten Code gelangen. Das Benchmarking jeder Iteration finden Sie am Ende der Antwort.

Alle Lösungen und den 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 XX_test.go Speichern und mit ausführen

go test -bench . -benchmem

Vorwort:

Die schnellste Lösung ist keine Sofortlösung, wenn Sie nur eine zufällige Zeichenfolge benötigen. Dafür ist Pauls Lösung perfekt. Dies ist der Fall, wenn Leistung eine Rolle spielt. Obwohl die ersten beiden Schritte (Bytes und Rest) möglicherweise einen akzeptablen Kompromiss darstellen: Sie verbessern die Leistung um etwa 50% (siehe genaue Zahlen in II. Benchmark Abschnitt), und sie erhöhen die Komplexität nicht wesentlich.

Trotzdem kann das Lesen dieser Antwort abenteuerlich und lehrreich sein, auch wenn Sie nicht die schnellste Lösung benötigen.

I. Verbesserungen

1. Genesis (Runen)

Zur Erinnerung, die ursprüngliche, allgemeine Lösung, die wir verbessern, ist folgende:

func init() {
    Rand.Seed(time.Now().UnixNano())
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func RandStringRunes(n int) string {
    b := make([]rune, n)
    for i := range b {
        b[i] = letterRunes[Rand.Intn(len(letterRunes))]
    }
    return string(b)
}

2. Bytes

Wenn die Zeichen, aus denen ausgewählt und die Zufallszeichenfolge zusammengestellt werden soll, nur die Groß- und Kleinbuchstaben des englischen Alphabets enthalten, können wir nur mit Bytes arbeiten, da die englischen Alphabetbuchstaben den Bytes 1-zu-1 in der UTF-8-Codierung (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"

Das ist jetzt schon eine große Verbesserung: Wir könnten erreichen, dass es const gibt (es gibt string Konstanten, aber es gibt keine Slice-Konstanten ). Als zusätzliche Verstärkung ist der Ausdruck len(letters) auch ein const! (Der Ausdruck len(s) ist konstant, wenn s eine Zeichenfolgenkonstante ist.)

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))]
    }
    return string(b)
}

3. Rest

Frühere Lösungen erhalten eine Zufallszahl, um einen zufälligen Buchstaben zu bestimmen, indem sie Rand.Intn() aufrufen, der an Rand.Intn() delegiert, der delegiert bis Rand.Int31n() .

Dies ist viel langsamer als Rand.Int63() , das eine Zufallszahl mit 63 Zufallsbits erzeugt.

Also können wir einfach Rand.Int63() aufrufen und den Rest nach Division durch len(letterBytes) verwenden:

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 nicht für alle Buchstaben gleich ist (vorausgesetzt, Rand.Int63() erzeugt alle 63-Bit-Zahlen mit gleicher Wahrscheinlichkeit). Obwohl die Verzerrung extrem gering ist, da die Anzahl der Buchstaben 52 Sehr viel geringer ist als 1<<63 - 1, Ist dies in der Praxis vollkommen in Ordnung.

Zum leichteren Verständnis: Nehmen wir an, Sie möchten eine Zufallszahl im Bereich von 0..5. Unter Verwendung von 3 Zufallsbits würde dies die Zahlen 0..1 Mit doppelter Wahrscheinlichkeit als aus dem Bereich 2..5 Erzeugen. Bei Verwendung von 5 Zufallsbits würden Zahlen im Bereich 0..1 Mit einer Wahrscheinlichkeit von 6/32 Und Zahlen im Bereich 2..5 Mit einer Wahrscheinlichkeit von 5/32 Auftreten, die jetzt näher an der gewünschten Wahrscheinlichkeit liegt. Durch Erhöhen der Anzahl der Bits wird diese weniger signifikant, wenn 63 Bits erreicht werden, ist sie vernachlässigbar.

4. Maskierung

Aufbauend auf der vorherigen Lösung können wir die gleiche Verteilung von Buchstaben beibehalten, indem wir nur so viele der niedrigsten Bits der Zufallszahl verwenden, wie für die Darstellung der Buchstabenanzahl erforderlich sind. Wenn wir zum Beispiel 52 Buchstaben haben, werden 6 Bits benötigt, um dies darzustellen: 52 = 110100b. Wir werden also nur die niedrigsten 6 Bits der von Rand.Int63() zurückgegebenen Zahl verwenden. Um eine gleichmäßige Verteilung der Buchstaben zu gewährleisten, "akzeptieren" wir die Zahl nur, wenn sie in den Bereich 0..len(letterBytes)-1 fällt. 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 len(letterBytes) sind, im Allgemeinen geringer als 0.5 Ist (0.25 Im Durchschnitt), was bedeutet, dass dies auch dann der Fall ist Wenn Sie diesen "seltenen" Fall wiederholen, wird die Wahrscheinlichkeit verringert, dass keine gute Zahl gefunden wird. Nach n Wiederholungen 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; Dies bedeutet zum Beispiel, dass die Wahrscheinlichkeit, nach 10 Wiederholungen keine gute Zahl zu haben, 1e-8 ist.

Also hier ist 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)
}

5. Maskierung verbessert

Die vorherige Lösung verwendet nur die niedrigsten 6 Bits der 63 Zufallsbits, die von Rand.Int63() zurückgegeben wurden. Dies ist eine Verschwendung, da das Erhalten der zufälligen Bits der langsamste Teil unseres Algorithmus ist.

Wenn wir 52 Buchstaben haben, bedeutet dies, dass 6 Bits einen Buchstabenindex codieren. So können 63 zufällige Bits 63/6 = 10 Verschiedene Buchstabenindizes bezeichnen. Lass uns all diese 10 benutzen:

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. Quelle

Das Masking Improved ist ziemlich gut, wir können nicht viel daran verbessern. Wir könnten, aber die Komplexität nicht wert.

Jetzt wollen wir etwas anderes finden, um es zu verbessern. Die Quelle von Zufallszahlen.

Es gibt ein crypto/Rand - Paket, das eine Read(b []byte) - Funktion enthält, mit der wir so viele abrufen können Bytes mit einem einzigen Aufruf, so viele wir brauchen. Dies würde in Bezug auf die Leistung nicht helfen, da crypto/Rand Einen kryptografisch sicheren Pseudozufallszahlengenerator implementiert, der also viel langsamer ist.

Bleiben wir also beim Paket math/Rand. Der Rand.Rand Verwendet einen Rand.Source als Quelle für zufällige Bits. Rand.Source Ist eine Schnittstelle, die eine Int63() int64 -Methode angibt: genau und das einzige, was wir in unserer neuesten Lösung brauchten und verwendeten.

Wir brauchen also nicht wirklich einen Rand.Rand (Entweder einen expliziten oder einen globalen, gemeinsam genutzten Code aus dem Rand -Paket), ein Rand.Source Reicht uns vollkommen aus:

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 das globale Rand des Pakets math/Rand Nicht initialisieren (seed) müssen, da dieses nicht verwendet wird (und unser Rand.Source Ordnungsgemäß initialisiert ist/ausgesät).

Noch etwas zu beachten: Paket doc von math/Rand Lautet:

Die Standardquelle ist für die gleichzeitige Verwendung durch mehrere Goroutinen sicher.

Die Standardquelle ist also langsamer als ein Source, das von Rand.NewSource() abgerufen werden kann, da die Standardquelle Sicherheit bei gleichzeitigem Zugriff/gleichzeitiger Verwendung bieten muss, während Rand.NewSource() dies nicht bietet dies (und damit das von ihm zurückgegebene Source ist wahrscheinlicher schneller).

7. Verwenden von strings.Builder

Alle vorherigen Lösungen geben ein string zurück, dessen Inhalt zuerst in einem Slice erstellt wird ([]rune In Genesis und []byte In nachfolgenden Lösungen), und dann konvertiert in string. Bei dieser endgültigen Konvertierung muss eine Kopie des Slice-Inhalts erstellt werden, da string Werte unveränderlich sind. Wenn bei der Konvertierung keine Kopie erstellt wird, kann nicht garantiert werden, dass der Inhalt des Strings nicht über das ursprüngliche Slice geändert wird . Weitere Informationen finden Sie unter Konvertieren von utf8-Zeichenfolgen in [] Bytes und golang: [] Bytes (Zeichenfolgen) vs [] Bytes (* Zeichenfolgen) ).

Mit Go 1.10 wurde strings.Builder.strings.Builder ein neuer Typ eingeführt, mit dem wir den Inhalt eines string ähnlich bytes.Buffer . Es macht es intern mit einem []byte Und wenn wir fertig sind, können wir den endgültigen string Wert mit seinem Builder.String() erhalten Methode. Aber das Coole daran ist, dass es dies tut, ohne die Kopie auszuführen, über die wir gerade gesprochen haben. Dies ist zu wagen, da das zum Erstellen des Strings verwendete Byte-Slice nicht verfügbar ist. Es wird daher garantiert, dass niemand es unbeabsichtigt oder in böswilliger Absicht ändern kann, um den erstellten "unveränderlichen" String zu ändern.

Unsere nächste Idee ist es, die zufällige Zeichenfolge nicht in einem Slice zu erstellen, sondern mit Hilfe von strings.Builder. Sobald wir fertig sind, können wir das Ergebnis abrufen und zurückgeben, ohne eine Kopie davon anfertigen zu müssen . Dies kann in Bezug auf die Geschwindigkeit hilfreich sein, und es wird definitiv in Bezug auf die Speichernutzung und -zuweisung hilfreich sein.

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 wir nach dem Erstellen eines neuen strings.Buidler Die Methode Builder.Grow() aufgerufen haben, um sicherzustellen, dass ein ausreichend großes internes Segment zugewiesen wird (um Neuzuweisungen zu vermeiden) wir addieren die zufälligen Buchstaben).

8. "Nachahmen" strings.Builder Mit Paket unsafe

strings.Builder Erstellt den String in einem internen []byte, Genauso wie wir es selbst gemacht haben. Das Ausführen über strings.Builder Ist also mit einem gewissen Aufwand verbunden. Wir haben nur auf strings.Builder Umgestellt, um das endgültige Kopieren des Slice zu vermeiden.

strings.Builder Vermeidet die endgültige Kopie mit package unsafe :

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

Die Sache ist, wir können dies auch selbst tun. Die Idee hier ist also, zurück zum Erstellen der Zufallszeichenfolge in einem []byte Zu wechseln, aber wenn wir fertig sind, konvertieren Sie sie nicht in string, um zurückzukehren, sondern führen Sie eine unsichere Konvertierung durch: erhalten Sie ein string, das auf unsere Byte-Scheibe als die Zeichenfolgendaten zeigt.

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. Mit Rand.Read())

Go 1.7 hat eine Rand.Read() - Funktion und eine Rand.Read() - Methode hinzugefügt . Wir sollten versucht sein, diese zu verwenden, um so viele Bytes wie nötig in einem Schritt zu lesen, um eine bessere Leistung zu erzielen.

Es gibt ein kleines "Problem" dabei: Wie viele Bytes brauchen wir? Wir könnten sagen: so viele wie die Anzahl der ausgegebenen Buchstaben. Wir würden annehmen, dass dies eine obere Schätzung ist, da ein Buchstabenindex weniger als 8 Bits (1 Byte) verwendet. Aber zu diesem Zeitpunkt geht es uns schon schlechter (da das Erhalten der Zufallsbits der "harte Teil" ist) und wir bekommen mehr als nötig.

Beachten Sie auch, dass es zur Aufrechterhaltung einer gleichmäßigen Verteilung aller Buchstabenindizes einige zufällige "Müll" -Daten geben kann, die wir nicht verwenden können, sodass wir am Ende einige Daten überspringen und somit am Ende nicht mehr weiterkommen, wenn wir alle durcharbeiten die Byte-Scheibe. Wir müssten "rekursiv" mehr zufällige Bytes erhalten. Und jetzt verlieren wir sogar den Vorteil des "einzelnen Aufrufs von Rand -Paket" ...

Wir könnten die Verwendung der Zufallsdaten, die wir aus math.Rand() erhalten, "etwas" optimieren. Wir können schätzen, wie viele Bytes (Bits) wir benötigen. Für einen Buchstaben sind letterIdxBits Bits erforderlich, und wir benötigen n Buchstaben, sodass wir n * letterIdxBits / 8.0 Bytes aufrunden müssen. Wir können die Wahrscheinlichkeit berechnen, dass ein zufälliger Index nicht verwendbar ist (siehe oben), damit wir mehr anfordern können, das "wahrscheinlicher" ist (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) .

Der Benchmark-Code zeigt jedoch, dass wir nicht gewinnen. Wieso ist es so?

Die Antwort auf die letzte Frage lautet, dass Rand.Read() eine Schleife verwendet und so lange Source.Int63() aufruft, bis das übergebene Slice gefüllt ist. Genau das, was die RandStringBytesMaskImprSrc() - Lösung macht, ohne den Zwischenpuffer und ohne die zusätzliche Komplexität. Deshalb bleibt RandStringBytesMaskImprSrc() auf dem Thron. Ja, RandStringBytesMaskImprSrc() verwendet im Gegensatz zu Rand.Read() einen nicht synchronisierten Rand.Source. Aber die Argumentation gilt immer noch; und das ist bewiesen, wenn wir Rand.Read() anstelle von Rand.Read() verwenden (ersteres ist ebenfalls unsynchronisiert).

II. Benchmark

Nun gut, 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 haben wir sofort einen Leistungszuwachs von 24% und der Speicherbedarf sinkt auf ein Drittel.

Wenn Sie Rand.Intn() loswerden und stattdessen Rand.Int63() verwenden, erhalten Sie einen weiteren 20% Schub.

Das Maskieren (und das Wiederholen bei großen Indizes) verlangsamt sich etwas (aufgrund von Wiederholungsaufrufen): - 22% ...

Aber wenn wir alle (oder die meisten) der 63 Zufallsbits (10 Indizes aus einem Rand.Int63() - Aufruf) verwenden, beschleunigt sich das erheblich: -mal.

Wenn wir uns mit einem (nicht standardmäßigen, neuen) Rand.Source Anstelle von Rand.Rand Begnügen, erhalten wir erneut 21%.

Wenn wir strings.Builder Verwenden, gewinnen wir ein winziges ,5% in Geschwindigkeit, aber wir haben auch 50% eine Verringerung von erreicht Speicherbelegung und Zuordnungen! Das ist schön!

Wenn wir es schließlich wagen, das Paket unsafe anstelle von strings.Builder Zu verwenden, erhalten wir wieder ein Nizza 14%.

Beim Vergleich der endgültigen mit der ursprünglichen Lösung: RandStringBytesMaskImprSrcUnsafe() ist 6,3-mal schneller als RandStringRunes(), verwendet ein Sechstel Speicher und die Hälfte wie wenige Zuordnungen. Mission erfüllt.

662
icza

Sie können einfach Code dafür schreiben. Dieser Code kann etwas einfacher sein, wenn Sie sich darauf verlassen wollen, dass die Buchstaben bei der Kodierung in UTF-8 aus Einzelbytes bestehen.

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))
}
99
Paul Hankin

Zwei mögliche Optionen (es gibt natürlich noch mehr):

  1. Sie können das crypto/Rand-Paket verwenden, das das Lesen von zufälligen Byte-Arrays (aus/dev/urandom) unterstützt und auf die kryptographische Zufallsgenerierung ausgerichtet ist. siehe http://golang.org/pkg/crypto/Rand/#example_Read . Es kann jedoch langsamer als die normale Erzeugung von Pseudo-Zufallszahlen sein. 

  2. Nimm eine Zufallszahl und hash sie mit md5 oder so ähnlich.

15
Not_a_Golfer

Verwenden Sie das Paket uniuri , das kryptografisch sichere, einheitliche (unverzerrte) Zeichenfolgen generiert.

9
dchest

Nach icza's wunderbar erklärter Lösung, hier ist eine Modifikation davon, die crypto/Rand anstelle von math/Rand verwendet.

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 generischere Lösung wünschen, mit der Sie die Zeichenbytes übergeben können, aus der die Zeichenfolge erstellt werden soll, können Sie Folgendes versuchen:

// 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 übergeben möchten, wäre es trivial, die obigen Angaben so zu ändern, dass io.Reader akzeptiert wird, anstatt crypto/Rand zu verwenden.

3
Chris

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 len
    return str[:len]
}
1
Dima
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 Zuweisungen/Op

0
user10987909

Wenn Sie kryptografisch sichere Zufallszahlen wünschen und der genaue Zeichensatz flexibel ist (beispielsweise ist base64 in Ordnung), können Sie anhand der gewünschten Ausgabegröße genau berechnen, wie viele zufällige Zeichen Sie benötigen.

Basis 64-Text ist 1/3 länger als Basis 256. (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 auch RawStdEncoding verwenden, wenn Sie + und/Zeichen vor - und _ bevorzugen.

Wenn Sie hex möchten, ist die Basis 16 2x länger als die Basis 256. (2 ^ 8 vs 2 ^ 4; 8 Bit/4 Bit = 2x Verhältnis)

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 jeden beliebigen Zeichensatz ausweiten, 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 für die Darstellung Ihres Zeichensatzes erforderlich sind. Die Verhältnisberechnung für einen beliebigen Zeichensatz lautet: ratio = 8 / log2(len(charset))).

Beide Lösungen sind zwar sicher, aber einfach, sollten schnell sein und Ihren Krypto-Entropie-Pool nicht verschwenden.

Hier ist der Spielplatz, auf dem gezeigt wird, dass er für jede Größe geeignet ist. https://play.golang.org/p/i61WUVR8_3Z

0
Steven Soroka