Go語(yǔ)言中字符串賦值中的問(wèn)題與解決方法
字符串的拼接方式
使用 + 號(hào)
使用 + 號(hào)拼接字符串的方式,每次拼接都會(huì)創(chuàng)建一個(gè)新的字符串,然后將原來(lái)的字符串復(fù)制到新的字符串中,這樣會(huì)導(dǎo)致大量的內(nèi)存分配和復(fù)制操作,性能較差。
字符串格式化函數(shù) fmt.Sprintf 函數(shù)
預(yù)分配 bytes.Buffer 緩沖區(qū)
func BufferCapResize() {
var str = "abcd"
var buf bytes.Buffer
// 預(yù)分配內(nèi)存
buf.Grow(4 * 10000) // 如果沒(méi)有這一行,當(dāng)長(zhǎng)度不夠了就會(huì)擴(kuò)容
cap := 0
for i := 0; i < 10000; i++ {
if buf.Cap() != cap {
println("cap:", buf.Cap())
cap = buf.Cap()
}
buf.WriteString(str)
}
}
- 預(yù)分配
strings.Builder構(gòu)建器 - 預(yù)分配
[]byte - 使用
strings.Join函數(shù)
strings.Builder 和 bytes.Buffer 底層都是一個(gè)字節(jié)數(shù)組,但是 bytes.Buffer 在轉(zhuǎn)換字符串的時(shí)候,需要重新申請(qǐng)內(nèi)存空間,而strings.Builder 是直接將底層的 bytes 轉(zhuǎn)換成字符串進(jìn)行返回
string(b.buf[b.off:])直接強(qiáng)轉(zhuǎn)unsafe.String(unsafe.Slice(b.buf), len(b.buf))零拷貝轉(zhuǎn)換
字符串內(nèi)存泄露
對(duì)字符進(jìn)行截取時(shí)指向同一塊內(nèi)存空間
如何避免:
- 將子字符串轉(zhuǎn)換成字節(jié)切片,在轉(zhuǎn)成
string - 截取后再前面拼接一個(gè)新字符串
- 使用
strings.Builder對(duì)新字符串進(jìn)行重新構(gòu)造
定義一個(gè)很長(zhǎng)的字符串 s := strings.Repeat("a", 1<<20),
賦值
① ② ④ 打印的是同一個(gè)地址 ③ 打印的是一個(gè) nil
字符串在賦值的時(shí)候不會(huì)發(fā)生拷貝,只是改變底層的指針指向
原始的字符串 s 即使被重新賦值為空字符串,但是 s2 依然指向原來(lái)的字符串,所以原始的地址不會(huì)被釋放
func main() {
ptr := (*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Println("s pointer:", unsafe.Pointer(ptr.Data)) // ① 0xc000180000
Assign()
}
func Assign() {
s2 := s
ptr := (*reflect.StringHeader)(unsafe.Pointer(&s2))
fmt.Println("Assign:", unsafe.Pointer(ptr.Data)) // ② 0xc000180000
s := ""
ptr = (*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Println("s pointer", unsafe.Pointer(ptr.Data)) // ③ nil
ptr = (*reflect.StringHeader)(unsafe.Pointer(&s2))
fmt.Println("Assign", unsafe.Pointer(ptr.Data)) // ④ 0xc000180000
_ = s2
}
通過(guò)引用賦值
不管是通過(guò)引用賦值,還是值賦值,最終都是指向同一個(gè)地址
func main() {
ptr := (*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Println("s pointer:", unsafe.Pointer(ptr.Data)) // ① 0xc000180000
AssignPointer()
}
func AssignPointer() {
s2 := &s // 通過(guò)引用賦值
ptr := (*reflect.StringHeader)(unsafe.Pointer(s2))
fmt.Println("AssignPointer:", unsafe.Pointer(ptr.Data)) // ② 0xc000180000
_ = s2
}
字符串截取
s2 是截取 s 字符串的前 20 位,這樣 s2 和 s 的起始地址是一樣的
這種解決很容易導(dǎo)致內(nèi)存泄露,因?yàn)樽址?s 申請(qǐng)的空間是非常大的,s 在不使用的情況下,也是不會(huì)被回收的,因?yàn)?s2 指向了 s 的地址
func main() {
ptr := (*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Println("s pointer:", unsafe.Pointer(ptr.Data)) // ① 0xc000100000
StringSlice()
}
func StringSlice() {
s2 := s[:20]
ptr := (*reflect.StringHeader)(unsafe.Pointer(&s2))
fmt.Println("StringSlice:", unsafe.Pointer(ptr.Data)) // ② 0xc000100000
_ = s2
}
字符串傳遞
字符串傳遞到函數(shù)內(nèi)部,不管是指針傳遞還是值傳遞,字符串實(shí)際內(nèi)容在內(nèi)存中的地址是相同的
func main() {
ptr := (*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Println("s pointer:", unsafe.Pointer(ptr.Data)) // ① 0x49b7ba
f1(s)
f2(&s)
}
func f1(s string) string {
ptr := unsafe.StringData(s)
fmt.Println("f1:", ptr) // ② 0x49b7ba
return s
}
func f2(s *string) *string {
ptr := unsafe.StringData(*s)
fmt.Println("f2:", ptr) // ③ 0x49b7ba
return s
}
你可能會(huì)發(fā)現(xiàn),網(wǎng)上說(shuō)傳遞指針才不會(huì)發(fā)生拷貝,傳遞值是會(huì)發(fā)生拷貝,但為什么現(xiàn)在無(wú)論是傳遞指針還是傳遞值,字符串都沒(méi)有發(fā)生拷貝
這是因?yàn)?&s 打印的是函數(shù)參數(shù) s 在棧上的地址,每次函數(shù)調(diào)用都會(huì)不同
func main() {
ptr := (*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Println("s pointer:", unsafe.Pointer(ptr.Data)) // ① 0x49b7ba
fmt.Println("s 地址:", &s) // 0x528650
f1(s)
f2(&s)
}
func f1(s string) string {
fmt.Println("f1 s:", &s) // 0xc000014070
ptr := unsafe.StringData(s)
fmt.Println("f1:", ptr) // 0x49b7ba
return s
}
func f2(s *string) *string {
fmt.Println("f2 s:", s) // 0x528650
ptr := unsafe.StringData(*s)
fmt.Println("f2:", ptr) // 0x49b7ba
return s
}
改變字符串地址的方式
1.強(qiáng)轉(zhuǎn)
func StringSlice1(s string) string {
fmt.Println("string:", unsafe.StringData(s)) // 0xc000100000
s1 := string([]byte(s[:20]))
ptr := unsafe.StringData(s1)
fmt.Println("StringSlice1:", ptr) // 0xc0000bc000
return s1
}
2.改變首字符,就能改變 s1 的地址
func StringSlice2(s string) string {
fmt.Println("string:", unsafe.StringData(s)) // 0xc000100000
s1 := (" " + s[:20])[1:]
ptr := unsafe.StringData(s1)
fmt.Println("StringSlice2:", ptr) // 0xc000018199
return s1
}
3.使用 StringsBuilder 改變 s1 的地址
func StringSliceUseBuilder(s string) string {
fmt.Println("string:", unsafe.StringData(s)) // 0xc000100000
var b strings.Builder
b.Grow(20)
b.WriteString(s[:20])
s1 := b.String()
ptr := unsafe.StringData(s1)
fmt.Println("StringSliceUseBuilder:", ptr) // 0xc0000b0000
return s1
}
字符切換零拷貝轉(zhuǎn)換
雖然同是切片操作,但是 s1 會(huì)改變地址,而 s2 不會(huì)改變地址
s1強(qiáng)轉(zhuǎn)為字符串的指針類型s2:先對(duì)s進(jìn)行取指,取指之后將它轉(zhuǎn)成字符串切片指針類型,然后在獲取指針的內(nèi)容
所以 s2 的方法是零拷貝轉(zhuǎn)換
func main() {
ptr := (*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Println("s pointer:", unsafe.Pointer(ptr.Data)) // 0xc000180000
}
func stringToBytes() {
s1 := []byte(s)
fmt.Println("s1: ", unsafe.SliceData(s1)) // 0xc000280000
s2 := *(*[]byte)(unsafe.Pointer(&s))
fmt.Println("s2: ", unsafe.SliceData(s2)) // 0xc000180000
}
到此這篇關(guān)于Go語(yǔ)言中字符串賦值中的問(wèn)題與解決方法的文章就介紹到這了,更多相關(guān)Go字符串賦值內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析Go語(yǔ)言的數(shù)據(jù)類型及數(shù)組
Golang是一種靜態(tài)強(qiáng)類型、編譯型語(yǔ)言。Go?語(yǔ)言語(yǔ)法與?C?相近,但功能上有:內(nèi)存安全,GC(垃圾回收),結(jié)構(gòu)形態(tài)及?CSP-style?并發(fā)計(jì)算。本文主要和大家聊聊Go語(yǔ)言的數(shù)據(jù)類型及數(shù)組,希望對(duì)大家有所幫助2022-11-11
Go語(yǔ)言中JWT的創(chuàng)建和解析操作詳解
JWT的全名是Json web token,是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開(kāi)放標(biāo)準(zhǔn),這篇文章主要介紹了在Go語(yǔ)言中JWT的創(chuàng)建和解析操作,感興趣的同學(xué)可以參考下文2023-05-05
Go語(yǔ)言學(xué)習(xí)之?dāng)?shù)組的用法詳解
數(shù)組是相同數(shù)據(jù)類型的一組數(shù)據(jù)的集合,數(shù)組一旦定義長(zhǎng)度不能修改,數(shù)組可以通過(guò)下標(biāo)(或者叫索引)來(lái)訪問(wèn)元素。本文將通過(guò)示例詳細(xì)講解Go語(yǔ)言中數(shù)組的使用,需要的可以參考一下2022-04-04
go協(xié)程池實(shí)現(xiàn)原理小結(jié)
本文主要介紹了go協(xié)程池實(shí)現(xiàn)原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04
go語(yǔ)言使用jwt認(rèn)證的實(shí)現(xiàn)
本文主要介紹了go語(yǔ)言使用jwt認(rèn)證的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
基于Go語(yǔ)言實(shí)現(xiàn)Base62編碼的三種方式以及對(duì)比分析
Base62 編碼是一種在字符編碼中使用62個(gè)字符的編碼方式,在計(jì)算機(jī)科學(xué)中,,Go語(yǔ)言是一種靜態(tài)類型、編譯型語(yǔ)言,它由Google開(kāi)發(fā)并開(kāi)源,本文給大家介紹了Go語(yǔ)言實(shí)現(xiàn)Base62編碼的三種方式以及對(duì)比分析,需要的朋友可以參考下2025-05-05

