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() 來獲取最終的字符串結(jié)果。為了節(jié)省內(nèi)存分配,它通過使用指針技術(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 類似,strings.Builder 也支持 4 類方法將數(shù)據(jù)寫入 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)
有了它們,用戶可以根據(jù)輸入數(shù)據(jù)的不同類型(byte 數(shù)組,byte, rune 或者 string),選擇對(duì)應(yīng)的寫入方法。
3.存儲(chǔ)原理
根據(jù)用法說明,我們通過調(diào)用 string.Builder 的寫入方法來寫入內(nèi)容,然后通過調(diào)用 String() 方法來獲取拼接的字符串。那么 string.Builder 是如何組織這些內(nèi)容的呢?
通過 slice,string.Builder 通過使用一個(gè)內(nèi)部的 slice 來存儲(chǔ)數(shù)據(jù)片段。當(dāng)開發(fā)者調(diào)用寫入方法的時(shí)候,數(shù)據(jù)實(shí)際上是被追加(append)到了其內(nèi)部的 slice 上。
4.拷貝問題
strings.Builder 不推薦被拷貝。當(dāng)你試圖拷貝 strings.Builder 并寫入的時(shí)候,你的程序就會(huì)崩潰。
你已經(jīng)知道,strings.Builder 內(nèi)部通過 slice 來保存和管理內(nèi)容。slice 內(nèi)部則是通過一個(gè)指針指向?qū)嶋H保存內(nèi)容的數(shù)組。 當(dāng)我們拷貝了 builder 以后,同樣也拷貝了其 slice 的指針。但是它仍然指向同一個(gè)舊的數(shù)組。當(dāng)你對(duì)源 builder 或者拷貝后的 builder 寫入的時(shí)候,問題就產(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è)未寫入任何東西的空內(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ì)在以下方法中檢測拷貝操作:
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 是通過其內(nèi)部的 slice 來儲(chǔ)存內(nèi)容的。當(dāng)你調(diào)用寫入方法的時(shí)候,新的字節(jié)數(shù)據(jù)就被追加到 slice 上。如果達(dá)到了 slice 的容量(capacity)限制,一個(gè)新的 slice 就會(huì)被分配,然后老的 slice 上的內(nèi)容會(huì)被拷貝到新的 slice 上。當(dāng) slice 長度很大時(shí),這個(gè)操作就會(huì)很消耗資源甚至引起 內(nèi)存問題。我們需要避免這一情況。
關(guān)于 slice,Go 語言提供了 make([]TypeOfSlice, length, capacity) 方法在初始化的時(shí)候預(yù)定義它的容量。這就避免了因達(dá)到最大容量而引起擴(kuò)容。
strings.Builder 同樣也提供了 Grow() 來支持預(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 空余空間不足以寫入 n 個(gè)字節(jié)時(shí),擴(kuò)容才有可能發(fā)生。
舉個(gè)例子:
builder 內(nèi)部 slice 容量為 10。
builder 內(nèi)部 slice 長度為 5。
當(dāng)我們調(diào)用 Grow(3) => 擴(kuò)容操作并不會(huì)發(fā)生。因?yàn)楫?dāng)前的空余空間為 5,足以提供 3 個(gè)字節(jié)的寫入。
當(dāng)我們調(diào)用 Grow(7) => 擴(kuò)容操作發(fā)生。因?yàn)楫?dāng)前的空余空間為 5,已不足以提供 7 個(gè)字節(jié)的寫入。
關(guān)于上面的情形,如果這時(shí)我們調(diào)用 Grow(7),則擴(kuò)容之后的實(shí)際容量是多少?
17 還是 12?
實(shí)際上,是 27。strings.Builder 的 Grow() 方法是通過 current_capacity * 2 + n (n 就是你想要擴(kuò)充的容量)的方式來對(duì)內(nèi)部的 slice 進(jìn)行擴(kuò)容的。所以說最后的容量是 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.不支持并行讀寫
和 bytes.Buffer 一樣,strings.Builder 也不支持并行的讀或者寫。所以我們們要稍加注意。
可以試一下,通過同時(shí)給 strings.Builder 添加 1000 個(gè)字符:
通過運(yùn)行,你會(huì)得到不同長度的結(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ā)繞不開的重要組件之Goroutine詳解
Goroutine、Channel、Context、Sync都是Golang并發(fā)編程中的幾個(gè)重要組件,這篇文中主要為大家介紹了Goroutine的相關(guān)知識(shí),需要的可以參考一下2023-06-06簡單聊聊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)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03詳解Go語言RESTful JSON API創(chuàng)建
這篇文章主要介紹了詳解Go語言RESTful JSON API創(chuàng)建,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05