欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

GoLang strings.Builder底層實現方法詳解

 更新時間:2022年10月12日 09:41:39   作者:~龐貝  
自從學習go一個月以來,我多少使用了一下strings.Builder,略有心得。你也許知道它,特別是你了解bytes.Buffer的話。所以我在此分享一下我的心得,并希望能對你有所幫助

1.strings.Builder結構體

1.1strings.Builder結構體

// 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é)省內存分配,它通過使用指針技術將內部的 buffer bytes 轉換為字符串。所以 String() 方法在轉換的時候節(jié)省了時間和空間。

// 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 類方法將數據寫入 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)

有了它們,用戶可以根據輸入數據的不同類型(byte 數組,byte, rune 或者 string),選擇對應的寫入方法。

3.存儲原理

根據用法說明,我們通過調用 string.Builder 的寫入方法來寫入內容,然后通過調用 String() 方法來獲取拼接的字符串。那么 string.Builder 是如何組織這些內容的呢?

通過 slice,string.Builder 通過使用一個內部的 slice 來存儲數據片段。當開發(fā)者調用寫入方法的時候,數據實際上是被追加(append)到了其內部的 slice 上。

4.拷貝問題

strings.Builder 不推薦被拷貝。當你試圖拷貝 strings.Builder 并寫入的時候,你的程序就會崩潰。

你已經知道,strings.Builder 內部通過 slice 來保存和管理內容。slice 內部則是通過一個指針指向實際保存內容的數組。 當我們拷貝了 builder 以后,同樣也拷貝了其 slice 的指針。但是它仍然指向同一個舊的數組。當你對源 builder 或者拷貝后的 builder 寫入的時候,問題就產生了。另一個 builder 指向的數組內容也被改變了。這就是為什么 strings.Builder 不允許拷貝的原因。

func main() {
	var b1 strings.Builder
	b1.WriteString("ABC")
	b2 := b1
	b2.WriteString("DEF")//出錯在這一行,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
}

但對于一個未寫入任何東西的空內容 builder 則是個例外。我們可以拷貝空內容的 builder 而不報錯。

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 會在以下方法中檢測拷貝操作:

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 是通過其內部的 slice 來儲存內容的。當你調用寫入方法的時候,新的字節(jié)數據就被追加到 slice 上。如果達到了 slice 的容量(capacity)限制,一個新的 slice 就會被分配,然后老的 slice 上的內容會被拷貝到新的 slice 上。當 slice 長度很大時,這個操作就會很消耗資源甚至引起 內存問題。我們需要避免這一情況。

關于 slice,Go 語言提供了 make([]TypeOfSlice, length, capacity) 方法在初始化的時候預定義它的容量。這就避免了因達到最大容量而引起擴容。

strings.Builder 同樣也提供了 Grow() 來支持預定義容量。當我們可以預定義我們需要使用的容量時,strings.Builder 就能避免擴容而創(chuàng)建新的 slice 了。

當調用 Grow() 時,我們必須定義要擴容的字節(jié)數(n)。 Grow() 方法保證了其內部的 slice 一定能夠寫入 n 個字節(jié)。只有當 slice 空余空間不足以寫入 n 個字節(jié)時,擴容才有可能發(fā)生。

舉個例子:

builder 內部 slice 容量為 10。

builder 內部 slice 長度為 5。

當我們調用 Grow(3) => 擴容操作并不會發(fā)生。因為當前的空余空間為 5,足以提供 3 個字節(jié)的寫入。

當我們調用 Grow(7) => 擴容操作發(fā)生。因為當前的空余空間為 5,已不足以提供 7 個字節(jié)的寫入。

關于上面的情形,如果這時我們調用 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 個字節(jié)。因為,你懂的,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 也不支持并行的讀或者寫。所以我們們要稍加注意。

可以試一下,通過同時給 strings.Builder 添加 1000 個字符:

通過運行,你會得到不同長度的結果。但它們都不到 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()))
	/*
			第一次運行輸出:946
		   第二次運行輸出:933
		 第三次運行輸出:900
	*/
}

到此這篇關于GoLang strings.Builder底層實現方法詳解的文章就介紹到這了,更多相關GoLang strings.Builder內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Golang并發(fā)繞不開的重要組件之Goroutine詳解

    Golang并發(fā)繞不開的重要組件之Goroutine詳解

    Goroutine、Channel、Context、Sync都是Golang并發(fā)編程中的幾個重要組件,這篇文中主要為大家介紹了Goroutine的相關知識,需要的可以參考一下
    2023-06-06
  • golang 占位符和fmt常見輸出介紹

    golang 占位符和fmt常見輸出介紹

    這篇文章主要介紹了golang 占位符和fmt常見輸出介紹,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go操作Kafka和Etcd方法詳解

    Go操作Kafka和Etcd方法詳解

    這篇文章主要為大家介紹了Go操作Kafka和Etcd方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-09-09
  • 詳解go 中的 fmt 占位符

    詳解go 中的 fmt 占位符

    這篇文章主要介紹了go 中的 fmt 占位符,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-01-01
  • Go語言類型轉換的方式有哪些

    Go語言類型轉換的方式有哪些

    本文主要介紹了Go語言類型轉換的方式有哪些,類型轉換主要有4種,分別為斷言類型轉換、顯式類型轉換、隱式類型轉換、強制類型轉換,感興趣的可以了解一下
    2023-11-11
  • go語言的sql包原理與用法分析

    go語言的sql包原理與用法分析

    這篇文章主要介紹了go語言的sql包原理與用法,較為詳細的分析了Go語言里sql包的結構、相關函數與使用方法,需要的朋友可以參考下
    2016-07-07
  • 簡單聊聊Golang中defer預計算參數

    簡單聊聊Golang中defer預計算參數

    在golang當中defer代碼塊會在函數調用鏈表中增加一個函數調用,下面這篇文章主要給大家介紹了關于Golang中defer預計算參數的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-03-03
  • Go語言入門教程之基礎語法快速入門

    Go語言入門教程之基礎語法快速入門

    這篇文章主要介紹了Go語言入門教程之基礎語法快速入門,本文講解了值類型、變量、常量、循環(huán)、條件語句、條件枚舉等內容,需要的朋友可以參考下
    2014-11-11
  • Golang中for循環(huán)的用法示例詳解

    Golang中for循環(huán)的用法示例詳解

    for循環(huán)就是讓一段代碼循環(huán)的執(zhí)行,接下來通過本文給大家講解Golang中for循環(huán)的用法,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-12-12
  • 詳解Go語言RESTful JSON API創(chuàng)建

    詳解Go語言RESTful JSON API創(chuàng)建

    這篇文章主要介紹了詳解Go語言RESTful JSON API創(chuàng)建,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05

最新評論