Chcę tylko losowy ciąg znaków (wielkie lub małe), bez liczb, w Go. Jaki jest najszybszy i najprostszy sposób to zrobić?
Chcę tylko losowy ciąg znaków (wielkie lub małe), bez liczb, w Go. Jaki jest najszybszy i najprostszy sposób to zrobić?
Odpowiedzi:
Rozwiązanie Paula zapewnia proste , ogólne rozwiązanie.
Pytanie dotyczy „najszybszego i najprostszego sposobu” . Zajmijmy się także najszybszą częścią. Nasz ostateczny, najszybszy kod dojdziemy w sposób iteracyjny. Benchmarking każdej iteracji można znaleźć na końcu odpowiedzi.
Wszystkie rozwiązania i kod porównawczy można znaleźć na Go Playground . Kod na placu zabaw to plik testowy, a nie plik wykonywalny. Musisz zapisać go w pliku o nazwie XX_test.go
i uruchomić go
go test -bench . -benchmem
Przedmowa :
Najszybsze rozwiązanie nie jest rozwiązaniem, jeśli potrzebujesz tylko losowego ciągu. Do tego rozwiązanie Paula jest idealne. Dzieje się tak, jeśli wydajność ma znaczenie. Chociaż pierwsze 2 kroki ( Bajty i pozostałe ) mogą być akceptowalnym kompromisem: poprawiają wydajność o około 50% (patrz dokładne liczby w sekcji II. Benchmark ) i nie zwiększają znacząco złożoności.
Powiedziawszy to, nawet jeśli nie potrzebujesz najszybszego rozwiązania, przeczytanie tej odpowiedzi może być ryzykowne i edukacyjne.
Przypominamy, że oryginalne ogólne rozwiązanie, które ulepszamy, to:
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)
}
Jeśli znaki do wyboru i złożenia losowego ciągu zawierają tylko wielkie i małe litery alfabetu angielskiego, możemy pracować z bajtami tylko dlatego, że litery alfabetu angielskiego odwzorowują bajty 1 do 1 w kodowaniu UTF-8 (które to jak Go przechowuje ciągi).
Więc zamiast:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
możemy użyć:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Lub nawet lepiej:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Jest to już duża poprawa: moglibyśmy ją osiągnąć const
(są string
stałe, ale nie ma stałych plasterek ). Jako dodatkowy zysk, wyrażenie len(letters)
będzie również const
! (Wyrażenie len(s)
jest stałe, jeśli s
jest ciągiem ciągłym.)
A jakim kosztem? W ogóle nic. string
s może być indeksowany, co indeksuje jego bajty, idealnie, dokładnie to, czego chcemy.
Nasz następny cel wygląda następująco:
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)
}
Wcześniejsze rozwiązania otrzymują losowy numer do wyznaczenia losowej litery, dzwoniąc do rand.Intn()
których delegatów, do Rand.Intn()
których delegatów Rand.Int31n()
.
Jest to znacznie wolniejsze w porównaniu do tego, rand.Int63()
który wytwarza losową liczbę z 63 losowymi bitami.
Więc możemy po prostu zadzwonić rand.Int63()
i wykorzystać resztę po podzieleniu przez 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)
}
Działa to i jest znacznie szybsze, wadą jest to, że prawdopodobieństwo wszystkich liter nie będzie dokładnie takie samo (zakładając, że rand.Int63()
wszystkie 63-bitowe liczby mają jednakowe prawdopodobieństwo). Chociaż zniekształcenie jest bardzo małe, ponieważ liczba liter 52
jest znacznie mniejsza niż 1<<63 - 1
, więc w praktyce jest to całkowicie w porządku.
Aby ułatwić zrozumienie: powiedzmy, że chcesz losową liczbę z zakresu 0..5
. Przy użyciu 3 losowych bitów uzyskałoby to liczby 0..1
z podwójnym prawdopodobieństwem niż z zakresu 2..5
. Przy użyciu 5 losowych bitów liczby w zakresie 0..1
wystąpiłyby z 6/32
prawdopodobieństwem, a liczby w zakresie 2..5
z 5/32
prawdopodobieństwem, które jest teraz bliższe pożądanemu. Zwiększenie liczby bitów czyni to mniej znaczącym, gdy osiągając 63 bity, jest to nieistotne.
Opierając się na poprzednim rozwiązaniu, możemy utrzymać równy rozkład liter, używając tylko tyle najniższych bitów liczby losowej, ile jest wymaganych do przedstawienia liczby liter. Tak na przykład, jeśli mamy 52 liter, wymaga 6 bitów do jej reprezentowania: 52 = 110100b
. Wykorzystamy więc tylko 6 najniższych bitów z liczby zwróconej przez rand.Int63()
. Aby utrzymać równy rozkład liter, „akceptujemy” liczbę tylko wtedy, gdy mieści się w zakresie 0..len(letterBytes)-1
. Jeśli najniższe bity są większe, odrzucamy je i sprawdzamy nową liczbę losową.
Zauważ, że szansa, że najniższe bity będą większe lub równe, len(letterBytes)
jest mniejsza niż 0.5
ogólnie ( 0.25
średnio), co oznacza, że nawet gdyby tak było, powtórzenie tego „rzadkiego” przypadku zmniejsza szansę na znalezienie dobrego numer. Po n
powtórzeniu szansa, że nie będziemy mieć dobrego indeksu, jest znacznie mniejsza niż pow(0.5, n)
, a to tylko górna ocena. W przypadku 52 liter szansa, że 6 najniższych bitów nie jest dobra, jest tylko (64-52)/64 = 0.19
; co oznacza na przykład, że szanse na brak dobrej liczby po 10 powtórzeniach są 1e-8
.
Oto rozwiązanie:
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)
}
Poprzednie rozwiązanie wykorzystuje tylko najniższe 6 bitów z 63 losowych bitów zwróconych przez rand.Int63()
. Jest to strata, ponieważ uzyskiwanie losowych bitów jest najwolniejszą częścią naszego algorytmu.
Jeśli mamy 52 litery, oznacza to, że 6 bitów koduje indeks liter. Tak więc 63 losowe bity mogą oznaczać 63/6 = 10
różne indeksy literowe. Użyjmy tych wszystkich 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)
}
Maskowanie Lepsza jest całkiem dobry, niewiele możemy poprawić na nim. Moglibyśmy, ale nie było to skomplikowane.
Teraz znajdźmy coś jeszcze do poprawy. Źródło liczb losowych.
Istnieje crypto/rand
pakiet, który zapewnia Read(b []byte)
funkcję, dzięki czemu możemy użyć tej liczby, aby uzyskać tyle bajtów za pomocą jednego wywołania, ile potrzebujemy. Nie pomogłoby to pod względem wydajności, ponieważ crypto/rand
implementuje bezpieczny kryptograficznie generator liczb pseudolosowych, więc jest znacznie wolniejszy.
Trzymajmy się math/rand
paczki. rand.Rand
Wykorzystuje rand.Source
jako źródło przypadkowych bitów. rand.Source
to interfejs, który określa Int63() int64
metodę: dokładnie i jedyną rzeczą, której potrzebowaliśmy i wykorzystaliśmy w naszym najnowszym rozwiązaniu.
Więc tak naprawdę nie potrzebujemy rand.Rand
(jawnego lub globalnego, wspólnego jednego z rand
pakietu), a rand.Source
jest dla nas całkowicie wystarczające:
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)
}
Należy również pamiętać, że to ostatnie rozwiązanie nie wymaga inicjalizacji (nasienie) globalna Rand
z math/rand
pakietu jako że nie jest używany (i nasze rand.Source
jest prawidłowo zainicjowany / zaszczepia).
Jeszcze jedna rzecz do zapamiętania: pakiet dokumentów math/rand
stanów:
Domyślne źródło jest bezpieczne do równoczesnego użycia przez wiele goroutine.
Tak więc domyślne źródło jest wolniejsze niż to, Source
które można uzyskać rand.NewSource()
, ponieważ domyślne źródło musi zapewniać bezpieczeństwo przy równoczesnym dostępie / użytkowaniu, podczas gdy rand.NewSource()
nie oferuje tego (a zatem Source
zwracane przez niego prawdopodobieństwo jest szybsze).
strings.Builder
Wszystkie poprzednie rozwiązania zwracają, string
którego treść jest najpierw wbudowana w plasterek ( []rune
w Genesis i []byte
kolejnych rozwiązaniach), a następnie przekonwertowana na string
. Ta końcowa konwersja musi wykonać kopię zawartości wycinka, ponieważ string
wartości są niezmienne, a jeśli konwersja nie utworzy kopii, nie można zagwarantować, że zawartość ciągu nie zostanie zmodyfikowana za pomocą oryginalnego wycinka. Aby uzyskać szczegółowe informacje, zobacz Jak przekonwertować ciąg utf8 na bajt []? i golang: [] bajt (ciąg) vs [] bajt (* ciąg) .
Wprowadzono wersję 1.10 strings.Builder
. strings.Builder
nowy typ, którego możemy użyć do zbudowania treści string
podobnej do bytes.Buffer
. Robi to wewnętrznie za pomocą []byte
, a kiedy skończymy, możemy uzyskać końcową string
wartość za pomocą jej Builder.String()
metody. Ale fajne jest to, że robi to bez wykonywania kopii, o której mówiliśmy powyżej. Odważy się to zrobić, ponieważ wycinek bajtu użyty do zbudowania zawartości ciągu nie jest odsłonięty, więc jest zagwarantowane, że nikt nie będzie mógł go przypadkowo lub złośliwie zmodyfikować, aby zmienić wytworzony ciąg „niezmienny”.
Więc naszym następnym pomysłem jest, aby nie budować losowego ciągu w wycinku, ale za pomocą a strings.Builder
, więc kiedy skończymy, możemy uzyskać i zwrócić wynik bez konieczności wykonywania jego kopii. Może to pomóc pod względem prędkości, a na pewno pomoże w zakresie wykorzystania pamięci i przydziałów.
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()
}
Zauważ, że po utworzeniu nowego strings.Buidler
wywołaliśmy jego Builder.Grow()
metodę, upewniając się, że alokuje on wystarczająco duży wewnętrzny wycinek (aby uniknąć przesunięć przy dodawaniu losowych liter).
strings.Builder
z pakietemunsafe
strings.Builder
buduje ciąg w wewnętrznej []byte
, tak samo jak my sami. Zasadniczo więc robienie tego za pomocą a strings.Builder
ma pewien narzut, jedyne, na co przeszliśmy, strings.Builder
to unikanie ostatecznego kopiowania wycinka.
strings.Builder
unika końcowej kopii za pomocą pakietu unsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Chodzi o to, że możemy to również zrobić sami. Pomysł polega na tym, aby powrócić do budowania losowego ciągu znaków w []byte
, ale kiedy skończymy, nie przekształcaj go string
na powrót, ale wykonaj niebezpieczną konwersję: uzyskaj string
który wskazuje na nasz kawałek bajtu jako dane ciągu .
Oto jak można to zrobić:
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 dodaje się rand.Read()
funkcję i Rand.Read()
metody. Powinniśmy mieć pokusę, aby użyć ich do odczytania tyle bajtów, ile potrzebujemy w jednym kroku, aby osiągnąć lepszą wydajność.
Jest z tym jeden „problem”: ile bajtów potrzebujemy? Można powiedzieć: tyle ile liter wyjściowych. Uważamy, że jest to górna ocena, ponieważ indeks literowy używa mniej niż 8 bitów (1 bajt). Ale w tym momencie już idziemy gorzej (ponieważ zdobywanie losowych bitów jest „trudną częścią”) i otrzymujemy więcej niż potrzeba.
Zauważ też, że aby utrzymać równy rozkład wszystkich indeksów literowych, mogą istnieć jakieś „śmieciowe” losowe dane, których nie będziemy w stanie wykorzystać, więc skończyłoby się to pomijaniem niektórych danych, a tym samym skończyłoby się to krótko, gdy przejdziemy przez wszystkie kawałek bajtu. Musielibyśmy uzyskać więcej losowych bajtów „rekurencyjnie”. A teraz tracimy nawet rand
przewagę „pojedynczego połączenia do pakietu” ...
Możemy „nieco” zoptymalizować wykorzystanie losowych danych, które pozyskujemy math.Rand()
. Możemy oszacować, ile bajtów (bitów) będziemy potrzebować. Jedna litera wymaga letterIdxBits
bitów i potrzebujemy n
liter, więc potrzebujemy n * letterIdxBits / 8.0
zaokrąglania bajtów w górę. Możemy obliczyć prawdopodobieństwo, że losowy indeks nie będzie użyteczny (patrz wyżej), więc możemy poprosić o więcej, które „bardziej prawdopodobne” będą wystarczające (jeśli okaże się, że tak nie jest, powtarzamy ten proces). Możemy przetworzyć wycinek bajtu na przykład jako „strumień bitów”, dla którego mamy niezłą bibliotekę stron trzecich: github.com/icza/bitio
(ujawnienie: jestem autorem).
Ale kod testu porównawczego wciąż pokazuje, że nie wygrywamy. Dlaczego tak jest
Odpowiedź na ostatnie pytanie brzmi, ponieważ rand.Read()
używa pętli i dzwoni Source.Int63()
do momentu wypełnienia przekazanego wycinka. Dokładnie to, co RandStringBytesMaskImprSrc()
robi rozwiązanie, bez pośredniego bufora i bez dodatkowej złożoności. Dlatego RandStringBytesMaskImprSrc()
pozostaje na tronie. Tak, RandStringBytesMaskImprSrc()
w rand.Source
przeciwieństwie do niezsynchronizowanego rand.Read()
. Ale rozumowanie wciąż obowiązuje; i co zostanie udowodnione, jeśli użyjemy Rand.Read()
zamiast rand.Read()
(ten pierwszy jest również niezsynchronizowany).
W porządku, czas na analizę porównawczą różnych rozwiązań.
Moment prawdy:
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
Po prostu zmieniając runy na bajty, natychmiast uzyskujemy 24% wzrost wydajności, a zapotrzebowanie na pamięć spada do jednej trzeciej .
Pozbycie się rand.Intn()
i użycie rand.Int63()
zamiast tego daje kolejne 20% doładowania.
Maskowanie (i powtarzanie w przypadku dużych indeksów) nieco spowalnia (z powodu wywołań powtórzeń): -22% ...
Ale kiedy wykorzystamy wszystkie (lub większość) z 63 losowych bitów (10 wskaźników z jednego rand.Int63()
połączenia): przyspieszy to duży czas: 3 razy .
Jeśli zdecydujemy się na (niewykonanie, nowe) rand.Source
zamiast rand.Rand
, ponownie zyskujemy 21%.
Jeśli korzystamy strings.Builder
, możemy zyskać niewielką 3,5% w prędkości , ale również osiągnąć 50% redukcję zużycia pamięci i alokacji! To miłe!
Wreszcie, jeśli ośmielimy się użyć pakietu unsafe
zamiast strings.Builder
, ponownie zyskamy niezłe 14% .
Porównanie ostatecznego rozwiązania do pierwotnego: RandStringBytesMaskImprSrcUnsafe()
jest 6,3 razy szybsze niż RandStringRunes()
, wykorzystuje jedną szóstą pamięć i połowę mniej alokacji . Misja zakończona sukcesem.
rand.Source
jest współużytkowany . Lepszym obejściem byłoby przekazanie rand.Source
do RandStringBytesMaskImprSrc()
funkcji, w ten sposób nie jest wymagane blokowanie, a zatem nie ma wpływu na wydajność / wydajność. Każdy goroutine może mieć swój własny Source
.
defer
gdy jest oczywiste, że nie jest to potrzebne. Zobacz grokbase.com/t/gg/golang-nuts/158zz5p42w/…
defer
IMO do odblokowania bezpośrednio przed lub po wywołaniu zamka jest w większości bardzo dobrym pomysłem; gwarantujesz, że zarówno nie zapomnisz odblokować, ale także odblokować nawet w nieśmiercionośnej panicznej środkowej funkcji.
Możesz po prostu napisać dla niego kod. Ten kod może być nieco prostszy, jeśli chcesz polegać na tym, że wszystkie litery są pojedynczymi bajtami, gdy są zakodowane w UTF-8.
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())
lubrand.Seed(time.Now().UnixNano())
math/rand
; crypto/rand
zamiast tego użyj (jak opcja 1 Not_A_Golfer 1).
Dwie możliwe opcje (może być oczywiście więcej):
Możesz użyć crypto/rand
pakietu, który obsługuje odczytywanie tablic bajtów losowych (z / dev / urandom) i jest ukierunkowany na losowe generowanie kryptograficzne. patrz http://golang.org/pkg/crypto/rand/#example_Read . Może to być jednak wolniejsze niż normalne generowanie liczb pseudolosowych.
Weź losową liczbę i hasz ją za pomocą md5 lub czegoś takiego.
Po icza's
cudownie wyjaśnionym rozwiązaniu, oto jego modyfikacja, która używa crypto/rand
zamiast 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
}
Jeśli potrzebujesz bardziej ogólnego rozwiązania, które pozwala przekazać fragment bajtu znaków w celu utworzenia ciągu znaków, możesz spróbować użyć tego:
// 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)
}
Jeśli chcesz przekazać własne źródło losowości, zmodyfikowanie powyższego w celu zaakceptowania io.Reader
zamiast użycia byłoby banalne crypto/rand
.
Jeśli chcesz kryptograficznie zabezpieczonych liczb losowych, a dokładny zestaw znaków jest elastyczny (powiedzmy, base64 jest w porządku), możesz dokładnie obliczyć wymaganą długość losowych znaków z pożądanego rozmiaru wyjściowego.
Podstawowy tekst 64 jest o 1/3 dłuższy niż podstawowy 256. (2 ^ 8 vs 2 ^ 6; stosunek 8 bitów / 6 bitów = 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
}
Uwaga: możesz również użyć RawStdEncoding, jeśli wolisz znaki + i / zamiast - i _
Jeśli chcesz hex, podstawa 16 jest 2x dłuższa niż podstawa 256. (2 ^ 8 vs 2 ^ 4; stosunek 8 bitów / 4 bity = 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
}
Możesz jednak rozszerzyć to na dowolny dowolny zestaw znaków, jeśli masz koder base256 na baseN dla swojego zestawu znaków. Możesz wykonać takie same obliczenia rozmiaru, ile bitów jest potrzebnych do przedstawienia zestawu znaków. Obliczenie współczynnika dla dowolnego dowolnego zestawu znaków wynosi:) ratio = 8 / log2(len(charset))
.
Chociaż oba te rozwiązania są bezpieczne, proste, powinny być szybkie i nie marnuj puli entropii kryptograficznej.
Oto plac zabaw pokazujący, że działa dla każdego rozmiaru. 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
?
Jeśli chcesz dodać kilka znaków do puli dozwolonych znaków, możesz sprawić, że kod będzie działał z dowolną liczbą losowych bajtów za pośrednictwem czytnika io.Reader. Tutaj korzystamy 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
konieczne
len(encodeURL) == 64
. Jeśli random % 64
nie zostało to zrobione, randomPos
może być> = 64 i spowodować panikę poza zakresem w czasie wykonywania.
const (
chars = "0123456789_abcdefghijkl-mnopqrstuvwxyz" //ABCDEFGHIJKLMNOPQRSTUVWXYZ
charsLen = len(chars)
mask = 1<<6 - 1
)
var rng = rand.NewSource(time.Now().UnixNano())
// RandStr 返回指定长度的随机字符串
func RandStr(ln int) string {
/* chars 38个字符
* rng.Int63() 每次产出64bit的随机数,每次我们使用6bit(2^6=64) 可以使用10次
*/
buf := make([]byte, ln)
for idx, cache, remain := ln-1, rng.Int63(), 10; idx >= 0; {
if remain == 0 {
cache, remain = rng.Int63(), 10
}
buf[idx] = chars[int(cache&mask)%charsLen]
cache >>= 6
remain--
idx--
}
return *(*string)(unsafe.Pointer(&buf))
}
BenchmarkRandStr16-8 20000000 68,1 ns / op 16 B / op 1 allocs / op