Golang 中 strings.builder 的 7 個要點

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。自從 Go 1.10 發布的一個月以來,我多少使用了一下 `strings.Builder`,略有心得。你也許知道它,特別是你瞭解 `bytes.Buffer` 的話。所以我在此分享一下我的心得,並希望能對你有所協助。## 1. 4 類寫入(write)方法與 `bytes.Buffer` 類似,`strings.Builder` 也支援 4 類方法將資料寫入 builder 中。```gofunc (b *Builder) Write(p []byte) (int, error)func (b *Builder) WriteByte(c byte) errorfunc (b *Builder) WriteRune(r rune) (int, error)func (b *Builder) WriteString(s string) (int, error)```有了它們,使用者可以根據輸入資料的不同類型(byte 數組,byte, rune 或者 string),選擇對應的寫入方法。![four-forms-of-writing-methods](https://raw.githubusercontent.com/studygolang/gctt-images/master/strings-builder/1_IGv0x1gMwkszbv7IWnpfaQ.png)## 2. 字串的儲存原理根據用法說明,我們通過調用 `string.Builder` 的寫入方法來寫入內容,然後通過調用 `String()` 方法來擷取拼接的字串。那麼 `string.Builder` 是如何組織這些內容的呢?**通過 slice**`string.Builder` 通過使用一個內部的 slice 來儲存資料片段。當開發人員調用寫入方法的時候,資料實際上是被追加(append)到了其內部的 slice 上。![slice-store-data](https://raw.githubusercontent.com/studygolang/gctt-images/master/strings-builder/1_luRaetJ4m36JH43xh0rHcA.png)## 3. 高效地使用 strings.Builder根據上面第 2 點可以知道,strings.Builder 是通過其內部的 slice 來儲存內容的。當你調用寫入方法的時候,新的位元組資料就被追加到 slice 上。如果達到了 slice 的容量(capacity)限制,一個新的 slice 就會被分配,然後老的 slice 上的內容會被拷貝到新的 slice 上。當 slice 長度很大時,這個操作就會很消耗資源甚至引起 [記憶體問題](https://blog.siliconstraits.com/out-of-memory-with-append-in-golang-956e7eb2c70e)。我們需要避免這一情況。關於 slice,Go 語言提供了 `make([]TypeOfSlice, length, capacity)` 方法在初始化的時候預定義它的容量。這就避免了因達到最大容量而引起擴容。`strings.Builder` 同樣也提供了 `Grow()` 來支援預定義容量。當我們可以預定義我們需要使用的容量時,`strings.Builder` 就能避免擴容而建立新的 slice 了。```gofunc (b *Builder) Grow(n int)```當調用 `Grow()` 時,我們必須定義要擴容的位元組數(`n`)。 `Grow()` 方法保證了其內部的 slice 一定能夠寫入 `n` 個位元組。只有當 slice 空餘空間不足以寫入 `n` 個位元組時,擴容才有可能發生。舉個例子:* builder 內部 slice 容量為 10。* builder 內部 slice 長度為 5。* 當我們調用 `Grow(3)` => 擴容操作並不會發生。因為當前的空餘空間為 5,足以提供 3 個位元組的寫入。* 當我們調用 `Grow(7)` => 擴容操作發生。因為當前的空餘空間為 5,已不足以提供 7 個位元組的寫入。關於上面的情形,如果這時我們調用 `Grow(7)`,則擴容之後的實際容量是多少?```17 還是 12?```實際上,是 `27`。`strings.Builder` 的 `Grow()` 方法是通過 `current_capacity * 2 + n` (`n` 就是你想要擴充的容量)的方式來對內部的 slice 進行擴容的。所以說最後的容量是 `10*2+7` = `27`。當你預定義 `strings.Builder` 容量的時候還要注意一點。調用 `WriteRune()` 和 `WriteString()` 時,`rune` 和 `string` 的字元可能不止 1 個位元組。因為,你懂的,[UTF-8](https://golang.org/pkg/unicode/utf8/#pkg-constants) 的原因。## 4. String()和 `bytes.Buffer` 一樣,`strings.Builder` 也支援使用 `String()` 來擷取最終的字串結果。為了節省記憶體配置,它通過使用指標技術將內部的 buffer bytes 轉換為字串。所以 `String()` 方法在轉換的時候節省了時間和空間。```go*(*string)(unsafe.Pointer(&bytes))```## 5. 不要拷貝![do-not-copy](https://raw.githubusercontent.com/studygolang/gctt-images/master/strings-builder/1_a4IwPDq3tEJJ_FRZfhreyQ.png)`strings.Builder` 不推薦被拷貝。當你試圖拷貝 `strings.Builder` 並寫入的時候,你的程式就會崩潰。```govar b1 strings.Builderb1.WriteString("ABC")b2 := b1b2.WriteString("DEF") // illegal use of non-zero Builder copied by value```你已經知道,`strings.Builder` 內部通過 slice 來儲存和管理內容。slice 內部則是通過一個指標指向實際儲存內容的數組。![slice-internally](https://raw.githubusercontent.com/studygolang/gctt-images/master/strings-builder/1_KD02pGfasisf8I_BWE_JKQ.png)當我們拷貝了 builder 以後,同樣也拷貝了其 slice 的指標。但是它仍然指向同一箇舊的數組。當你對源 builder 或者拷貝後的 builder 寫入的時候,問題就產生了。另一個 builder 指向的數組內容也被改變了。這就是為什麼 `strings.Builder` 不允許拷貝的原因。![copy-and-write](https://raw.githubusercontent.com/studygolang/gctt-images/master/strings-builder/1_Ppak_h63S_TvYzJa2sFCpA.png)對於一個未寫入任何東西的空內容 builder 則是個例外。我們可以拷貝空內容的 builder 而不報錯。```govar b1 strings.Builderb2 := b1b2.WriteString("DEF")b1.WriteString("ABC")// b1 = ABC, b2 = DEF````strings.Builder` 會在以下方法中檢測拷貝操作:```goGrow(n int)Write(p []byte)WriteRune(r rune)WriteString(s string)```所以,拷貝並使用下列這些方法是允許的:```go// Reset()// Len()// String()var b1 strings.Builderb1.WriteString("ABC")b2 := b1fmt.Println(b2.Len()) // 3fmt.Println(b2.String()) // ABCb2.Reset()b2.WriteString("DEF")fmt.Println(b2.String()) // DEF```## 6. 並行支援和 `bytes.Buffer` 一樣,`strings.Builder` 也不支援並行的讀或者寫。所以我們們要稍加註意。可以試一下,通過同時給 `strings.Builder` 添加 `1000` 個字元:```gopackage mainimport ("fmt""strings""sync")func main() {var b strings.Buildern := 0var wait sync.WaitGroupfor n < 1000 {wait.Add(1)go func() {b.WriteString("1")n++wait.Done()}()}wait.Wait()fmt.Println(len(b.String()))}```通過運行,你會得到不同長度的結果。但它們都不到 `1000`。## 7. io.Writer 介面`strings.Builder` 通過 `Write(p []byte) (n int, err error)` 方法實現了 `io.Writer` 介面。所以,我們多了很多使用它的情形:* `io.Copy(dst Writer, src Reader) (written int64, err error)`* `bufio.NewWriter(w io.Writer) *Writer`* `fmt.Fprint(w io.Writer, a …interface{}) (n int, err error)`* `func (r *http.Request) Write(w io.Writer) error`* 其他使用 io.Writer 的庫![io-writer](https://raw.githubusercontent.com/studygolang/gctt-images/master/strings-builder/1_MhBcQBYT4ocfA7ftVT2iGw.png)

via: https://medium.com/@thuc/8-notes-about-strings-builder-in-golang-65260daae6e9

作者:Thuc Le 譯者:alfred-zhong 校對:rxcai

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

1061 次點擊  
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.