GoLang strings.Builder底層實(shí)現(xiàn)方法詳解
1.strings.Builder結(jié)構(gòu)體
1.1strings.Builder結(jié)構(gòu)體
// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
addr *Builder // of receiver, to detect copies by value
buf []byte
}
1.2Write方法
// Write appends the contents of p to b's buffer.
// Write always returns len(p), nil.
func (b *Builder) Write(p []byte) (int, error) {
b.copyCheck()
b.buf = append(b.buf, p...)
return len(p), nil
}
1.3WriteByte方法
// WriteByte appends the byte c to b's buffer.
// The returned error is always nil.
func (b *Builder) WriteByte(c byte) error {
b.copyCheck()
b.buf = append(b.buf, c)
return nil
}
1.4WriteRune方法
// WriteRune appends the UTF-8 encoding of Unicode code point r to b's buffer.
// It returns the length of r and a nil error.
func (b *Builder) WriteRune(r rune) (int, error) {
b.copyCheck()
// Compare as uint32 to correctly handle negative runes.
if uint32(r) < utf8.RuneSelf {
b.buf = append(b.buf, byte(r))
return 1, nil
}
l := len(b.buf)
if cap(b.buf)-l < utf8.UTFMax {
b.grow(utf8.UTFMax)
}
n := utf8.EncodeRune(b.buf[l:l+utf8.UTFMax], r)
b.buf = b.buf[:l+n]
return n, nil
}
1.5.WriteString方法
// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *Builder) WriteString(s string) (int, error) {
b.copyCheck()
b.buf = append(b.buf, s...)
return len(s), nil
}
1.6String方法
和 bytes.Buffer一樣,strings.Builder 也支持使用 String() 來(lái)獲取最終的字符串結(jié)果。為了節(jié)省內(nèi)存分配,它通過(guò)使用指針技術(shù)將內(nèi)部的 buffer bytes 轉(zhuǎn)換為字符串。所以 String() 方法在轉(zhuǎn)換的時(shí)候節(jié)省了時(shí)間和空間。
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
1.7Len方法
// Len returns the number of accumulated bytes; b.Len() == len(b.String()).
func (b *Builder) Len() int { return len(b.buf) }
1.8Cap方法
// Cap returns the capacity of the builder's underlying byte slice. It is the
// total space allocated for the string being built and includes any bytes
// already written.
func (b *Builder) Cap() int { return cap(b.buf) }
1.9Reset方法
// Reset resets the Builder to be empty.
func (b *Builder) Reset() {
b.addr = nil
b.buf = nil
}
1.10Grow方法
// Grow grows b's capacity, if necessary, to guarantee space for
// another n bytes. After Grow(n), at least n bytes can be written to b
// without another allocation. If n is negative, Grow panics.
func (b *Builder) Grow(n int) {
b.copyCheck()
if n < 0 {
panic("strings.Builder.Grow: negative count")
}
if cap(b.buf)-len(b.buf) < n {
b.grow(n)
}
}
1.11grow方法
// grow copies the buffer to a new, larger buffer so that there are at least n
// bytes of capacity beyond len(b.buf).
func (b *Builder) grow(n int) {
buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
copy(buf, b.buf)
b.buf = buf
}
1.12copyCheck方法
func (b *Builder) copyCheck() {
if b.addr == nil {
// This hack works around a failing of Go's escape analysis
// that was causing b to escape and be heap allocated.
// See issue 23382.
// TODO: once issue 7921 is fixed, this should be reverted to
// just "b.addr = b".
b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
} else if b.addr != b {
panic("strings: illegal use of non-zero Builder copied by value")
}
}
2.strings.Builder介紹
與 bytes.Buffer 類(lèi)似,strings.Builder 也支持 4 類(lèi)方法將數(shù)據(jù)寫(xiě)入 builder 中。
func (b *Builder) Write(p []byte) (int, error)
func (b *Builder) WriteByte(c byte) error
func (b *Builder) WriteRune(r rune) (int, error)
func (b *Builder) WriteString(s string) (int, error)
有了它們,用戶(hù)可以根據(jù)輸入數(shù)據(jù)的不同類(lèi)型(byte 數(shù)組,byte, rune 或者 string),選擇對(duì)應(yīng)的寫(xiě)入方法。
3.存儲(chǔ)原理
根據(jù)用法說(shuō)明,我們通過(guò)調(diào)用 string.Builder 的寫(xiě)入方法來(lái)寫(xiě)入內(nèi)容,然后通過(guò)調(diào)用 String() 方法來(lái)獲取拼接的字符串。那么 string.Builder 是如何組織這些內(nèi)容的呢?
通過(guò) slice,string.Builder 通過(guò)使用一個(gè)內(nèi)部的 slice 來(lái)存儲(chǔ)數(shù)據(jù)片段。當(dāng)開(kāi)發(fā)者調(diào)用寫(xiě)入方法的時(shí)候,數(shù)據(jù)實(shí)際上是被追加(append)到了其內(nèi)部的 slice 上。
4.拷貝問(wèn)題
strings.Builder 不推薦被拷貝。當(dāng)你試圖拷貝 strings.Builder 并寫(xiě)入的時(shí)候,你的程序就會(huì)崩潰。
你已經(jīng)知道,strings.Builder 內(nèi)部通過(guò) slice 來(lái)保存和管理內(nèi)容。slice 內(nèi)部則是通過(guò)一個(gè)指針指向?qū)嶋H保存內(nèi)容的數(shù)組。 當(dāng)我們拷貝了 builder 以后,同樣也拷貝了其 slice 的指針。但是它仍然指向同一個(gè)舊的數(shù)組。當(dāng)你對(duì)源 builder 或者拷貝后的 builder 寫(xiě)入的時(shí)候,問(wèn)題就產(chǎn)生了。另一個(gè) builder 指向的數(shù)組內(nèi)容也被改變了。這就是為什么 strings.Builder 不允許拷貝的原因。
func main() {
var b1 strings.Builder
b1.WriteString("ABC")
b2 := b1
b2.WriteString("DEF")//出錯(cuò)在這一行,panic: strings: illegal use of non-zero Builder copied by value
}
func main() {
var b1 strings.Builder
b1.WriteString("ABC")
b2 := b1
fmt.Println(b2.String())//ABC
}
func main() {
var b1 strings.Builder
b1.WriteString("ABC")
b2 := b1
fmt.Println(b1.String()) //輸出:ABC
fmt.Println(b2.String()) //輸出:ABC
b1.WriteString("DEF")
fmt.Println(b1.String()) //輸出:ABCDEF
fmt.Println(b2.String()) //輸出:ABC
}
但對(duì)于一個(gè)未寫(xiě)入任何東西的空內(nèi)容 builder 則是個(gè)例外。我們可以拷貝空內(nèi)容的 builder 而不報(bào)錯(cuò)。
func main() {
var b1 strings.Builder
b2 := b1
fmt.Println(b1.String()) //輸出空行
fmt.Println(b2.String()) //輸出空行
b2.WriteString("DEF")
fmt.Println(b1.String()) //輸出空行
fmt.Println(b2.String()) //輸出:DEF
b1.WriteString("ABC")
fmt.Println(b1.String()) //輸出:ABC
fmt.Println(b2.String()) //輸出:DEF
}
strings.Builder 會(huì)在以下方法中檢測(cè)拷貝操作:
Grow(n int)
Write(p []byte)
WriteRune(r rune)
WriteString(s string)
所以,拷貝并使用下列這些方法是允許的:
func main() {
// Reset()
// Len()
// String()
var b1 strings.Builder
b1.WriteString("ABC")
b2 := b1
fmt.Println(b2.Len()) // 3
fmt.Println(b2.String()) // ABC
b2.Reset()
b2.WriteString("DEF")
fmt.Println(b2.String()) // DEF
}
5.不能與nil作比較

