ฉันต้องการสตริงอักขระแบบสุ่มเท่านั้น (ตัวพิมพ์ใหญ่หรือตัวพิมพ์เล็ก) ไม่มีตัวเลขใน Go วิธีที่เร็วและง่ายที่สุดในการทำเช่นนี้คืออะไร?
ฉันต้องการสตริงอักขระแบบสุ่มเท่านั้น (ตัวพิมพ์ใหญ่หรือตัวพิมพ์เล็ก) ไม่มีตัวเลขใน Go วิธีที่เร็วและง่ายที่สุดในการทำเช่นนี้คืออะไร?
คำตอบ:
ทางออกของ Paulให้วิธีแก้ปัญหาที่ง่ายและทั่วไป
คำถามที่ถามสำหรับ"วิธีที่เร็วและง่ายที่สุด" มาพูดถึงส่วนที่เร็วที่สุดด้วย เราจะมาถึงรหัสสุดท้ายและเร็วที่สุดในลักษณะวนซ้ำ การเปรียบเทียบการทำซ้ำแต่ละครั้งสามารถพบได้ในตอนท้ายของคำตอบ
การแก้ปัญหาและการเปรียบเทียบรหัสที่สามารถพบได้บนไปสนามเด็กเล่น รหัสในสนามเด็กเล่นเป็นไฟล์ทดสอบไม่ใช่ไฟล์เรียกทำงาน คุณต้องบันทึกลงในไฟล์ชื่อXX_test.goและเรียกใช้ด้วย
go test -bench . -benchmem
คำนำ :
วิธีแก้ปัญหาที่เร็วที่สุดไม่ใช่วิธีแก้ปัญหาหากคุณต้องการสตริงแบบสุ่ม สำหรับวิธีการแก้ปัญหาของพอลที่สมบูรณ์แบบ นี่คือถ้าประสิทธิภาพไม่สำคัญ แม้ว่า 2 ขั้นตอนแรก ( ไบต์และส่วนที่เหลือ ) อาจเป็นการประนีประนอมที่ยอมรับได้: พวกเขาปรับปรุงประสิทธิภาพโดยเช่น 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เป็นค่าคงที่สตริง)
และราคาเท่าไหร่ ไม่มีอะไรทั้งนั้น. strings สามารถจัดทำดัชนีซึ่งดัชนีไบต์สมบูรณ์แบบสิ่งที่เราต้องการ
จุดหมายต่อไปของเราจะเป็นดังนี้:
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()ทุกประการ แม้ว่าการบิดเบือนจะน้อยมากเนื่องจากจำนวนตัวอักษร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)
}
การปรับปรุง Maskingค่อนข้างดีไม่มากที่เราสามารถปรับปรุงได้ เราทำได้ แต่ไม่คุ้มกับความซับซ้อน
ตอนนี้เราจะค้นหาสิ่งอื่นเพื่อปรับปรุง แหล่งที่มาของตัวเลขสุ่ม
มี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จะเริ่มต้น / เริ่มต้นอย่างเหมาะสม
อีกสิ่งที่ควรทราบที่นี่: package doc of math/randstates:
แหล่งที่มาเริ่มต้นมีความปลอดภัยสำหรับการใช้งานพร้อมกันหลาย goroutines
ดังนั้นแหล่งที่มาเริ่มต้นจะช้ากว่าSourceที่อาจจะได้รับโดยrand.NewSource()เพราะแหล่งที่มาเริ่มต้นที่มีการให้ความปลอดภัยภายใต้พร้อมกันการเข้าถึง / การใช้งานในขณะที่rand.NewSource()ไม่ได้นำเสนอนี้ (และทำให้Sourceกลับโดยจะมีแนวโน้มที่จะเร็ว)
strings.Builderการแก้ปัญหาก่อนหน้านี้ทั้งหมดกลับstringมีเนื้อหาที่ถูกสร้างขึ้นเป็นครั้งแรกในชิ้น ( []runeในปฐมกาลและ[]byteในการแก้ไขปัญหาที่ตามมา) stringและแปลงแล้ว การแปลงครั้งสุดท้ายนี้จะต้องทำสำเนาเนื้อหาของชิ้นเพราะstringค่าไม่เปลี่ยนรูปและหากการแปลงจะไม่ทำสำเนาก็ไม่สามารถรับประกันได้ว่าเนื้อหาของสตริงจะไม่แก้ไขผ่านชิ้นเดิม ดูรายละเอียดได้ที่วิธีแปลงสตริง utf8 เป็น [] ไบต์ และgolang [] ไบต์ (สตริง) VS [] ไบต์ (* สตริง)
ไป 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()
}
โปรดทราบว่าหลังจากสร้างใหม่strings.Buidlerแล้วเราเรียกBuilder.Grow()วิธีการของมันเพื่อให้แน่ใจว่าได้จัดสรรชิ้นส่วนภายในที่ใหญ่พอ (เพื่อหลีกเลี่ยงการจัดสรรใหม่เมื่อเราเพิ่มตัวอักษรแบบสุ่ม)
strings.Builderพร้อมแพ็คเกจunsafestrings.Builderสร้างสตริงในภายใน[]byteเช่นเดียวกับที่เราทำเอง โดยทั่วไปแล้วการทำมันผ่านทาง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กลับ แต่ทำการแปลงที่ไม่ปลอดภัย: รับ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 ไบต์) แต่ ณ จุดนี้เรากำลังทำแย่ลงไปอีก (เนื่องจากการสุ่มบิตคือ "ส่วนที่ยาก") และเราได้รับมากกว่าที่ต้องการ
นอกจากนี้โปรดทราบว่าเพื่อรักษาการกระจายตัวของดัชนีจดหมายทั้งหมดอย่างเท่าเทียมกันอาจมีข้อมูลสุ่ม "ขยะ" ที่เราไม่สามารถใช้งานได้ดังนั้นเราจะสิ้นสุดการข้ามข้อมูลบางอย่างและจบลงเมื่อเราผ่านทั้งหมด ชิ้นไบต์ เราจะต้องได้รับเพิ่มเติมสุ่ม bytes "ซ้ำ" และตอนนี้เรากำลังสูญเสียความrandได้เปรียบ "การโทรหาแพ็คเกจเดียว" ...
เราสามารถ "ค่อนข้าง" math.Rand()เพิ่มประสิทธิภาพการใช้งานของข้อมูลแบบสุ่มที่เราได้รับจาก เราอาจประมาณจำนวนไบต์ (บิต) ที่เราต้องการ 1 ตัวอักษรต้องใช้letterIdxBitsบิตและเราต้องการnตัวอักษรดังนั้นเราจึงจำเป็นต้องมีn * letterIdxBits / 8.0ไบต์ปัดเศษขึ้น เราสามารถคำนวณความน่าจะเป็นของดัชนีแบบสุ่มที่ไม่สามารถใช้งานได้ (ดูด้านบน) ดังนั้นเราจึงสามารถร้องขอได้มากขึ้นว่า "มีโอกาสมากขึ้น" ก็เพียงพอแล้ว (หากปรากฎว่าไม่ใช่ เราสามารถประมวลผลชิ้นส่วนของไบต์เป็น "สตรีมบิต" ซึ่งเรามี lib บุคคลที่สามที่ดี: 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%กำไรจากผลการดำเนินงานและความต้องการหน่วยความจำลดลงถึงหนึ่งในสาม
การกำจัด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()ฟังก์ชั่นและวิธีการไม่จำเป็นต้องล็อคและดังนั้นประสิทธิภาพ / ประสิทธิภาพจะไม่ได้รับผลกระทบ แต่ละ goroutine Sourceอาจจะมีเป็นของตัวเอง
deferเมื่อเห็นได้ชัดว่าคุณไม่ต้องการมัน ดูgrokbase.com/t/gg/golang-nuts/158zz5p42w/…
deferการปลดล็อก mutex ทั้งก่อนหรือหลังการโทรล็อคเป็น 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) แทน
สองตัวเลือกที่เป็นไปได้ (อาจมีมากกว่า):
ต่อไปนี้icza'sการแก้ปัญหาที่อธิบายไว้อย่างน่าพิศวงและนี่คือการเปลี่ยนแปลงของมันที่ใช้แทนcrypto/randmath/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.Readercrypto/rand
หากคุณต้องการตัวเลขสุ่มแบบเข้ารหัสที่ปลอดภัยและชุดอักขระที่แน่นอนมีความยืดหยุ่น (เช่น base64 ใช้ได้) คุณสามารถคำนวณความยาวของอักขระสุ่มที่คุณต้องการจากขนาดเอาต์พุตที่ต้องการ
ข้อความ 64 ฐานคือ 1/3 ยาวกว่าฐาน 256 (2 ^ 8 กับ 2 ^ 6; 8bits / 6bits = อัตราส่วน 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 นั้นยาวกว่าฐาน 256 2 เท่า (2 ^ 8 เทียบกับ 2 ^ 4; 8bits / 4bits = 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 เป็น baseN encoder สำหรับชุดอักขระของคุณ คุณสามารถทำการคำนวณขนาดเดียวกันกับจำนวนบิตที่ต้องการเพื่อแสดงชุดอักขระของคุณ การคำนวณอัตราส่วน charset โดยพลการใด ๆ ที่เป็น: ratio = 8 / log2(len(charset)))
แม้ว่าวิธีแก้ปัญหาเหล่านี้ทั้งสองจะมีความปลอดภัยง่าย ๆ ควรรวดเร็วและไม่ทำให้เสียเอนโทรปีของ crypto
นี่คือสนามเด็กเล่นที่แสดงว่าใช้ได้กับทุกขนาด 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?
นี่คือวิธีของฉัน) ใช้แรนด์คณิตศาสตร์หรือ crypto แรนด์ตามที่คุณต้องการ
func randStr(len int) string {
buff := make([]byte, len)
rand.Read(buff)
str := base64.StdEncoding.EncodeToString(buff)
// Base 64 can be longer than len
return str[:len]
}
หากคุณยินดีที่จะเพิ่มอักขระบางตัวลงในกลุ่มอักขระที่อนุญาตคุณสามารถทำให้โค้ดทำงานกับทุกสิ่งที่มีการสุ่มไบต์ผ่าน 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 และทำให้เกินขอบเขตที่ตื่นตระหนกขณะรันไทม์
/*
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 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