Tôi muốn một chuỗi ký tự ngẫu nhiên duy nhất (chữ hoa hoặc chữ thường), không có số, trong Go. Cách nhanh nhất và đơn giản nhất để làm điều này là gì?
Tôi muốn một chuỗi ký tự ngẫu nhiên duy nhất (chữ hoa hoặc chữ thường), không có số, trong Go. Cách nhanh nhất và đơn giản nhất để làm điều này là gì?
Câu trả lời:
Giải pháp của Paul cung cấp một giải pháp chung, đơn giản .
Câu hỏi yêu cầu "cách nhanh nhất và đơn giản nhất" . Hãy giải quyết phần nhanh nhất quá. Chúng tôi sẽ đến mã cuối cùng, nhanh nhất của chúng tôi theo cách lặp. Điểm chuẩn mỗi lần lặp có thể được tìm thấy ở cuối câu trả lời.
Tất cả các giải pháp và mã điểm chuẩn có thể được tìm thấy trên Sân chơi Go . Mã trên Playground là một tệp thử nghiệm, không phải là tệp thực thi. Bạn phải lưu nó vào một tệp có tên XX_test.go
và chạy nó với
go test -bench . -benchmem
Lời nói đầu :
Giải pháp nhanh nhất không phải là giải pháp đi tới nếu bạn chỉ cần một chuỗi ngẫu nhiên. Vì thế, giải pháp của Paul là hoàn hảo. Đây là nếu hiệu suất không thành vấn đề. Mặc dù 2 bước đầu tiên ( Byte và phần còn lại ) có thể là một sự thỏa hiệp chấp nhận được: chúng cải thiện hiệu suất lên tới 50% (xem số chính xác trong phần II. Điểm chuẩn ) và chúng không làm tăng độ phức tạp đáng kể.
Đã nói rằng, ngay cả khi bạn không cần giải pháp nhanh nhất, đọc qua câu trả lời này có thể là phiêu lưu và giáo dục.
Xin nhắc lại, giải pháp chung, nguyên bản mà chúng tôi đang cải thiện là:
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)
}
Nếu các ký tự để chọn và lắp ráp chuỗi ngẫu nhiên chỉ chứa các chữ cái viết hoa và viết thường của bảng chữ cái tiếng Anh, chúng ta chỉ có thể làm việc với các byte vì các chữ cái trong bảng chữ cái tiếng Anh ánh xạ tới các byte 1 trong 1 trong mã hóa UTF-8 (mà là cách Go lưu trữ chuỗi).
Vì vậy, thay vì:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
chúng ta có thể sử dụng:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Hoặc thậm chí tốt hơn:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Bây giờ đây đã là một cải tiến lớn: chúng ta có thể đạt được nó là một const
(có string
hằng số nhưng không có hằng số lát ). Là một lợi ích thêm, biểu thức len(letters)
cũng sẽ là a const
! (Biểu thức len(s)
là hằng số nếu s
là hằng chuỗi.)
Và với giá nào? Không có gì đâu. string
s có thể được lập chỉ mục mà lập chỉ mục các byte của nó, hoàn hảo, chính xác những gì chúng ta muốn.
Điểm đến tiếp theo của chúng tôi trông như thế này:
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)
}
Các giải pháp trước đó có được một số ngẫu nhiên để chỉ định một chữ cái ngẫu nhiên bằng cách gọi rand.Intn()
đại biểu Rand.Intn()
nào được ủy quyền Rand.Int31n()
.
Điều này chậm hơn nhiều so với việc rand.Int63()
tạo ra một số ngẫu nhiên với 63 bit ngẫu nhiên.
Vì vậy, chúng tôi chỉ có thể gọi rand.Int63()
và sử dụng phần còn lại sau khi chia cho 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)
}
Điều này hoạt động và nhanh hơn đáng kể, nhược điểm là xác suất của tất cả các chữ cái sẽ không hoàn toàn giống nhau (giả sử rand.Int63()
tạo ra tất cả các số 63 bit với xác suất bằng nhau). Mặc dù độ méo là cực kỳ nhỏ vì số lượng chữ cái 52
nhỏ hơn rất nhiều so với 1<<63 - 1
thực tế, nhưng trong thực tế, điều này là hoàn toàn tốt.
Để làm cho điều này dễ hiểu hơn: giả sử bạn muốn có một số ngẫu nhiên trong phạm vi 0..5
. Sử dụng 3 bit ngẫu nhiên, điều này sẽ tạo ra các số 0..1
có xác suất gấp đôi so với từ phạm vi 2..5
. Sử dụng 5 bit ngẫu nhiên, các số trong phạm vi 0..1
sẽ xảy ra với 6/32
xác suất và các số trong phạm vi 2..5
với 5/32
xác suất hiện gần với mong muốn. Việc tăng số lượng bit làm cho điều này ít quan trọng hơn, khi đạt tới 63 bit, nó không đáng kể.
Dựa trên giải pháp trước đó, chúng ta có thể duy trì phân phối các chữ cái bằng nhau bằng cách chỉ sử dụng càng nhiều bit thấp nhất của số ngẫu nhiên, càng nhiều số được yêu cầu để thể hiện số lượng chữ cái. Vì vậy, ví dụ nếu chúng ta có 52 chữ cái, nó cần 6 bit để thể hiện nó : 52 = 110100b
. Vì vậy, chúng tôi sẽ chỉ sử dụng 6 bit thấp nhất của số được trả về rand.Int63()
. Và để duy trì phân phối các chữ cái bằng nhau, chúng tôi chỉ "chấp nhận" số nếu nó nằm trong phạm vi 0..len(letterBytes)-1
. Nếu các bit thấp nhất lớn hơn, chúng tôi loại bỏ nó và truy vấn một số ngẫu nhiên mới.
Lưu ý rằng cơ hội của các bit thấp nhất lớn hơn hoặc bằng len(letterBytes)
nhỏ hơn so với 0.5
nói chung ( 0.25
trung bình), điều đó có nghĩa là ngay cả khi điều này xảy ra, việc lặp lại trường hợp "hiếm" này làm giảm cơ hội không tìm thấy hàng hóa con số. Sau khi n
lặp lại, cơ hội mà chúng ta không có chỉ số tốt ít hơn nhiều pow(0.5, n)
và đây chỉ là ước tính cao hơn. Trong trường hợp 52 chữ cái, khả năng 6 bit thấp nhất là không tốt (64-52)/64 = 0.19
; có nghĩa là ví dụ cơ hội không có số tốt sau 10 lần lặp lại là 1e-8
.
Vì vậy, đây là giải pháp:
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)
}
Giải pháp trước đó chỉ sử dụng 6 bit thấp nhất trong số 63 bit ngẫu nhiên được trả về rand.Int63()
. Đây là một sự lãng phí vì nhận được các bit ngẫu nhiên là phần chậm nhất trong thuật toán của chúng tôi.
Nếu chúng ta có 52 chữ cái, điều đó có nghĩa là 6 bit mã chỉ mục chữ cái. Vì vậy, 63 bit ngẫu nhiên có thể chỉ định 63/6 = 10
các chỉ số chữ cái khác nhau. Hãy sử dụng tất cả 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)
}
Mặt nạ cải tiến là khá tốt, chúng ta không thể cải thiện nó nhiều. Chúng ta có thể, nhưng không đáng để phức tạp.
Bây giờ hãy tìm một cái gì đó khác để cải thiện. Nguồn của các số ngẫu nhiên.
Có một crypto/rand
gói cung cấp một Read(b []byte)
hàm, vì vậy chúng ta có thể sử dụng gói đó để nhận được nhiều byte với một cuộc gọi mà chúng ta cần. Điều này sẽ không giúp ích gì về mặt hiệu suất khi crypto/rand
triển khai trình tạo số giả ngẫu nhiên được bảo mật bằng mật mã nên chậm hơn nhiều.
Vì vậy, hãy gắn bó với math/rand
gói. Việc rand.Rand
sử dụng rand.Source
như là nguồn của các bit ngẫu nhiên. rand.Source
là một giao diện chỉ định một Int63() int64
phương thức: chính xác và là thứ duy nhất chúng tôi cần và sử dụng trong giải pháp mới nhất của chúng tôi.
Vì vậy, chúng tôi không thực sự cần một rand.Rand
(rõ ràng hoặc toàn cầu, được chia sẻ một trong các rand
gói), một rand.Source
là hoàn toàn đủ cho chúng tôi:
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)
}
Cũng lưu ý rằng giải pháp cuối cùng này không yêu cầu bạn khởi tạo (hạt giống) toàn cầu Rand
của math/rand
gói vì nó không được sử dụng (và chúng tôi rand.Source
được khởi tạo / gieo hạt đúng cách).
Một điều nữa cần lưu ý ở đây: gói doc của các math/rand
trạng thái:
Nguồn mặc định là an toàn để sử dụng đồng thời bởi nhiều con khỉ đột.
Vì vậy, nguồn mặc định chậm hơn nguồn Source
có thể có được bởi rand.NewSource()
vì nguồn mặc định phải cung cấp sự an toàn theo truy cập / sử dụng đồng thời, trong khi rand.NewSource()
không cung cấp điều này (và do đó, Source
nó được trả về có nhiều khả năng nhanh hơn).
strings.Builder
Tất cả các giải pháp trước đó trả về một string
nội dung có nội dung đầu tiên được xây dựng trong một lát ( []rune
trong Genesis và []byte
trong các giải pháp tiếp theo), sau đó được chuyển đổi thành string
. Chuyển đổi cuối cùng này phải tạo một bản sao nội dung của lát cắt, bởi vì string
các giá trị là bất biến và nếu chuyển đổi không tạo ra một bản sao, không thể đảm bảo rằng nội dung của chuỗi không được sửa đổi thông qua lát cắt ban đầu. Để biết chi tiết, hãy xem Cách chuyển đổi chuỗi utf8 thành [] byte? và golang: [] byte (chuỗi) so với [] byte (* chuỗi) .
Giới thiệu 1.10 strings.Builder
. strings.Builder
một loại mới chúng ta có thể sử dụng để xây dựng nội dung string
tương tự bytes.Buffer
. Nó thực hiện bên trong bằng cách sử dụng a []byte
và khi hoàn thành, chúng ta có thể nhận được string
giá trị cuối cùng bằng Builder.String()
phương thức của nó . Nhưng điều thú vị ở đây là nó thực hiện điều này mà không thực hiện bản sao mà chúng ta vừa nói ở trên. Nó dám làm như vậy bởi vì lát cắt byte được sử dụng để xây dựng nội dung của chuỗi không bị lộ, do đó, đảm bảo rằng không ai có thể sửa đổi nó vô tình hoặc độc hại để thay đổi chuỗi "bất biến" được tạo ra.
Vì vậy, ý tưởng tiếp theo của chúng tôi là không xây dựng chuỗi ngẫu nhiên trong một lát, nhưng với sự trợ giúp của a strings.Builder
, vì vậy, khi chúng tôi hoàn thành, chúng tôi có thể lấy và trả về kết quả mà không cần phải sao chép chuỗi đó. Điều này có thể giúp về tốc độ, và nó chắc chắn sẽ giúp về mặt sử dụng và phân bổ bộ nhớ.
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()
}
Xin lưu ý rằng sau khi tạo mới strings.Buidler
, chúng tôi đã gọi Builder.Grow()
phương thức của nó , đảm bảo rằng nó phân bổ một lát nội bộ đủ lớn (để tránh phân bổ lại khi chúng tôi thêm các chữ cái ngẫu nhiên).
strings.Builder
với góiunsafe
strings.Builder
xây dựng chuỗi trong một nội bộ []byte
, giống như chúng ta đã làm. Vì vậy, về cơ bản thực hiện nó thông qua một strings.Builder
số chi phí, điều duy nhất chúng tôi chuyển sang strings.Builder
là để tránh việc sao chép cuối cùng của lát cắt.
strings.Builder
tránh bản sao cuối cùng bằng cách sử dụng gói unsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Vấn đề là, chúng ta cũng có thể tự làm điều này. Vì vậy, ý tưởng ở đây là chuyển trở lại để xây dựng chuỗi ngẫu nhiên trong một []byte
, nhưng khi chúng ta hoàn thành, đừng chuyển đổi nó string
thành trả về, nhưng thực hiện chuyển đổi không an toàn: lấy một string
điểm trỏ đến lát byte của chúng ta dưới dạng dữ liệu chuỗi .
Đây là cách nó có thể được thực hiện:
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()
)Đi 1.7 thêm một rand.Read()
chức năng và một Rand.Read()
phương thức. Chúng ta nên sử dụng chúng để đọc bao nhiêu byte mà chúng ta cần trong một bước, để đạt được hiệu suất tốt hơn.
Có một "vấn đề" nhỏ với điều này: chúng ta cần bao nhiêu byte? Chúng ta có thể nói: nhiều như số lượng chữ cái đầu ra. Chúng tôi sẽ nghĩ rằng đây là một ước tính trên, vì một chỉ mục chữ sử dụng ít hơn 8 bit (1 byte). Nhưng tại thời điểm này, chúng tôi đã làm tồi tệ hơn (vì nhận được các bit ngẫu nhiên là "phần cứng") và chúng tôi đang nhận được nhiều hơn mức cần thiết.
Cũng lưu ý rằng để duy trì phân phối đồng đều của tất cả các chỉ mục chữ cái, có thể có một số dữ liệu ngẫu nhiên "rác" mà chúng tôi sẽ không thể sử dụng, vì vậy chúng tôi sẽ bỏ qua một số dữ liệu và do đó kết thúc ngắn khi chúng tôi trải qua tất cả các lát byte. Chúng ta sẽ cần thêm các byte ngẫu nhiên, "đệ quy". Và giờ chúng ta thậm chí còn mất lợi thế "một cuộc gọi đến rand
gói" ...
Chúng tôi có thể "phần nào" tối ưu hóa việc sử dụng dữ liệu ngẫu nhiên mà chúng tôi có được math.Rand()
. Chúng tôi có thể ước tính cần bao nhiêu byte (bit). 1 chữ cái yêu cầu letterIdxBits
bit và chúng ta cần n
chữ cái, vì vậy chúng ta cần n * letterIdxBits / 8.0
byte làm tròn. Chúng tôi có thể tính xác suất của một chỉ số ngẫu nhiên không thể sử dụng được (xem ở trên), vì vậy chúng tôi có thể yêu cầu nhiều hơn "nhiều khả năng" là đủ (nếu nó không thành công, chúng tôi sẽ lặp lại quy trình). Ví dụ, chúng ta có thể xử lý lát byte dưới dạng "luồng bit", trong đó chúng ta có một lib bên thứ 3 đẹp: github.com/icza/bitio
(tiết lộ: Tôi là tác giả).
Nhưng mã điểm chuẩn vẫn cho thấy chúng ta không chiến thắng. Tại sao nó như vậy?
Câu trả lời cho câu hỏi cuối cùng là bởi vì rand.Read()
sử dụng một vòng lặp và tiếp tục gọi Source.Int63()
cho đến khi nó lấp đầy lát cắt đã qua. Chính xác những gì RandStringBytesMaskImprSrc()
giải pháp làm, không có bộ đệm trung gian và không có sự phức tạp thêm vào. Đó là lý do tại sao RandStringBytesMaskImprSrc()
vẫn còn trên ngai vàng. Có, RandStringBytesMaskImprSrc()
sử dụng không đồng bộ rand.Source
không giống như rand.Read()
. Nhưng lý luận vẫn được áp dụng; và được chứng minh nếu chúng ta sử dụng Rand.Read()
thay vì rand.Read()
(trước đây cũng không được đồng bộ hóa).
Được rồi, đã đến lúc điểm chuẩn các giải pháp khác nhau.
Khoảnh khắc của sự thật:
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
Chỉ bằng cách chuyển từ rune sang byte, chúng tôi ngay lập tức có hiệu suất tăng 24% và yêu cầu bộ nhớ giảm xuống còn một phần ba .
Loại bỏ rand.Intn()
và sử dụng rand.Int63()
thay vào đó giúp tăng thêm 20% .
Mặt nạ (và lặp lại trong trường hợp chỉ số lớn) chậm lại một chút (do các cuộc gọi lặp lại): -22% ...
Nhưng khi chúng ta sử dụng tất cả (hoặc hầu hết) trong số 63 bit ngẫu nhiên (10 chỉ số từ một rand.Int63()
cuộc gọi): sẽ tăng tốc thời gian lớn: 3 lần .
Nếu chúng tôi giải quyết bằng một (không mặc định, mới) rand.Source
thay vì rand.Rand
, chúng tôi lại nhận được 21%.
Nếu chúng ta sử dụng strings.Builder
, chúng ta đạt được một nhỏ 3,5% trong tốc độ , nhưng chúng tôi cũng đã đạt được 50% giảm sử dụng bộ nhớ và phân bổ! Thật tuyệt!
Cuối cùng, nếu chúng tôi dám sử dụng gói unsafe
thay vì strings.Builder
, chúng tôi lại nhận được 14% tốt đẹp .
So sánh trận chung kết các giải pháp ban đầu: RandStringBytesMaskImprSrcUnsafe()
là nhanh hơn 6,3 lần so với RandStringRunes()
, sử dụng một phần sáu bộ nhớ và một nửa số phân bổ . Nhiệm vụ đã hoàn thành.
rand.Source
được sử dụng. Một cách giải quyết tốt hơn sẽ được vượt qua một rand.Source
đến RandStringBytesMaskImprSrc()
chức năng, và như vậy không khóa là cần thiết và do đó hiệu suất / hiệu quả là không bị ảnh hưởng. Mỗi con goroutine có thể có cái riêng của nó Source
.
defer
khi rõ ràng là bạn không cần nó. Xem grokbase.com/t/gg/golang-nuts/158zz5p42w/
defer
để mở khóa một mutex ngay lập tức trước hoặc sau khi gọi khóa là IMO chủ yếu là một ý tưởng rất tốt; Bạn được đảm bảo cả hai không quên mở khóa mà còn mở khóa ngay cả trong chức năng trung gian hoảng loạn không gây tử vong.
Bạn chỉ có thể viết mã cho nó. Mã này có thể đơn giản hơn một chút nếu bạn muốn dựa vào tất cả các chữ cái là các byte đơn khi được mã hóa trong 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())
hoặcrand.Seed(time.Now().UnixNano())
math/rand
; thay vào đó, hãy sử dụng crypto/rand
(như @ Not_A_Golfer's tùy chọn 1).
Hai tùy chọn có thể (có thể có nhiều khóa học hơn):
Bạn có thể sử dụng crypto/rand
gói hỗ trợ đọc các mảng byte ngẫu nhiên (từ / dev / urandom) và hướng đến việc tạo ngẫu nhiên bằng mật mã. xem http://golang.org/pkg/crypto/rand/#example_Read . Nó có thể chậm hơn so với thế hệ số giả ngẫu nhiên bình thường.
Lấy một số ngẫu nhiên và băm nó bằng md5 hoặc một cái gì đó như thế này.
Sau icza's
giải pháp tuyệt vời được giải thích, đây là một sửa đổi của nó sử dụng crypto/rand
thay vì 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
}
Nếu bạn muốn một giải pháp chung hơn, cho phép bạn chuyển vào lát byte ký tự để tạo chuỗi ra, bạn có thể thử sử dụng cách này:
// 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)
}
Nếu bạn muốn vượt qua trong nguồn ngẫu nhiên của riêng mình, việc sửa đổi những điều trên để chấp nhận io.Reader
thay vì sử dụng là chuyện nhỏ crypto/rand
.
Nếu bạn muốn số ngẫu nhiên được bảo mật bằng mật mã và bộ ký tự chính xác là linh hoạt (giả sử, cơ sở 64 là tốt), bạn có thể tính chính xác độ dài của các ký tự ngẫu nhiên bạn cần từ kích thước đầu ra mong muốn.
Văn bản cơ sở 64 dài hơn 1/3 so với cơ sở 256. (tỷ lệ 2 ^ 8 so với 2 ^ 6; 8 tỷ / 6 bit = 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
}
Lưu ý: bạn cũng có thể sử dụng RawStdEncoding nếu bạn thích + và / ký tự cho - và _
Nếu bạn muốn hex, cơ sở 16 dài hơn 2 lần so với cơ sở 256. (tỷ lệ 2 ^ 8 so với 2 ^ 4; 8 bit / 4 bit = 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
}
Tuy nhiên, bạn có thể mở rộng nó thành bất kỳ bộ ký tự tùy ý nào nếu bạn có bộ mã hóa base256 sang baseN cho bộ ký tự của mình. Bạn có thể thực hiện phép tính kích thước tương tự với số lượng bit cần thiết để thể hiện bộ ký tự của bạn. Tính toán tỷ lệ cho bất kỳ bộ ký tự tùy ý là ratio = 8 / log2(len(charset))
:).
Mặc dù cả hai giải pháp này đều an toàn, đơn giản, nên nhanh chóng và không lãng phí nhóm entropy tiền điện tử của bạn.
Đây là sân chơi cho thấy nó hoạt động cho mọi kích cỡ. 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
?
Nếu bạn sẵn sàng thêm một vài ký tự vào nhóm ký tự được phép, bạn có thể làm cho mã hoạt động với bất kỳ thứ gì cung cấp byte ngẫu nhiên thông qua io.Reader. Ở đây chúng tôi đang sử dụng 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
cần thiết
len(encodeURL) == 64
. Nếu random % 64
không được thực hiện, randomPos
có thể> = 64 và gây ra sự hoảng loạn trong thời gian chạy.
/*
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))
}
Điểm chuẩnRandStr16-8 20000000 68,1 ns / op 16 B / op 1 allocs / op