Мені потрібно лише випадковий рядок символів (великі або малі літери), без цифр, у Go. Який найшвидший і найпростіший спосіб зробити це?
Мені потрібно лише випадковий рядок символів (великі або малі літери), без цифр, у Go. Який найшвидший і найпростіший спосіб зробити це?
Відповіді:
Рішення Павла забезпечує просте загальне рішення.
Питання задає "найшвидший і найпростіший спосіб" . Звернімося також до найшвидшої частини. Ми дійдемо до свого останнього, найшвидшого коду в ітераційному порядку. Бенчмаркинг кожної ітерації можна знайти в кінці відповіді.
Усі рішення та код бенчмаркінгу можна знайти на майданчику Go Play . Код на дитячій площадці - це тестовий файл, а не виконуваний файл. Ви повинні зберегти його у файлі з ім'ям XX_test.go
та запустити його
go test -bench . -benchmem
Передмова :
Найшвидше рішення не є рішенням, якщо вам просто потрібен випадковий рядок. Для цього ідеально рішення Павла. Це якщо продуктивність має значення. Хоча перші два кроки ( байт і залишок ) можуть бути прийнятним компромісом: вони покращують продуктивність приблизно на 50% (див. Точні цифри в розділі II. Бенчмарк ), і вони не збільшують складність значно.
Сказавши, що навіть якщо вам не потрібно найшвидше рішення, читання цієї відповіді може бути пригодницьким та навчальним.
Нагадуємо, оригінальне загальне рішення, яке ми вдосконалюємо, таке:
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)
}
Якщо символи на вибір і збирання випадкової рядки містять лише великі і малі літери англійського алфавіту, ми можемо працювати з байтами лише тому, що літери англійського алфавіту відображають в байтах 1-до-1 в кодуванні UTF-8 (який як Go зберігає рядки).
Тож замість:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
ми можемо використовувати:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Або ще краще:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Зараз це вже велике поліпшення: ми могли б домогтися того, щоб він був const
(є string
константи, але немає констант зрізу ). Як додатковий виграш, вираз len(letters)
буде також const
! (Вираз len(s)
є постійним, якщо s
є постійною струною.)
І якою ціною? Нічого взагалі. string
s може бути індексовано, що індексує його байти, ідеально, саме те, що ми хочемо.
Наступне наше призначення виглядає так:
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)
}
Попередні рішення отримують випадкове число, щоб позначити випадкову букву, зателефонувавши rand.Intn()
до Rand.Intn()
яких делегатів, яким делегатам Rand.Int31n()
.
Це набагато повільніше порівняно з цим rand.Int63()
виробляє випадкове число з 63 випадковими бітами.
Тому ми могли просто зателефонувати rand.Int63()
та використати решту після поділу на 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)
}
Це працює і значно швидше, недоліком є те, що ймовірність усіх букв буде не однаковою (якщо припустити, що rand.Int63()
всі 63-бітні числа з однаковою ймовірністю). Хоча спотворення вкрай мало, оскільки кількість букв 52
набагато-набагато менша, ніж 1<<63 - 1
на практиці це цілком добре.
Щоб зрозуміти це простіше: скажімо, вам потрібно випадкове число в діапазоні 0..5
. Використовуючи 3 випадкових біта, це призведе до отримання чисел 0..1
з подвійною ймовірністю, ніж з діапазону 2..5
. Використовуючи 5 випадкових бітів, числа в діапазоні 0..1
відбуватимуться з 6/32
ймовірністю, а числа в діапазоні 2..5
з 5/32
ймовірністю, яка тепер ближче до потрібної. Збільшення кількості бітів робить це менш значущим, коли досягає 63 біта, це незначно.
Спираючись на попереднє рішення, ми можемо підтримувати рівний розподіл літер, використовуючи лише стільки найменших бітів випадкового числа, скільки потрібно для представлення кількості літер. Так, наприклад , якщо у нас є 52 літери, вона вимагає 6 біт для представлення його: 52 = 110100b
. Таким чином, ми будемо використовувати лише найнижчі 6 біт числа, повернутого номером rand.Int63()
. І щоб підтримувати рівний розподіл літер, ми "приймаємо" цифру лише в тому випадку, якщо вона потрапляє в діапазон 0..len(letterBytes)-1
. Якщо найнижчі біти більше, ми відкидаємо його і запитуємо нове випадкове число.
Зауважте, що шанс найнижчих бітів бути більшим або рівним len(letterBytes)
менше, ніж 0.5
загалом ( 0.25
в середньому), а це означає, що навіть у такому випадку повторення цього "рідкісного" випадку зменшує шанс не знайти хорошого число. Після n
повторення ймовірність того, що у нас на підвіконні не буде хорошого показника, значно менше pow(0.5, n)
, і це лише верхня оцінка. У випадку 52 літер шанс, що 6 найнижчих бітів не є хорошими, є лише (64-52)/64 = 0.19
; що означає, наприклад, що шанси не мати гарного числа після 10 повторень є 1e-8
.
Тож ось рішення:
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)
}
У попередньому рішенні використовуються лише найнижчі 6 бітів із 63 випадкових бітів, повернених о rand.Int63()
. Це марно, оскільки отримання випадкових бітів є найповільнішою частиною нашого алгоритму.
Якщо у нас 52 букви, це означає, що 6 біт коду буквеного індексу. Тож 63 випадкові біти можуть позначати 63/6 = 10
різні буквені індекси. Давайте використаємо всі ці 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)
}
Маскування Поліпшення досить добре, що не так багато ми можемо поліпшити його. Ми могли, але не варті складності.
Тепер давайте знайдемо ще щось, щоб покращити. Джерело випадкових чисел.
Існує crypto/rand
пакет, який забезпечує Read(b []byte)
функцію, і ми могли б використовувати цей вміст, щоб отримати стільки байтів за один виклик, скільки нам потрібно. Це не допоможе з точки зору продуктивності, оскільки crypto/rand
реалізує криптографічно захищений генератор псевдовипадкових чисел, тому це набагато повільніше.
Тож давайте дотримуватись math/rand
пакету. rand.Rand
Використовує в rand.Source
якості джерела випадкових бітів. rand.Source
це інтерфейс, який визначає Int63() int64
метод: саме і єдине, що нам було потрібно і використане в нашому останньому рішенні.
Тож нам насправді не потрібен rand.Rand
(явний або глобальний, спільний один із rand
пакетів), a rand.Source
нам цілком достатньо:
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)
}
Також зверніть увагу , що це останнє рішення не вимагає ініціалізації (насіння) глобальна Rand
частина math/rand
пакета , як не використовується (і наша rand.Source
правильно инициализирован / приманку).
Тут слід зазначити ще одну річ: пакет документів для math/rand
держав:
Джерело за замовчуванням безпечно для одночасного використання декількома програмами.
Отже джерело за замовчуванням повільніше, ніж це Source
може бути отримано rand.NewSource()
, оскільки джерело за замовчуванням має забезпечувати безпеку при одночасному доступі / використанні, при цьому rand.NewSource()
не пропонує цього (і, таким чином, Source
повернене ним швидше буде швидшим).
strings.Builder
Усі попередні рішення повертають string
вміст, вміст якого спочатку будується в фрагменті ( []rune
в « Генезісі» та []byte
в наступних рішеннях), а потім перетворюється в string
. Це остаточне перетворення повинно зробити копію вмісту фрагмента, оскільки string
значення незмінні, і якщо перетворення не зробило б копію, не можна було гарантувати, що вміст рядка не буде змінено через його початковий фрагмент. Докладніше див. Як перетворити рядок utf8 в байт []? і golang: [] байт (рядок) проти [] байт (* рядок) .
Перейти 1.10 введено strings.Builder
. strings.Builder
новий тип, який ми можемо використовувати для створення вмісту, string
подібного до bytes.Buffer
. Це робиться всередині, використовуючи a []byte
, і коли ми закінчимо, ми можемо отримати остаточне string
значення за допомогою його Builder.String()
методу. Але що в цьому круто - це те, що він робить це, не виконуючи копію, про яку ми говорили вище. Це наважується зробити це, оскільки байтовий фрагмент, який використовується для створення вмісту рядка, не піддається впливу, тому гарантується, що ніхто не може його змінити ненавмисно чи зловмисно, щоб змінити створений "незмінний" рядок.
Отже, наступна наша ідея полягає в тому, щоб не будувати випадковий рядок у зрізі, а за допомогою а. strings.Builder
Отже, коли ми закінчимо, ми зможемо отримати та повернути результат, не створюючи його копії. Це може допомогти з точки зору швидкості, і, безумовно, допоможе з точки зору використання пам'яті та розподілу даних.
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()
}
Зауважте, що створивши новий strings.Buidler
, ми назвали його Builder.Grow()
метод, переконавшись, що він виділяє достатньо великий внутрішній зріз (щоб уникнути перерозподілу, коли ми додаємо випадкові букви).
strings.Builder
з пакетомunsafe
strings.Builder
будує рядок у внутрішній []byte
, такий же, як ми зробили самі. Так що в основному це робиться через a, strings.Builder
має деяку накладну вартість, єдине, на що ми переключились strings.Builder
- це уникати остаточного копіювання фрагмента.
strings.Builder
уникає остаточної копії, використовуючи пакет unsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Річ у тому, що ми також можемо це зробити і самі. Отже, ідея полягає в тому, щоб повернутися до побудови випадкової рядки в a []byte
, але коли ми закінчимо, не перетворюйте його string
на повернення, а зробіть небезпечне перетворення: отримайте a, string
який вказує на наш байтовий фрагмент як дані рядка .
Ось як це можна зробити:
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()
)Перейти 1.7 доданий в rand.Read()
функцію і Rand.Read()
метод. Нам слід спокуси використовувати їх для того, щоб прочитати стільки байтів, скільки нам потрібно за один крок, щоб досягти кращої продуктивності.
У цьому є одна невелика «проблема»: скільки байтів нам потрібно? Можна було б сказати: стільки ж кількості вихідних літер. Ми вважаємо, що це верхня оцінка, оскільки індекс літер використовує менше 8 біт (1 байт). Але в цей момент ми вже робимо гірше (оскільки отримання випадкових бітів - це "важка частина"), і ми отримуємо більше, ніж потрібно.
Також зауважте, що для збереження рівномірного розподілу всіх буквених індексів може виникнути деяка кількість «сміття» випадкових даних, які ми не зможемо використати, тому ми в кінцевому підсумку пропустимо деякі дані і, таким чином, будемо короткою, коли пройдемо всі шматочок байта. Нам потрібно додатково отримати більше випадкових байтів, "рекурсивно". А зараз ми навіть втрачаємо rand
перевагу "єдиного дзвінка на пакет" ...
Ми могли б «дещо» оптимізувати використання випадкових даних, які ми отримуємо math.Rand()
. Ми можемо оцінити, скільки байтів (біт) нам знадобиться. 1 лист вимагає letterIdxBits
бітів, і нам потрібні n
букви, тому нам потрібні n * letterIdxBits / 8.0
байти в округленні. Ми можемо обчислити ймовірність того, що випадковий індекс не може бути використаний (див. Вище), тому ми могли б запитати більше, що буде "скоріше" достатньо (якщо виявиться, що це не так, ми повторимо процес). Наприклад, ми можемо обробити фрагмент байту як "бітовий потік", для якого у нас є хороша сторона lib: github.com/icza/bitio
(розкриття: я автор).
Але код Benchmark все ще показує, що ми не виграємо. Чому так?
Відповідь на останнє запитання полягає в тому, що він rand.Read()
використовує цикл і продовжує дзвонити, Source.Int63()
поки не заповнить пропущений фрагмент. Саме те, що RandStringBytesMaskImprSrc()
робить розчин, без проміжного буфера і без додаткової складності. Ось чому RandStringBytesMaskImprSrc()
залишається на троні. Так, RandStringBytesMaskImprSrc()
використовується несинхронізований rand.Source
на відміну від rand.Read()
. Але міркування все ж застосовуються; і що доведено, якщо ми використовуємо Rand.Read()
замість rand.Read()
(колишній також несинхронізований).
Гаразд, настав час для порівняльного аналізу різних рішень.
Момент істини:
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
Просто перейшовши з рун на байти, ми одразу маємо 24-відсотковий приріст продуктивності, а потреба в пам'яті знижується до однієї третини .
Позбавлення rand.Intn()
та використання rand.Int63()
натомість дає ще 20% -ве збільшення.
Маскування (і повторення у випадку великих індексів) трохи сповільнюється (через повторення дзвінків): -22% ...
Але коли ми використовуємо всі (або більшість) з 63 випадкових біт (10 індексів за один rand.Int63()
виклик): це прискорює великий час: 3 рази .
Якщо ми погодимось із (не за замовчуванням, новим) rand.Source
замість rand.Rand
, ми знову наберемо 21%.
Якщо ми використовуємо strings.Builder
, ми отримуємо крихітну 3,5% в швидкості , але ми також досягли 50% зниження у використанні пам'яті і розподілі! Це мило!
Нарешті, якщо ми наважимось використовувати пакет unsafe
замість strings.Builder
, ми знову наберемо приємні 14% .
Порівнюючи фінал вихідного розчину: RandStringBytesMaskImprSrcUnsafe()
в 6,3 рази швидше , ніж RandStringRunes()
, використовує одну шосту пам'яті і половину , як кілька розподілів . Місію виконано.
rand.Source
. Кращим рішенням було б перехід rand.Source
на RandStringBytesMaskImprSrc()
функцію, і таким чином не потрібно блокувати, а отже, продуктивність / ефективність не досягається. Кожна програма може мати своє Source
.
defer
коли очевидно, що він вам не потрібен. Дивіться grokbase.com/t/gg/golang-nuts/158zz5p42w/…
defer
розблокувати мютекс безпосередньо перед або після виклику блокування IMO здебільшого дуже гарна ідея; Ви гарантовано не будете забувати розблокувати, але також розблокувати навіть у не фатальній панічній середині функції.
Ви можете просто написати код для цього. Цей код може бути дещо простішим, якщо ви хочете покластися на букви, які є єдиними байтами, коли закодовані в 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())
абоrand.Seed(time.Now().UnixNano())
math/rand
; скористайтеся crypto/rand
(наприклад, опція @ Not_A_Golfer 1).
Два можливі варіанти (може бути, звичайно, більше):
Ви можете використовувати crypto/rand
пакет, який підтримує зчитування випадкових байтових масивів (з / dev / urandom) і орієнтований на криптографічне випадкове генерація. див. http://golang.org/pkg/crypto/rand/#example_Read . Це може бути повільніше, ніж звичайне генерація псевдовипадкових чисел.
Візьміть випадкове число і хеште його, використовуючи md5 або щось подібне.
Після icza's
чудово поясненого рішення, ось його модифікація, яка використовується crypto/rand
замість 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
}
Якщо ви хочете отримати більш загальне рішення, яке дозволяє вам передавати фрагмент байтів символів, щоб створити рядок з, ви можете спробувати скористатися цим:
// 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)
}
Якщо ви хочете передати своє власне джерело випадковості, було б тривіально змінити вищезазначене, щоб прийняти io.Reader
замість використання crypto/rand
.
Якщо ви хочете, щоб криптографічно захищені випадкові числа, а точний графік був гнучким (скажімо, base64 чудово), ви можете точно розрахувати, яка довжина випадкових символів вам потрібна з потрібного розміру виводу.
Текст основи 64 на 1/3 довший, ніж базовий 256. (співвідношення 2 ^ 8 проти 2 ^ 6; відношення 8 біт / 6 біт = 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
}
Примітка: ви також можете використовувати RawStdEncoding, якщо ви віддаєте перевагу + та / символи - і _
Якщо ви хочете шістнадцятковий, база 16 в 2 рази довша, ніж основа 256. (2 ^ 8 проти 2 ^ 4; відношення 8 біт / 4 біт = 2х)
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
}
Однак ви можете поширити це на будь-який довільний набір символів, якщо для вашого набору символів у вас є базовий кодер base256. Ви можете виконати однаковий розрахунок розміру з тим, скільки бітів потрібно для представлення набору символів. Розрахунок відношення для будь-якої довільної схеми становить:) ratio = 8 / log2(len(charset))
.
Хоча обидва ці рішення є безпечними, простими, вони повинні бути швидкими, і не витрачайте свій криптовалютний пул ентропії.
Ось майданчик показує, що він працює для будь-якого розміру. 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
?
Якщо ви готові додати кілька символів у свій пул дозволених символів, ви можете змусити код працювати з усім, що забезпечує випадкові байти через io.Reader. Ось ми і використовуємо 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
потрібно?
len(encodeURL) == 64
. Якщо цього random % 64
не зробити, це randomPos
може бути> = 64 і викликати паніку поза межами часу виконання.
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 нс / оп 16 Б / оп 1 алоків / оп