Git'te yalnızca rastgele bir karakter dizesi (büyük veya küçük harf) istiyorum, sayı yok. Bunu yapmanın en hızlı ve basit yolu nedir?
Git'te yalnızca rastgele bir karakter dizesi (büyük veya küçük harf) istiyorum, sayı yok. Bunu yapmanın en hızlı ve basit yolu nedir?
Yanıtlar:
Paul çözüm bir sağlar basit , genel çözüm.
Soru "en hızlı ve en basit yol" istiyor . En hızlı kısmı da ele alalım . Son, en hızlı kodumuza yinelemeli bir şekilde ulaşacağız. Her bir yinelemeyi kıyaslamak cevabın sonunda bulunabilir.
Tüm çözümler ve kıyaslama kodu Go Playground'da bulunabilir . Oyun Alanı'ndaki kod, yürütülebilir bir dosya değil, bir test dosyasıdır. Bu dosyayı adlı bir dosyaya kaydetmeniz XX_test.go
ve
go test -bench . -benchmem
Önsöz :
Rastgele bir dizeye ihtiyacınız varsa, en hızlı çözüm bir çözüm değildir. Bunun için Paul'ün çözümü mükemmel. Performans önemliyse budur. İlk 2 adım ( Bayt ve Kalan ) kabul edilebilir bir uzlaşma olsa da: performansı% 50 gibi iyileştirirler ( II. Karşılaştırma bölümünde kesin sayılara bakın) ve karmaşıklığı önemli ölçüde artırmazlar.
En hızlı çözüme ihtiyacınız olmasa bile, bu yanıtı okumak maceracı ve eğitici olabilir.
Bir hatırlatma olarak, geliştirdiğimiz orijinal, genel çözüm şudur:
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)
}
Rastgele dizeden seçilecek ve bir araya getirilecek karakterler İngilizce alfabenin yalnızca büyük ve küçük harflerini içeriyorsa, yalnızca İngilizce alfabe harfleri UTF-8 kodlamasında 1 ile 1 arasındaki baytlarla eşleştiği için baytlarla çalışabiliriz. Go dizeleri nasıl saklar).
Bunun yerine:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
kullanabiliriz:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Ya da daha iyisi:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Şimdi bu zaten büyük bir gelişme: bunu const
( string
sabitler var ancak dilim sabitleri yok ) olarak başarabiliriz . Ekstra bir kazanç olarak, ifade len(letters)
de bir olacak const
! ( Bir dize sabiti len(s)
ise ifade s
sabittir.)
Ve ne pahasına? Hiçbir şey. string
s, hangi baytını indeksler, mükemmel, tam olarak ne istediğimizi endekslenebilir.
Bir sonraki hedefimiz şöyle görünüyor:
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)
}
Önceki çözümler rand.Intn()
, Rand.Intn()
hangi delegelerin delegelerini çağırarak rastgele bir harf belirtmek için rastgele bir sayı alır Rand.Int31n()
.
Bu rand.Int63()
63 rastgele bit ile rastgele bir sayı üreten ile karşılaştırıldığında çok daha yavaştır .
Böylece rand.Int63()
, bölünerek geri kalanını arayabilir ve kullanabiliriz 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)
}
Bu işe yarar ve önemli ölçüde daha hızlı, dezavantaj, tüm harflerin olasılığının tam olarak aynı olmayacağıdır ( rand.Int63()
63-bit sayıların tümünü eşit olasılıkla ürettiği varsayılarak ). Harf sayısı 52
çok daha küçük olduğu için bozulma son derece küçük olmasına rağmen, 1<<63 - 1
pratikte bu gayet iyi.
Bunu daha kolay anlamak için: diyelim ki aralığında rasgele bir sayı istiyorsunuz 0..5
. 3 rasgele bit kullanıldığında, 0..1
aralıktan iki kat daha fazla olasılık olan sayılar elde edilir 2..5
. 5 rastgele bit kullanılarak, dizi numaraları 0..1
ile ortaya çıkabilecek 6/32
aralığında olasılık ve sayı 2..5
ile 5/32
yakın arzu için artık olasılık. Bit sayısını artırmak bunu daha az önemli hale getirir, 63 bite ulaştığında önemsizdir.
Önceki çözüme dayanarak, harflerin sayısını temsil etmek için rastgele sayının en düşük bitlerinden sadece çoğunu kullanarak harflerin eşit dağılımını koruyabiliriz. Örneğin biz 52 harf var ise, onu temsil etmek 6 bit gerektirir: 52 = 110100b
. Bu nedenle, yalnızca döndürülen sayının en düşük 6 bitini kullanacağız rand.Int63()
. Ve harflerin eşit dağılımını sağlamak için, sadece aralığa girerse sayıyı "kabul ederiz" 0..len(letterBytes)-1
. En düşük bitler daha büyükse, onu atar ve yeni bir rasgele sayı sorgularız.
En düşük bitlerin eşit veya daha büyük olma şansının genel olarak ( ortalama) len(letterBytes)
daha düşük olduğuna dikkat edin, bu durum böyle olsa bile, bu "nadir" durumu tekrarlamanın iyi bir şey bulma şansını azalttığı anlamına gelir. numara. Tekrarlamadan sonra , eşik alma şansımızın iyi bir endekse sahip olmama şansı çok daha azdır ve bu sadece bir üst tahmindir. 52 harf olması durumunda, en düşük 6 bitin iyi olmama ihtimali sadece ; örneğin 10 tekrardan sonra iyi bir sayıya sahip olmama şansı .0.5
0.25
n
pow(0.5, n)
(64-52)/64 = 0.19
1e-8
İşte çözüm:
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)
}
Önceki çözüm yalnızca döndürülen 63 rastgele bitin en düşük 6 bitini kullanır rand.Int63()
. Rastgele bitleri almak algoritmamızın en yavaş kısmı olduğu için bu bir israftır.
52 harfimiz varsa, bu 6 bitin harf indeksini kodladığı anlamına gelir. Böylece 63 rastgele bit 63/6 = 10
farklı harf indeksleri belirleyebilir. Tüm bu 10'u kullanalım:
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)
}
Geliştirilmiş Maskeleme çok bunun üzerinde artırabilir değil, oldukça iyidir. Yapabilirdik, ama karmaşıklığa değmez.
Şimdi geliştirmek için başka bir şey bulalım. Rasgele sayıların kaynağı.
crypto/rand
Bir Read(b []byte)
işlev sağlayan bir paket var , bu yüzden ihtiyacımız olan tek bir çağrı ile bayt almak için kullanabiliriz. Bu, crypto/rand
kriptografik olarak güvenli bir sözde sayı üretecini uyguladığı için performans açısından yardımcı olmaz, bu yüzden çok daha yavaştır.
Öyleyse math/rand
pakete yapışalım. A , rastgele bitlerin kaynağı olarak rand.Rand
kullanır rand.Source
. rand.Source
bir Int63() int64
yöntemi belirten bir arayüzdür : tam olarak ve en son çözümümüzde ihtiyaç duyduğumuz ve kullandığımız tek şey.
Yani gerçekten rand.Rand
(açık ya da küresel, rand
paketin paylaşılan bir tanesine ) ihtiyacımız yok, a rand.Source
bizim için mükemmel:
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)
}
Ayrıca, bu son çözümün kullanılmadığı Rand
için math/rand
paketin genelini başlatmanızı (tohumlamayı) gerektirmediğini (ve bizim rand.Source
uygun şekilde başlatıldığını / tohumlandığını) unutmayın.
Burada dikkat edilmesi gereken bir şey daha var: paket doc of math/rand
states:
Varsayılan Kaynak, birden fazla goroutin tarafından eşzamanlı kullanım için güvenlidir.
Bu nedenle, varsayılan kaynak Source
elde edilebilecek bir kaynaktan daha yavaştır rand.NewSource()
, çünkü varsayılan kaynağın eşzamanlı erişim / kullanım altında güvenlik sağlaması gerekirken rand.NewSource()
, bunu sunmaz (ve dolayısıyla Source
geri dönenin daha hızlı olma olasılığı daha yüksektir).
strings.Builder
Daha önceki tüm çözümler geri bir string
, içeriği ilk olarak bir dilim inşa edilmiştir ( []rune
de Genesis , ve []byte
takip eden çözeltiler içinde) ve daha sonra ilk olarak string
. Bu son dönüşüm, dilimin içeriğinin bir kopyasını oluşturmak zorundadır, çünkü string
değerler değişmezdir ve dönüşüm bir kopya oluşturmazsa, dizenin içeriğinin orijinal dilimi aracılığıyla değiştirilmemesi garanti edilemez. Ayrıntılar için bkz. Utf8 dizesi [] baytına nasıl dönüştürülür? ve golang: [] bayt (string) vs [] bayt (* string) .
Go 1.10 tanıtıldı strings.Builder
. benzer strings.Builder
içerik oluşturmak için kullanabileceğimiz yeni bir tür . Dahili olarak a kullanarak yapar ve bittiğinde, yöntemini kullanarak son değeri elde edebiliriz . Ama içinde güzel olan, yukarıda bahsettiğimiz kopyayı yapmadan bunu yapmasıdır. Dizenin içeriğini oluşturmak için kullanılan bayt dilimi açıkta olmadığından bunu yapmaya cesaret edebilir, bu nedenle üretilen "değişmez" dizeyi değiştirmek için hiç kimsenin istemeden veya kötü amaçlı olarak değiştiremeyeceği garanti edilir.string
bytes.Buffer
[]byte
string
Builder.String()
Bu yüzden bir sonraki fikrimiz rastgele bir dizeyi bir dilimde oluşturmak değil, ama a'nın yardımıyla, strings.Builder
bittikten sonra, bir kopyasını yapmak zorunda kalmadan sonucu elde edebilir ve geri verebiliriz. Bu, hız açısından yardımcı olabilir ve bellek kullanımı ve tahsisleri konusunda kesinlikle yardımcı olacaktır.
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()
}
Yeni bir tane oluşturduktan sonra strings.Buidler
, Builder.Grow()
yeterince büyük bir iç dilim ayırdığından emin olarak yöntemini aradığımızı unutmayın (rastgele harfleri eklerken yeniden tahsisleri önlemek için).
strings.Builder
paketi ileunsafe
strings.Builder
ipi []byte
kendimizde yaptığımız gibi içsel olarak inşa eder . Yani temelde bunu bir strings.Builder
ek yük ile yapmak, geçiş yaptığımız tek şey strings.Builder
dilimin son kopyalanmasını önlemek.
strings.Builder
paketi kullanarak son kopyadan kaçınır unsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Mesele şu ki, bunu kendimiz de yapabiliriz. Buradaki fikir, rastgele dizgiyi a'ya geri döndürmektir []byte
, ancak işimiz bittiğinde onu string
geri dönüşe dönüştürmeyin , ancak güvenli olmayan bir dönüşüm yapın: string
dizgi verileri olarak bayt dilimimize bir nokta elde edin .
Bu şekilde yapılabilir:
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 bir rand.Read()
fonksiyon ve Rand.Read()
yöntem ekledi . Daha iyi performans elde etmek için bunları bir adımda ihtiyaç duyduğumuz kadar bayt okumak için kullanmaya cazip gelmeliyiz.
Bununla ilgili küçük bir "sorun" var: kaç bayta ihtiyacımız var? Şunu söyleyebiliriz: çıktı harflerinin sayısı. Bir harf endeksi 8 bit'ten (1 byte) daha az kullandığı için bunun bir üst tahmin olduğunu düşünüyoruz. Ama bu noktada zaten daha kötüsünü yapıyoruz (rastgele bitleri almak "zor kısım" olduğu için) ve ihtiyaç duyduğumuzdan daha fazlasını elde ediyoruz.
Ayrıca, tüm harf endekslerinin eşit dağılımını sağlamak için kullanamayacağımız bazı "çöp" rastgele veriler olabileceğini unutmayın, bu nedenle bazı verileri atlayacağız ve böylece tüm bayt dilimi. Daha fazla "yinelemeli" rastgele bayt almamız gerekecekti. Ve şimdi "tek rand
paket arama " avantajını bile kaybediyoruz ...
Elde ettiğimiz rastgele verilerin kullanımını "bir şekilde" optimize edebiliriz math.Rand()
. Kaç bayta (bit) ihtiyacımız olacağını tahmin edebiliriz. 1 harf letterIdxBits
bit gerektirir ve n
harflere ihtiyacımız vardır , yani n * letterIdxBits / 8.0
baytların yuvarlanması gerekir . Rastgele bir endeksin kullanılabilir olmama olasılığını hesaplayabiliriz (yukarıya bakınız), bu yüzden "daha muhtemel" olacak daha fazla talep edebiliriz (eğer ortaya çıkmazsa, işlemi tekrarlıyoruz). Bayt dilimini, örneğin güzel bir 3. taraf lib'e sahip olduğumuz bir "bit akışı" olarak işleyebiliriz: github.com/icza/bitio
(açıklama: Ben yazarım).
Ancak Benchmark kodu hala kazanmadığımızı gösteriyor. Neden böyle?
Son sorunun cevabı rand.Read()
, bir döngü kullanması ve Source.Int63()
geçen dilimi doldurana kadar aramaya devam etmesidir. Tam olarak ne RandStringBytesMaskImprSrc()
Çözelti yapar olmayan ara tampon ve ilave karmaşıklık olmadan. Bu yüzden RandStringBytesMaskImprSrc()
tahtta kalır. Evet, aksine RandStringBytesMaskImprSrc()
senkronize edilmemiş bir kullanır . Ancak akıl yürütme hala geçerlidir; ve bunun yerine kullanırsak kanıtlanır (birincisi de senkronize değildir).rand.Source
rand.Read()
Rand.Read()
rand.Read()
Pekala, farklı çözümleri kıyaslama zamanı.
Gerçek anı:
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
Sadece runiklerden baytlara geçerek, hemen % 24 performans kazancımız olur ve bellek gereksinimi üçte birine düşer .
Kurtulmak rand.Intn()
ve rand.Int63()
onun yerine kullanmak % 20'lik bir artış sağlar.
Maskeleme (ve büyük endekslerde tekrarlama) biraz yavaşlar (tekrarlama çağrıları nedeniyle): -22% ...
Ancak 63 rastgele bitin (veya bir rand.Int63()
çağrının 10 endeksinin) tümünü (veya çoğunu) kullandığımızda : bu büyük zamanı hızlandırır: 3 kez .
rand.Source
Bunun yerine ( varsayılan olmayan, yeni) bir çözüm rand.Rand
bulursak, yine % 21 kazanırız.
Biz kullanmak durumunda strings.Builder
, biz küçücük kazanç 3.5% içinde hız , ama biz de elde % 50 bellek kullanımı ve tahsisleri azalma! Bu iyi!
Sonunda paketi kullanmak unsafe
yerine cesaret strings.Builder
edersek yine güzel bir % 14 kazanırız .
İlk çözeltiye son Karşılaştırılması: RandStringBytesMaskImprSrcUnsafe()
bir 6.3 kat daha hızlı daha RandStringRunes()
kullanır altıda biri bellek ve birkaç yerleri olarak yarısı . Görev tamamlandı.
rand.Source
bir kullanılıyor. Daha iyi bir geçici çözüm rand.Source
, RandStringBytesMaskImprSrc()
fonksiyona bir geçmektir ve bu şekilde kilitleme gerekmez ve dolayısıyla performans / verimlilik etkilenmez. Her goroutinin kendine ait olabilir Source
.
defer
İhtiyacınız olmadığı açık olduğunda kullanmaktan kaçınmalısınız . Bkz. Grokbase.com/t/gg/golang-nuts/158zz5p42w/…
defer
ya hemen önce veya bir kilit çağıran IMO sonra bir nesnenin kilidini kaldıracak çoğunlukla çok iyi bir fikir; hem ölümcül olmayan bir panik orta fonksiyonunda bile kilidini açmayı unutma hem de kilidini açmayı garanti ediyorsun.
Sadece kod yazabilirsiniz. UTF-8'de kodlandığında tüm harflerin tek bayt olmasına güvenmek istiyorsanız bu kod biraz daha basit olabilir.
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())
veyarand.Seed(time.Now().UnixNano())
math/rand
; kullanmak crypto/rand
yerine (Not_A_Golfer seçimine bağlı 1 @ gibi).
İki olası seçenek (elbette daha fazlası olabilir):
crypto/rand
Rasgele bayt dizilerini (/ dev / urandom'dan) okumayı destekleyen ve kriptografik rasgele üretime yönelik paketi kullanabilirsiniz . bkz. http://golang.org/pkg/crypto/rand/#example_Read . Yine de normal rasgele sayı üretmekten daha yavaş olabilir.
Rastgele bir sayı alın ve md5 veya bunun gibi bir şey kullanarak hash yapın.
icza's
Harika açıklanmış bir çözümün ardından , crypto/rand
bunun yerine kullanılan bir modifikasyon 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
}
Dizeyi oluşturmak için karakter bayt diliminden geçmenize izin veren daha genel bir çözüm istiyorsanız, bunu kullanmayı deneyebilirsiniz:
// 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)
}
Kendi rastgelelik kaynağınızdan geçmek istiyorsanız io.Reader
, kullanmak yerine yukarıdakileri değiştirmek önemsiz olacaktır crypto/rand
.
Kriptografik olarak güvenli olmasını istiyorsanız rasgele sayılar ve tam karakter seti esnekse (örneğin, base64 iyidir), istediğiniz çıktı boyutundan rastgele karakterlerin uzunluğunu tam olarak hesaplayabilirsiniz.
Taban 64 metni taban 256'dan 1/3 daha uzundur. (2 ^ 8 vs 2 ^ 6; 8 bit / 6 bit = 1,333 oran)
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
}
Not: + ve / karakterlerini - ve _ olarak tercih ediyorsanız RawStdEncoding'i de kullanabilirsiniz
Onaltılık istiyorsanız, taban 16 taban 256'dan 2 kat daha uzundur. (2 ^ 8 vs 2 ^ 4; 8 bit / 4 bit = 2x oran)
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
}
Ancak, karakter kümeniz için bir base256 - baseN kodlayıcı varsa, bunu herhangi bir rastgele karakter kümesine genişletebilirsiniz. Karakter kümenizi temsil etmek için kaç bitin gerekli olduğu ile aynı boyut hesaplamasını yapabilirsiniz. Herhangi bir rastgele karakter kümesi için oran hesaplaması:) ratio = 8 / log2(len(charset))
.
Her iki çözüm de güvenli, basit, hızlı olmalı ve kripto entropi havuzunuzu boşa harcamayın.
İşte her boyutta çalıştığını gösteren oyun alanı. https://play.golang.org/p/i61WUVR8_3Z
İzin verilen karakter havuzunuza birkaç karakter eklemek istiyorsanız, kodun io.Reader aracılığıyla rasgele bayt sağlayan herhangi bir şeyle çalışmasını sağlayabilirsiniz. Burada kullanıyoruz 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
gerekli?
len(encodeURL) == 64
. Eğer random % 64
yapılmadıysa, randomPos
> = 64 olabilir ve çalışma zamanında sınırların dışında paniğe neden olabilir.
/*
korzhao
*/
package rand
import (
crand "crypto/rand"
"math/rand"
"sync"
"time"
"unsafe"
)
// 不全局共用rand库,减少锁竞争
type Rand struct {
Seed int64
Pool *sync.Pool
}
var (
MRand = NewRand()
randlist = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
)
// 初始化随机数发生器
func NewRand() *Rand {
p := &sync.Pool{New: func() interface{} {
return rand.New(rand.NewSource(getSeed()))
},
}
mrand := &Rand{
Pool: p,
}
return mrand
}
// 获取种子
func getSeed() int64 {
return time.Now().UnixNano()
}
func (s *Rand) getrand() *rand.Rand {
return s.Pool.Get().(*rand.Rand)
}
func (s *Rand) putrand(r *rand.Rand) {
s.Pool.Put(r)
}
// 获取随机数
func (s *Rand) Intn(n int) int {
r := s.getrand()
defer s.putrand(r)
return r.Intn(n)
}
// 批量获取随机数
func (s *Rand) Read(p []byte) (int, error) {
r := s.getrand()
defer s.putrand(r)
return r.Read(p)
}
func CreateRandomString(len int) string {
b := make([]byte, len)
_, err := MRand.Read(b)
if err != nil {
return ""
}
for i := 0; i < len; i++ {
b[i] = randlist[b[i]%(62)]
}
return *(*string)(unsafe.Pointer(&b))
}
24,0 ns / op 16 B / op 1 alaşımları /
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 alaşımları / op