Go에서 임의의 문자열 만 (대문자 또는 소문자), 숫자 없음을 원합니다. 가장 빠르고 간단한 방법은 무엇입니까?
Go에서 임의의 문자열 만 (대문자 또는 소문자), 숫자 없음을 원합니다. 가장 빠르고 간단한 방법은 무엇입니까?
답변:
Paul의 솔루션 은 단순 하고 일반적인 솔루션을 제공합니다.
질문은 "가장 빠르고 간단한 방법"을 요구 합니다. 가장 빠른 부분도 다루겠습니다 . 반복적 인 방식으로 가장 빠른 최종 코드에 도달합니다. 각 반복 벤치마킹은 답변 끝에서 찾을 수 있습니다.
모든 솔루션과 벤치마킹 코드는 Go Playground 에서 찾을 수 있습니다 . 놀이터의 코드는 테스트 파일이며 실행 파일이 아닙니다. 당신은라는 이름의 파일로 저장해야 XX_test.go
하고 그것을 실행
go test -bench . -benchmem
서문 :
임의의 문자열 만 필요한 경우 가장 빠른 솔루션은 사용하지 않는 솔루션입니다. 이를 위해 Paul의 솔루션은 완벽합니다. 성능이 중요한 경우입니다. 처음 두 단계 ( Bytes and Remainder )는 허용 가능한 타협 일 수 있지만 성능을 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)
}
임의의 문자열에서 선택하고 어셈블 할 문자에 영어 알파벳의 대문자와 소문자 만 포함 된 경우 UTF-8 인코딩에서 영어 알파벳 문자가 1에서 1로 바이트로 매핑되므로 바이트 만 사용할 수 있습니다. Go가 문자열을 저장하는 방법입니다).
따라서 대신 :
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
우리는 사용할 수 있습니다 :
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
또는 더 나은 :
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
이제 이것은 이미 큰 개선입니다 : 우리는 그것을 달성 할 수 있습니다 const
( string
상수는 있지만 슬라이스 상수는 없습니다 ). 추가 이득으로 표현 len(letters)
도 const
! ( 문자열 상수 인 len(s)
경우 표현식 은 s
상수입니다.)
그리고 어떤 비용으로? 전혀 없습니다. string
바이트를 인덱싱 할 수 있습니다. 정확히 원하는만큼 완벽합니다.
다음 목적지는 다음과 같습니다.
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)
}
이전 솔루션은에서 반환 한 63 개의 임의 비트 중 가장 낮은 6 비트 만 사용합니다 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
패키지 중 하나를 공유 하는) 실제로 필요하지 않습니다 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
제대로 시드 / 초기화).
여기서 주목할 사항이 하나 더 있습니다 : package doc of math/rand
states :
기본 소스는 여러 고 루틴이 동시에 사용할 수 있습니다.
따라서 기본 소스는 동시 액세스 / 사용시 안전을 제공해야하지만 이를 제공하지 않기 때문에 기본 소스는에 Source
의해 얻을 수있는 것보다 느립니다 (따라서 반환되는 것이 더 빠를 가능성이 높습니다).rand.NewSource()
rand.NewSource()
Source
strings.Builder
이전의 모든 솔루션은 돌려 string
그 내용이 먼저 슬라이스에 내장되어 있습니다 ( []rune
에 창세기 , 그리고 []byte
이후의 솔루션), 다음으로 변환 string
. 이 최종 변환은 string
값을 변경할 수 없기 때문에 슬라이스 내용의 사본을 만들어야하며 , 변환이 사본을 만들지 않으면 문자열 내용이 원래 슬라이스를 통해 수정되지 않을 수도 있습니다. 자세한 내용은 utf8 문자열을 [] 바이트로 변환하는 방법을 참조하십시오 . 및 golang [] 바이트 (문자열)] 바이트 (* 문자열) 대 .
이동 1.10 도입 strings.Builder
. 와 비슷한 strings.Builder
내용을 만드는 데 사용할 수있는 새로운 유형 입니다. 내부적으로를 사용하여 수행하며 완료되면 해당 값을 사용하여 최종 값을 얻을 수 있습니다.string
bytes.Buffer
[]byte
string
Builder.String()
방법을 . 그러나 멋진 점은 위에서 언급 한 사본을 수행하지 않고이 작업을 수행한다는 것입니다. 문자열의 내용을 구성하는 데 사용 된 바이트 슬라이스가 노출되지 않으므로 감히 또는 악의적으로 생성 된 "불변의"문자열을 변경하기 위해 수정할 수 없다는 것이 보장됩니다.
따라서 우리의 다음 아이디어는 슬라이스에 임의의 문자열을 작성하는 것이 아니라 a의 도움으로 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()
}
new을 만든 후에 strings.Buidler
는 Builder.Grow()
메소드를 호출 하여 임의의 문자를 추가 할 때 재 할당을 피하기 위해 충분히 큰 내부 슬라이스를 할당해야합니다.
strings.Builder
패키지unsafe
strings.Builder
[]byte
우리와 마찬가지로 내부에 문자열을 작성합니다 . 따라서 기본적으로 a strings.Builder
를 통해 수행하는 것은 약간의 오버 헤드가 있습니다. 우리가 전환 한 유일한 것은 strings.Builder
슬라이스의 최종 복사를 피하는 것입니다.
strings.Builder
package를 사용하여 최종 사본을 피하십시오 unsafe
.
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
문제는 우리도 직접 할 수 있다는 것입니다. 그래서 여기서 아이디어는에서 임의의 문자열을 빌드하는 것으로 다시 전환하는 []byte
것이지만, 완료되면 string
반환 하도록 변환하지 말고 안전하지 않은 변환을 수행하십시오 : 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
바이트 반올림이 필요 합니다. 우리는 무작위 인덱스가 사용 가능하지 않을 확률을 계산할 수 있으므로 (위 참조) "더 많을 것"만으로도 더 많은 것을 요청할 수 있습니다 (그렇지 않으면 프로세스를 반복합니다). 예를 들어 바이트 슬라이스를 "비트 스트림"으로 처리 할 수 있습니다. 여기에는 멋진 타사 라이브러리가 있습니다.github.com/icza/bitio
(공개 : 저자입니다).
그러나 벤치 마크 코드는 여전히 우리가 이기지 못하고 있음을 보여줍니다. 왜 그래야만하지?
마지막 질문에 대한 대답은 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 %의 성능 향상을 얻을 수 있으며 메모리 요구 사항은 1/3로 감소 .
제거 rand.Intn()
하고 rand.Int63()
대신 사용하면 20 %가 추가로 발생합니다 증가합니다.
마스킹 (그리고 큰 지수의 경우 반복)은 약간의 속도 저하 (반복 호출로 인해) : -22 % ...
그러나 63 개의 랜덤 비트 (한 번의 rand.Int63()
호출 에서 10 개의 인덱스)를 모두 (또는 대부분) 사용하면 시간이 3 배 빨라집니다 .
rand.Source
대신 ( 기본이 아닌 새로운)로 정착하면 rand.Rand
다시 21 % 증가합니다.
우리가 사용하는 경우 strings.Builder
, 우리는 작은 이득 3.5 %를 에 속도 , 그러나 우리는 또한 달성 50 %의 메모리 사용 및 할당에 감소! 멋지다!
마지막으로 우리가 unsafe
대신에 패키지를 사용한다면 strings.Builder
, 다시 14 %를 얻을 수 있습니다.
초기 용액의 최종 비교 : RandStringBytesMaskImprSrcUnsafe()
인 6.3 배 빠르게 보다 RandStringRunes()
사용 육분의 일 메모리 및 거의 절반을 할당 . 임무 완수.
rand.Source
가 사용 되기 때문에 @RobbieV Yup . 더 나은 해결 방법은을 통과하는 것입니다 rand.Source
받는 RandStringBytesMaskImprSrc()
기능, 그리고 어떤 잠금이 필요하지 않은 방식 때문에 성능 / 효율은 영향을받지 않습니다. 각 goroutine은 자체적으로 가질 수 있습니다 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 텍스트는 기본 256보다 1/3 더 깁니다. (2 ^ 8 vs 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 진법을 원하면 16 진법은 256 진법보다 2 배 더 깁니다. (2 ^ 8 vs 2 ^ 4; 8 비트 / 4 비트 = 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
}
그러나 문자 집합에 대한 base256 to baseN 인코더가있는 경우 임의의 문자 집합으로 확장 할 수 있습니다. 문자 집합을 나타내는 데 필요한 비트 수로 동일한 크기 계산을 수행 할 수 있습니다. 임의의 문자 집합에 대한 비율 계산은 다음과 같습니다. 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))
}
벤치 마크 LandStr16-8 20000000 68.1 ns / op 16 B / op 1 allocs / op