@VinceEmigh: Oto meta temat omawiający podstawowe pytania. meta.stackoverflow.com/q/274645/395461 Osobiście uważam, że podstawowe pytania są w porządku, jeśli są dobrze napisane i dotyczą tematu. Spójrz na poniższe odpowiedzi, ilustrują one wiele rzeczy, które byłyby przydatne dla kogoś nowego. W przypadku pętli, rzutowania, make () itp.
Shannon Matthews,
2
@Shannon „ To pytanie nie pokazuje żadnego wysiłku badawczego ” (pierwsza bardzo pozytywna odpowiedź w twoim linku) - o to mi chodziło. Nie wykazuje wysiłku badawczego. Bez żadnego wysiłku (próba, a nawet stwierdzenie, że wyglądał online, czego oczywiście nie zrobił). Mimo, że byłby użyteczny dla kogoś nowego , ta strona nie koncentruje się na nauczaniu nowych ludzi. Koncentruje się na rozwiązywaniu konkretnych problemów / pytań programistycznych, a nie na samouczkach / przewodnikach. Chociaż można go wykorzystać w tym drugim przypadku, nie jest to celem, a zatem to pytanie powinno zostać zamknięte. Zamiast tego jego spoonfed /:
Vince Emigh
9
@VinceEmigh Zadałem to pytanie rok temu. Szukałem w Internecie losowych ciągów i czytałem dokumenty. Ale to nie było pomocne. Jeśli nie napisałem w pytaniu, to nie znaczy, że nie badałem.
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.goi 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.
I. Ulepszenia
1. Geneza (runy)
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))]}returnstring(b)}
2. Bajty
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")
Jest to już duża poprawa: moglibyśmy ją osiągnąć const(są stringstał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 sjest ciągiem ciągłym.)
A jakim kosztem? W ogóle nic. strings 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))]}returnstring(b)}
3. Resztki
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))]}returnstring(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 52jest 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..1z podwójnym prawdopodobieństwem niż z zakresu 2..5. Przy użyciu 5 losowych bitów liczby w zakresie 0..1wystąpiłyby z 6/32prawdopodobieństwem, a liczby w zakresie 2..5z 5/32prawdopodobień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.
4. Maskowanie
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.5ogólnie ( 0.25średnio), co oznacza, że nawet gdyby tak było, powtórzenie tego „rzadkiego” przypadku zmniejsza szansę na znalezienie dobrego numer. Po npowtó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++}}returnstring(b)}
5. Ulepszone maskowanie
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 = 10róż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--}returnstring(b)}
6. Źródło
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/randpakiet, 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/randimplementuje bezpieczny kryptograficznie generator liczb pseudolosowych, więc jest znacznie wolniejszy.
Trzymajmy się math/randpaczki. rand.RandWykorzystuje rand.Sourcejako źródło przypadkowych bitów. rand.Sourceto interfejs, który określa Int63() int64metodę: 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 randpakietu), a rand.Sourcejest 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--}returnstring(b)}
Należy również pamiętać, że to ostatnie rozwiązanie nie wymaga inicjalizacji (nasienie) globalna Randz math/randpakietu jako że nie jest używany (i nasze rand.Sourcejest prawidłowo zainicjowany / zaszczepia).
Jeszcze jedna rzecz do zapamiętania: pakiet dokumentów math/randstanó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, Sourcektó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 Sourcezwracane przez niego prawdopodobieństwo jest szybsze).
7. Wykorzystanie strings.Builder
Wszystkie poprzednie rozwiązania zwracają, stringktórego treść jest najpierw wbudowana w plasterek ( []runew Genesis i []bytekolejnych rozwiązaniach), a następnie przekonwertowana na string. Ta końcowa konwersja musi wykonać kopię zawartości wycinka, ponieważ stringwartoś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.Buildernowy typ, którego możemy użyć do zbudowania treści stringpodobnej do bytes.Buffer. Robi to wewnętrznie za pomocą []byte, a kiedy skończymy, możemy uzyskać końcową stringwartość 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.Buidlerwywoł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).
8. „Naśladowanie” strings.Builderz pakietemunsafe
strings.Builderbuduje ciąg w wewnętrznej []byte, tak samo jak my sami. Zasadniczo więc robienie tego za pomocą a strings.Builderma pewien narzut, jedyne, na co przeszliśmy, strings.Builderto unikanie ostatecznego kopiowania wycinka.
strings.Builderunika 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 stringna powrót, ale wykonaj niebezpieczną konwersję: uzyskaj stringktó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))}
(9. Korzystanie 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 randprzewagę „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 letterIdxBitsbitów i potrzebujemy nliter, więc potrzebujemy n * letterIdxBits / 8.0zaokrą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.Sourceprzeciwień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).
II. Reper
W porządku, czas na analizę porównawczą różnych rozwiązań.
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.Sourcezamiast 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 unsafezamiast 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.
@RobbieV Tak, ponieważ używany rand.Sourcejest współużytkowany . Lepszym obejściem byłoby przekazanie rand.Sourcedo 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.
icza
113
@icza, to jedna z najlepszych odpowiedzi, jakie widziałem od dawna na SO!
@ZanLynx thx za wskazówkę; chociaż deferIMO 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.
Mike Atlas,
1
@RobbieV wygląda na to, że ten kod jest bezpieczny dla wątków / goroutine, ponieważ podstawowe udostępnione źródło jest już LockedSource, które implementuje mutex ( golang.org/src/math/rand/rand.go:259 ).
adityajones,
130
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))]}returnstring(b)}
func main(){
rand.Seed(time.Now().UnixNano())
fmt.Println(randSeq(10))}
Nie zapomnij o rand.Seed (), w przeciwnym razie otrzymasz ten sam ciąg przy pierwszym uruchomieniu ... rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin
2
Dodanie Evana jest poprawne, jednak istnieją inne podobne opcje: rand.Seed(time.Now().Unix())lubrand.Seed(time.Now().UnixNano())
openwonk
7
W przypadku trudnego do odgadnięcia sekretu - hasła, klucza kryptograficznego itp. - nigdy nie używaj math/rand; crypto/randzamiast tego użyj (jak opcja 1 Not_A_Golfer 1).
twotwotwo
1
@EvanLin Czy to nie zgadnie? Jeśli będę musiał uruchomić generator, atakujący może odgadnąć czas, w którym go uruchamiam i przewidzieć to samo, co generuję.
Matej
4
Zauważ, że jeśli wypróbujesz powyższy program z ziarnem, na placu zabaw go zwróci cały czas ten sam wynik. Próbowałem tego na placu zabaw i po jakimś czasie zdałem sobie z tego sprawę. Dla mnie to działało dobrze. Mam nadzieję, że oszczędzi to komuś czas :)
Poza tym: autor, dchest, jest doskonałym programistą i stworzył wiele małych, przydatnych pakietów takich jak ten.
Roshambo,
16
Dwie możliwe opcje (może być oczywiście więcej):
Możesz użyć crypto/randpakietu, 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'scudownie wyjaśnionym rozwiązaniu, oto jego modyfikacja, która używa crypto/randzamiast 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
}
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 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)}
Jeśli chcesz przekazać własne źródło losowości, zmodyfikowanie powyższego w celu zaakceptowania io.Readerzamiast 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.
warto wspomnieć, że Go Playground zawsze zwraca tę samą liczbę losową, więc nie zobaczysz w niej różnych losowych ciągów przy różnych wykonaniach tego kodu
Oto moja droga) Użyj matematyki rand lub crypto rand, jak chcesz.
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]}
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 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
}
Ponieważ len(encodeURL) == 64. Jeśli random % 64nie zostało to zrobione, randomPosmoże być> = 64 i spowodować panikę poza zakresem w czasie wykonywania.
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ć goPrzedmowa :
Powiedziawszy to, nawet jeśli nie potrzebujesz najszybszego rozwiązania, przeczytanie tej odpowiedzi może być ryzykowne i edukacyjne.
I. Ulepszenia
1. Geneza (runy)
Przypominamy, że oryginalne ogólne rozwiązanie, które ulepszamy, to:
2. Bajty
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:
możemy użyć:
Lub nawet lepiej:
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żenielen(letters)
będzie równieżconst
! (Wyrażenielen(s)
jest stałe, jeślis
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:
3. Resztki
Wcześniejsze rozwiązania otrzymują losowy numer do wyznaczenia losowej litery, dzwoniąc do
rand.Intn()
których delegatów, doRand.Intn()
których delegatówRand.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 przezlen(letterBytes)
: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 liter52
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 liczby0..1
z podwójnym prawdopodobieństwem niż z zakresu2..5
. Przy użyciu 5 losowych bitów liczby w zakresie0..1
wystąpiłyby z6/32
prawdopodobieństwem, a liczby w zakresie2..5
z5/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.4. Maskowanie
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 przezrand.Int63()
. Aby utrzymać równy rozkład liter, „akceptujemy” liczbę tylko wtedy, gdy mieści się w zakresie0..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. Pon
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:
5. Ulepszone maskowanie
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:6. Źródło
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 zapewniaRead(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
Wykorzystujerand.Source
jako źródło przypadkowych bitów.rand.Source
to interfejs, który określaInt63() 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 zrand
pakietu), arand.Source
jest dla nas całkowicie wystarczające:Należy również pamiętać, że to ostatnie rozwiązanie nie wymaga inicjalizacji (nasienie) globalna
Rand
zmath/rand
pakietu jako że nie jest używany (i naszerand.Source
jest prawidłowo zainicjowany / zaszczepia).Jeszcze jedna rzecz do zapamiętania: pakiet dokumentów
math/rand
stanów: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 gdyrand.NewSource()
nie oferuje tego (a zatemSource
zwracane przez niego prawdopodobieństwo jest szybsze).7. Wykorzystanie
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 nastring
. 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ścistring
podobnej dobytes.Buffer
. Robi to wewnętrznie za pomocą[]byte
, a kiedy skończymy, możemy uzyskać końcowąstring
wartość za pomocą jejBuilder.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.Zauważ, że po utworzeniu nowego
strings.Buidler
wywołaliśmy jegoBuilder.Grow()
metodę, upewniając się, że alokuje on wystarczająco duży wewnętrzny wycinek (aby uniknąć przesunięć przy dodawaniu losowych liter).8. „Naśladowanie”
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ą astrings.Builder
ma pewien narzut, jedyne, na co przeszliśmy,strings.Builder
to unikanie ostatecznego kopiowania wycinka.strings.Builder
unika końcowej kopii za pomocą pakietuunsafe
: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 gostring
na powrót, ale wykonaj niebezpieczną konwersję: uzyskajstring
który wskazuje na nasz kawałek bajtu jako dane ciągu .Oto jak można to zrobić:
(9. Korzystanie
rand.Read()
)Go 1.7 dodaje się
rand.Read()
funkcję iRand.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 wymagaletterIdxBits
bitów i potrzebujemyn
liter, więc potrzebujemyn * 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 dzwoniSource.Int63()
do momentu wypełnienia przekazanego wycinka. Dokładnie to, coRandStringBytesMaskImprSrc()
robi rozwiązanie, bez pośredniego bufora i bez dodatkowej złożoności. DlategoRandStringBytesMaskImprSrc()
pozostaje na tronie. Tak,RandStringBytesMaskImprSrc()
wrand.Source
przeciwieństwie do niezsynchronizowanegorand.Read()
. Ale rozumowanie wciąż obowiązuje; i co zostanie udowodnione, jeśli użyjemyRand.Read()
zamiastrand.Read()
(ten pierwszy jest również niezsynchronizowany).II. Reper
W porządku, czas na analizę porównawczą różnych rozwiązań.
Moment prawdy:
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życierand.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
zamiastrand.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
zamiaststrings.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.źródło
rand.Source
jest współużytkowany . Lepszym obejściem byłoby przekazanierand.Source
doRandStringBytesMaskImprSrc()
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łasnySource
.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.
źródło
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).Użyj pakietu uniuri , który generuje kryptograficznie bezpieczne jednolite (bezstronne) ciągi.
Oświadczenie: Jestem autorem pakietu
źródło
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.
źródło
Po
icza's
cudownie wyjaśnionym rozwiązaniu, oto jego modyfikacja, która używacrypto/rand
zamiastmath/rand
.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:
Jeśli chcesz przekazać własne źródło losowości, zmodyfikowanie powyższego w celu zaakceptowania
io.Reader
zamiast użycia byłoby banalnecrypto/rand
.źródło
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)
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)
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
źródło
źródło
[]byte
?Oto moja droga) Użyj matematyki rand lub crypto rand, jak chcesz.
źródło
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
.źródło
random % 64
koniecznelen(encodeURL) == 64
. Jeślirandom % 64
nie zostało to zrobione,randomPos
może być> = 64 i spowodować panikę poza zakresem w czasie wykonywania.BenchmarkRandStr16-8 20000000 68,1 ns / op 16 B / op 1 allocs / op
źródło