6.Grow深入
strings.Builder 是通過(guò)其內(nèi)部的 slice 來(lái)儲(chǔ)存內(nèi)容的。當(dāng)你調(diào)用寫(xiě)入方法的時(shí)候,新的字節(jié)數(shù)據(jù)就被追加到 slice 上。如果達(dá)到了 slice 的容量(capacity)限制,一個(gè)新的 slice 就會(huì)被分配,然后老的 slice 上的內(nèi)容會(huì)被拷貝到新的 slice 上。當(dāng) slice 長(zhǎng)度很大時(shí),這個(gè)操作就會(huì)很消耗資源甚至引起 內(nèi)存問(wèn)題。我們需要避免這一情況。
關(guān)于 slice,Go 語(yǔ)言提供了 make([]TypeOfSlice, length, capacity) 方法在初始化的時(shí)候預(yù)定義它的容量。這就避免了因達(dá)到最大容量而引起擴(kuò)容。
strings.Builder 同樣也提供了 Grow() 來(lái)支持預(yù)定義容量。當(dāng)我們可以預(yù)定義我們需要使用的容量時(shí),strings.Builder 就能避免擴(kuò)容而創(chuàng)建新的 slice 了。
當(dāng)調(diào)用 Grow() 時(shí),我們必須定義要擴(kuò)容的字節(jié)數(shù)(n)。 Grow() 方法保證了其內(nèi)部的 slice 一定能夠?qū)懭?n 個(gè)字節(jié)。只有當(dāng) slice 空余空間不足以寫(xiě)入 n 個(gè)字節(jié)時(shí),擴(kuò)容才有可能發(fā)生。
舉個(gè)例子:
builder 內(nèi)部 slice 容量為 10。
builder 內(nèi)部 slice 長(zhǎng)度為 5。
當(dāng)我們調(diào)用 Grow(3) => 擴(kuò)容操作并不會(huì)發(fā)生。因?yàn)楫?dāng)前的空余空間為 5,足以提供 3 個(gè)字節(jié)的寫(xiě)入。
當(dāng)我們調(diào)用 Grow(7) => 擴(kuò)容操作發(fā)生。因?yàn)楫?dāng)前的空余空間為 5,已不足以提供 7 個(gè)字節(jié)的寫(xiě)入。
關(guān)于上面的情形,如果這時(shí)我們調(diào)用 Grow(7),則擴(kuò)容之后的實(shí)際容量是多少?
17 還是 12?
實(shí)際上,是 27。strings.Builder 的 Grow() 方法是通過(guò) current_capacity * 2 + n (n 就是你想要擴(kuò)充的容量)的方式來(lái)對(duì)內(nèi)部的 slice 進(jìn)行擴(kuò)容的。所以說(shuō)最后的容量是 10*2+7 = 27。 當(dāng)你預(yù)定義 strings.Builder 容量的時(shí)候還要注意一點(diǎn)。調(diào)用 WriteRune() 和 WriteString() 時(shí),rune 和 string 的字符可能不止 1 個(gè)字節(jié)。因?yàn)椋愣?,UTF-8 的原因。
func main() {
var b1 strings.Builder
fmt.Println(b1.Len()) //0
fmt.Println(b1.Cap()) //0
b1.Grow(3)
fmt.Println(b1.Len()) //0
fmt.Println(b1.Cap()) //3
b1.Grow(1)
fmt.Println(b1.Len()) //0
fmt.Println(b1.Cap()) //3
}
func main() {
a := strings.Builder{}
a.Grow(11)
fmt.Println(a.Len()) //0
fmt.Println(a.Cap()) //11
a.WriteRune('李')
a.WriteRune('陸')
a.WriteRune('豪')
a.WriteRune('Z')
a.WriteRune('Z')
fmt.Println(a.Len()) //11
fmt.Println(a.Cap()) //11
}
7.不支持并行讀寫(xiě)
和 bytes.Buffer 一樣,strings.Builder 也不支持并行的讀或者寫(xiě)。所以我們們要稍加注意。
可以試一下,通過(guò)同時(shí)給 strings.Builder 添加 1000 個(gè)字符:
通過(guò)運(yùn)行,你會(huì)得到不同長(zhǎng)度的結(jié)果。但它們都不到 1000。
func main() {
var b strings.Builder
n := 0
var wait sync.WaitGroup
for n < 1000 {
wait.Add(1)
go func() {
b.WriteString("1")
n++
wait.Done()
}()
}
wait.Wait()
fmt.Println(len(b.String()))
/*
第一次運(yùn)行輸出:946
第二次運(yùn)行輸出:933
第三次運(yùn)行輸出:900
*/
}
到此這篇關(guān)于GoLang strings.Builder底層實(shí)現(xiàn)方法詳解的文章就介紹到這了,更多相關(guān)GoLang strings.Builder內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang并發(fā)繞不開(kāi)的重要組件之Goroutine詳解
Goroutine、Channel、Context、Sync都是Golang并發(fā)編程中的幾個(gè)重要組件,這篇文中主要為大家介紹了Goroutine的相關(guān)知識(shí),需要的可以參考一下2023-06-06
Go語(yǔ)言類(lèi)型轉(zhuǎn)換的方式有哪些
本文主要介紹了Go語(yǔ)言類(lèi)型轉(zhuǎn)換的方式有哪些,類(lèi)型轉(zhuǎn)換主要有4種,分別為斷言類(lèi)型轉(zhuǎn)換、顯式類(lèi)型轉(zhuǎn)換、隱式類(lèi)型轉(zhuǎn)換、強(qiáng)制類(lèi)型轉(zhuǎn)換,感興趣的可以了解一下2023-11-11
簡(jiǎn)單聊聊Golang中defer預(yù)計(jì)算參數(shù)
在golang當(dāng)中defer代碼塊會(huì)在函數(shù)調(diào)用鏈表中增加一個(gè)函數(shù)調(diào)用,下面這篇文章主要給大家介紹了關(guān)于Golang中defer預(yù)計(jì)算參數(shù)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03
Go語(yǔ)言入門(mén)教程之基礎(chǔ)語(yǔ)法快速入門(mén)
這篇文章主要介紹了Go語(yǔ)言入門(mén)教程之基礎(chǔ)語(yǔ)法快速入門(mén),本文講解了值類(lèi)型、變量、常量、循環(huán)、條件語(yǔ)句、條件枚舉等內(nèi)容,需要的朋友可以參考下2014-11-11
詳解Go語(yǔ)言RESTful JSON API創(chuàng)建
這篇文章主要介紹了詳解Go語(yǔ)言RESTful JSON API創(chuàng)建,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05

