Saya ingin string acak hanya karakter (huruf besar atau kecil), tanpa angka, di Go. Apa cara tercepat dan termudah untuk melakukan ini?
Saya ingin string acak hanya karakter (huruf besar atau kecil), tanpa angka, di Go. Apa cara tercepat dan termudah untuk melakukan ini?
Jawaban:
Solusi Paul menyediakan solusi sederhana dan umum.
Pertanyaannya menanyakan "cara tercepat dan termudah" . Mari kita membahas bagian tercepat juga. Kami akan tiba di kode final kami yang tercepat dengan cara berulang-ulang. Benchmarking setiap iterasi dapat ditemukan di akhir jawaban.
Semua solusi dan kode pembandingan dapat ditemukan di Go Playground . Kode di Playground adalah file tes, bukan yang dapat dieksekusi. Anda harus menyimpannya ke dalam file bernama XX_test.go
dan menjalankannya
go test -bench . -benchmem
Kata Pengantar :
Solusi tercepat bukanlah solusi masuk jika Anda hanya membutuhkan string acak. Untuk itu, solusi Paul sangat sempurna. Ini jika kinerja memang penting. Meskipun 2 langkah pertama ( Bytes dan Remainder ) mungkin merupakan kompromi yang dapat diterima: mereka memang meningkatkan kinerja sebesar 50% (lihat angka pasti di bagian II. Benchmark ), dan mereka tidak meningkatkan kompleksitas secara signifikan.
Karena itu, bahkan jika Anda tidak membutuhkan solusi tercepat, membaca jawaban ini mungkin penuh petualangan dan mendidik.
Sebagai pengingat, solusi umum asli yang kami tingkatkan adalah ini:
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)
}
Jika karakter untuk memilih dari dan merangkai string acak hanya berisi huruf besar dan kecil dari alfabet bahasa Inggris, kita dapat bekerja dengan byte hanya karena huruf alfabet Inggris memetakan ke byte 1-ke-1 dalam pengkodean UTF-8 (yang adalah cara Go menyimpan string).
Jadi alih-alih:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
kita bisa menggunakan:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Atau lebih baik lagi:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Sekarang ini sudah merupakan perbaikan besar: kita bisa mencapainya menjadi const
(ada string
konstanta tetapi tidak ada konstanta slice ). Sebagai keuntungan tambahan, ekspresi len(letters)
juga akan menjadi const
! (Ekspresi len(s)
konstan jika s
string konstan.)
Dan berapa biayanya? Tidak ada sama sekali. string
s dapat diindeks yang mengindeks byte-nya, sempurna, persis seperti yang kita inginkan.
Tujuan kami selanjutnya terlihat seperti ini:
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)
}
Solusi sebelumnya mendapatkan nomor acak untuk menunjuk surat acak dengan memanggil rand.Intn()
delegasi Rand.Intn()
mana yang akan didelegasikan Rand.Int31n()
.
Ini jauh lebih lambat dibandingkan dengan rand.Int63()
yang menghasilkan angka acak dengan 63 bit acak.
Jadi kita bisa memanggil rand.Int63()
dan menggunakan sisanya setelah membaginya dengan 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)
}
Ini bekerja dan secara signifikan lebih cepat, kerugiannya adalah bahwa probabilitas semua huruf tidak akan persis sama (dengan asumsi rand.Int63()
menghasilkan semua angka 63-bit dengan probabilitas yang sama). Meskipun distorsi sangat kecil karena jumlah huruf 52
jauh lebih kecil daripada 1<<63 - 1
, jadi dalam praktiknya ini baik-baik saja.
Untuk membuat ini lebih mudah dipahami: katakanlah Anda ingin nomor acak dalam kisaran 0..5
. Menggunakan 3 bit acak, ini akan menghasilkan angka 0..1
dengan probabilitas ganda daripada dari kisaran 2..5
. Dengan menggunakan 5 bit acak, angka dalam kisaran 0..1
akan muncul dengan 6/32
probabilitas dan angka dalam kisaran 2..5
dengan 5/32
probabilitas yang sekarang lebih dekat ke yang diinginkan. Meningkatkan jumlah bit membuat ini kurang signifikan, ketika mencapai 63 bit, dapat diabaikan.
Membangun pada solusi sebelumnya, kita dapat mempertahankan distribusi huruf yang sama dengan hanya menggunakan banyak bit terendah dari angka acak sebanyak yang diperlukan untuk mewakili jumlah huruf. Jadi misalnya jika kita memiliki 52 huruf, membutuhkan 6 bit untuk mewakilinya: 52 = 110100b
. Jadi kita hanya akan menggunakan 6 bit terendah dari angka yang dikembalikan oleh rand.Int63()
. Dan untuk menjaga pemerataan surat, kami hanya "menerima" nomor itu jika berada dalam kisaran 0..len(letterBytes)-1
. Jika bit terendah lebih besar, kami membuangnya dan meminta nomor acak baru.
Perhatikan bahwa peluang bit terendah untuk lebih besar dari atau sama dengan len(letterBytes)
kurang dari 0.5
pada umumnya ( 0.25
rata-rata), yang berarti bahwa bahkan jika ini yang terjadi, mengulangi kasus "langka" ini mengurangi kemungkinan tidak menemukan barang yang baik. jumlah. Setelah n
pengulangan, kemungkinan kita masih tidak memiliki indeks yang baik adalah jauh lebih sedikit daripada pow(0.5, n)
, dan ini hanya perkiraan yang lebih tinggi. Dalam hal 52 huruf kemungkinan 6 bit terendah tidak bagus hanya (64-52)/64 = 0.19
; yang artinya misalnya peluang untuk tidak memiliki angka yang baik setelah 10 kali pengulangan 1e-8
.
Jadi, inilah solusinya:
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)
}
Solusi sebelumnya hanya menggunakan 6 bit terendah dari 63 bit acak yang dikembalikan oleh rand.Int63()
. Ini adalah pemborosan karena mendapatkan bit acak adalah bagian paling lambat dari algoritma kami.
Jika kita memiliki 52 huruf, itu berarti 6 bit kode indeks huruf. Jadi 63 bit acak dapat menunjuk 63/6 = 10
indeks huruf yang berbeda. Mari kita gunakan semua 10 itu:
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)
}
The Masking Peningkatan ini cukup bagus, tidak banyak yang bisa kita memperbaiki itu. Kita bisa, tetapi tidak sebanding dengan kerumitannya.
Sekarang mari kita cari hal lain untuk diperbaiki. Sumber angka acak.
Ada crypto/rand
paket yang menyediakan Read(b []byte)
fungsi, jadi kita bisa menggunakannya untuk mendapatkan banyak byte dengan satu panggilan sebanyak yang kita butuhkan. Ini tidak akan membantu dalam hal kinerja karena crypto/rand
mengimplementasikan generator nomor pseudorandom yang aman secara kriptografis sehingga jauh lebih lambat.
Jadi mari kita tetap berpegang pada math/rand
paket. The rand.Rand
menggunakan rand.Source
sebagai sumber bit acak. rand.Source
adalah antarmuka yang menentukan Int63() int64
metode: tepat dan satu-satunya hal yang kami butuhkan dan gunakan dalam solusi terbaru kami.
Jadi kita tidak benar-benar membutuhkan rand.Rand
(baik eksplisit atau global, berbagi salah satu rand
paket), a rand.Source
sudah cukup sempurna bagi kita:
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)
}
Juga catatan bahwa solusi terakhir ini tidak mengharuskan Anda untuk menginisialisasi (benih) global Rand
dari math/rand
paket sebagai yang tidak digunakan (dan kami rand.Source
benar diinisialisasi / unggulan).
Satu hal lagi yang perlu diperhatikan di sini: paket dokumen math/rand
negara:
Sumber default aman untuk digunakan bersamaan oleh beberapa goroutine.
Jadi sumber default lebih lambat dari Source
yang dapat diperoleh rand.NewSource()
, karena sumber default harus memberikan keamanan di bawah akses / penggunaan bersamaan, sementara rand.NewSource()
tidak menawarkan ini (dan dengan demikian Source
dikembalikan oleh lebih mungkin lebih cepat).
strings.Builder
Semua solusi sebelumnya menghasilkan string
konten yang pertama kali dibuat dalam slice ( []rune
dalam Genesis , dan []byte
dalam solusi berikutnya), dan kemudian dikonversi menjadi string
. Konversi akhir ini harus membuat salinan konten slice, karena string
nilainya tidak berubah, dan jika konversi tidak akan membuat salinan, tidak dapat dijamin bahwa konten string tidak dimodifikasi melalui slice aslinya. Untuk detailnya, lihat Bagaimana mengonversi utf8 string ke [] byte? dan golang: [] byte (string) vs [] byte (* string) .
Go 1.10 diperkenalkan strings.Builder
. strings.Builder
tipe baru yang bisa kita gunakan untuk membangun konten yang string
mirip dengan bytes.Buffer
. Itu melakukannya secara internal menggunakan []byte
, dan ketika kita selesai, kita bisa mendapatkan nilai akhir string
menggunakan ituBuilder.String()
metodenya. Tapi apa yang keren di dalamnya adalah ia melakukan ini tanpa melakukan salinan yang baru saja kita bicarakan di atas. Ia berani melakukannya karena irisan byte yang digunakan untuk membangun konten string tidak diekspos, sehingga dijamin tidak ada yang dapat memodifikasinya secara tidak sengaja atau jahat untuk mengubah string "immutable" yang dihasilkan.
Jadi ide kami berikutnya adalah untuk tidak membangun string acak dalam sebuah irisan, tetapi dengan bantuan a strings.Builder
, jadi setelah kami selesai, kami dapat memperoleh dan mengembalikan hasilnya tanpa harus membuat salinannya. Ini mungkin membantu dalam hal kecepatan, dan pasti akan membantu dalam hal penggunaan dan alokasi memori.
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()
}
Perhatikan bahwa setelah membuat yang baru strings.Buidler
, kami memanggil Builder.Grow()
metodenya, memastikannya mengalokasikan irisan internal yang cukup besar (untuk menghindari realokasi saat kami menambahkan huruf acak).
strings.Builder
dengan paketunsafe
strings.Builder
membangun string di internal []byte
, sama seperti yang kita lakukan sendiri. Jadi pada dasarnya melakukannya melalui strings.Builder
beberapa overhead, satu-satunya hal yang kita beralih strings.Builder
adalah untuk menghindari penyalinan akhir dari slice.
strings.Builder
menghindari salinan akhir dengan menggunakan paket unsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Masalahnya, kita juga bisa melakukan ini sendiri. Jadi idenya di sini adalah untuk kembali ke membangun string acak dalam []byte
, tetapi ketika kita selesai, jangan mengubahnya string
untuk kembali, tetapi lakukan konversi yang tidak aman: dapatkan string
yang menunjuk ke byte slice kami sebagai data string .
Ini adalah bagaimana hal itu dapat dilakukan:
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 menambahkan suatu rand.Read()
fungsi danRand.Read()
metode. Kita harus tergoda untuk menggunakan ini untuk membaca byte sebanyak yang kita butuhkan dalam satu langkah, untuk mencapai kinerja yang lebih baik.
Ada satu "masalah" kecil dengan ini: berapa banyak byte yang kita butuhkan? Kita bisa mengatakan: sebanyak jumlah surat keluaran. Kami akan berpikir ini adalah estimasi atas, karena indeks huruf menggunakan kurang dari 8 bit (1 byte). Tetapi pada titik ini kita sudah melakukan yang lebih buruk (karena mendapatkan bit acak adalah "bagian yang sulit"), dan kita mendapatkan lebih dari yang dibutuhkan.
Juga perhatikan bahwa untuk menjaga distribusi yang sama dari semua indeks huruf, mungkin ada beberapa data acak "sampah" yang tidak dapat kami gunakan, jadi kami akan melewatkan beberapa data, dan dengan demikian berakhir pendek ketika kami memeriksa semua potongan byte. Kita perlu mendapatkan lebih banyak byte acak, "secara rekursif". Dan sekarang kita bahkan kehilangan keuntungan "satu panggilan ke rand
paket" ...
Kami dapat "agak" mengoptimalkan penggunaan data acak yang kami peroleh math.Rand()
. Kami dapat memperkirakan berapa banyak byte (bit) yang kami butuhkan. 1 huruf membutuhkan letterIdxBits
bit, dan kita perlu n
huruf, jadi kita perlu n * letterIdxBits / 8.0
byte yang mengumpulkan. Kami dapat menghitung probabilitas indeks acak tidak dapat digunakan (lihat di atas), jadi kami dapat meminta lebih banyak yang "lebih mungkin" cukup (jika ternyata tidak, kami ulangi prosesnya). Kita dapat memproses byte slice sebagai "bit stream" misalnya, yang kami punya lib pihak ke-3 yang bagus: github.com/icza/bitio
(pengungkapan: Saya penulisnya).
Namun kode Benchmark masih menunjukkan kita tidak menang. Kenapa gitu?
Jawaban untuk pertanyaan terakhir adalah karena rand.Read()
menggunakan perulangan dan terus memanggil Source.Int63()
hingga mengisi irisan yang dilewati. Apa yang dilakukan RandStringBytesMaskImprSrc()
solusinya, tanpa buffer perantara, dan tanpa kompleksitas tambahan. Itu sebabnya RandStringBytesMaskImprSrc()
tetap di atas takhta. Ya, RandStringBytesMaskImprSrc()
gunakan yang tidak disinkronkan rand.Source
seperti rand.Read()
. Namun alasannya masih berlaku; dan yang terbukti jika kita menggunakan Rand.Read()
bukan rand.Read()
(yang sebelumnya juga tidak disinkronkan).
Baiklah, sudah waktunya untuk membandingkan solusi yang berbeda.
Momen kebenaran:
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
Hanya dengan beralih dari rune ke byte, kami langsung memiliki peningkatan kinerja 24% , dan kebutuhan memori turun menjadi sepertiga .
Menyingkirkan rand.Intn()
dan menggunakan rand.Int63()
sebagai gantinya memberikan 20% lagi .
Masking (dan mengulangi jika indeks besar) sedikit melambat (karena panggilan pengulangan): -22% ...
Tetapi ketika kita menggunakan semua (atau sebagian besar) dari 63 bit acak (10 indeks dari satu rand.Int63()
panggilan): yang mempercepat waktu besar: 3 kali .
Jika kita puas dengan (non-default, baru) rand.Source
bukan rand.Rand
, kita lagi memperoleh 21%.
Jika kita menggunakan strings.Builder
, kita memperoleh kecil 3,5% di kecepatan , tapi kami juga mencapai 50% pengurangan penggunaan memori dan alokasi! Itu bagus!
Akhirnya jika kita berani menggunakan paket, unsafe
bukan strings.Builder
, kita lagi mendapatkan 14% yang bagus .
Membandingkan final dengan solusi awal: RandStringBytesMaskImprSrcUnsafe()
adalah 6,3 kali lebih cepat daripada RandStringRunes()
, menggunakan memori keenam dan alokasi setengah . Misi selesai.
rand.Source
digunakan bersama . Solusi yang lebih baik adalah dengan meneruskan rand.Source
ke RandStringBytesMaskImprSrc()
fungsi, dan dengan demikian tidak diperlukan penguncian dan karenanya kinerja / efisiensi tidak terpengaruh. Setiap goroutine bisa memiliki sendiri Source
.
defer
ketika jelas bahwa Anda tidak membutuhkannya. Lihat grokbase.com/t/gg/golang-nuts/158zz5p42w/…
defer
untuk membuka kunci mutex baik segera sebelum atau setelah memanggil kunci adalah IMO sebagian besar adalah ide yang sangat bagus; Anda dijamin untuk keduanya tidak lupa untuk membuka kunci tetapi juga membuka kunci meskipun dalam fungsi panik yang tidak fatal.
Anda bisa menulis kode untuk itu. Kode ini bisa sedikit lebih sederhana jika Anda ingin mengandalkan huruf-huruf yang semuanya byte tunggal ketika dikodekan dalam 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())
ataurand.Seed(time.Now().UnixNano())
math/rand
; gunakan crypto/rand
(seperti opsi @ Not_A_Golfer 1).
Dua opsi yang mungkin (tentu saja ada lebih banyak):
Anda dapat menggunakan crypto/rand
paket yang mendukung membaca array byte acak (dari / dev / urandom) dan diarahkan untuk pembuatan acak kriptografis. lihat http://golang.org/pkg/crypto/rand/#example_Read . Mungkin lebih lambat dari generasi nomor pseudo-acak normal.
Ambil nomor acak dan hash menggunakan md5 atau sesuatu seperti ini.
Mengikuti icza's
solusi yang dijelaskan dengan luar biasa, berikut ini adalah modifikasi yang menggunakan crypto/rand
alih-alih 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
}
Jika Anda menginginkan solusi yang lebih umum, yang memungkinkan Anda meneruskan potongan byte karakter untuk membuat string keluar, Anda dapat mencoba menggunakan ini:
// 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)
}
Jika Anda ingin meneruskan sumber keacakan Anda sendiri, akan sepele untuk memodifikasi di atas untuk menerima io.Reader
alih - alih menggunakan crypto/rand
.
jika kamu mau angka acak yang aman secara kriptografis , dan rangkaian karakter yang tepat fleksibel (misalnya, base64 baik-baik saja), Anda dapat menghitung dengan tepat berapa panjang karakter acak yang Anda butuhkan dari ukuran output yang diinginkan.
Teks Base 64 1/3 lebih panjang dari base 256. (2 ^ 8 vs 2 ^ 6; 8bits / 6bits = 1,333 rasio)
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
}
Catatan: Anda juga dapat menggunakan RawStdEncoding jika Anda lebih suka + dan / karakter untuk - dan _
Jika Anda ingin hex, basis 16 adalah 2x lebih panjang dari basis 256. (2 ^ 8 vs 2 ^ 4; 8bits / 4bits = rasio 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
}
Namun, Anda bisa memperluas ini ke set karakter yang sewenang-wenang jika Anda memiliki encoder base256 ke baseN untuk set karakter Anda. Anda dapat melakukan perhitungan ukuran yang sama dengan berapa banyak bit yang dibutuhkan untuk mewakili set karakter Anda. Perhitungan rasio untuk rangkaian karakter acak adalah:ratio = 8 / log2(len(charset))
.
Meskipun kedua solusi ini aman, sederhana, harus cepat, dan jangan sia-siakan kolam entropi kripto Anda.
Inilah taman bermain yang menunjukkan bahwa itu berfungsi untuk berbagai ukuran. 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
?
Jika Anda ingin menambahkan beberapa karakter ke kumpulan karakter yang diizinkan, Anda dapat membuat kode berfungsi dengan apa saja yang menyediakan byte acak melalui io.Reader. Di sini kita gunakan 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
perlu?
len(encodeURL) == 64
. Jika random % 64
tidak dilakukan, randomPos
bisa> = 64 dan menyebabkan kepanikan di luar batas saat runtime.
/*
korzhao
*/
package rand
import (
crand "crypto/rand"
"math/rand"
"sync"
"time"
"unsafe"
)
// Doesn't share the rand library globally, reducing lock contention
type Rand struct {
Seed int64
Pool *sync.Pool
}
var (
MRand = NewRand()
randlist = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
)
// init random number generator
func NewRand() *Rand {
p := &sync.Pool{New: func() interface{} {
return rand.New(rand.NewSource(getSeed()))
},
}
mrand := &Rand{
Pool: p,
}
return mrand
}
// get the seed
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)
}
// get a random number
func (s *Rand) Intn(n int) int {
r := s.getrand()
defer s.putrand(r)
return r.Intn(n)
}
// bulk get random numbers
func (s *Rand) Read(p []byte) (int, error) {
r := s.getrand()
defer s.putrand(r)
return r.Read(p)
}
func CreateRandomString(len int) string {
b := make([]byte, len)
_, err := MRand.Read(b)
if err != nil {
return ""
}
for i := 0; i < len; i++ {
b[i] = randlist[b[i]%(62)]
}
return *(*string)(unsafe.Pointer(&b))
}
24.0 ns / op 16 B / op 1 allocs /
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