与 string 值相比,strings.Builder 类型的值的优势:
- 已存在的内容不可变,但可以拼接更多的内容;
- 减少了内存分配和内容拷贝的次数;
- 可将内容重置,可重用值。
与 string 值相比,Builder 值的优势主要体现在字符串拼接方面。
package main
import (
"fmt"
"strings"
)
func main() {
// 拼接方法
// 已存在于 Builder 值中的内容不可变,可以拼接更多的内容
var builder1 strings.Builder
builder1.WriteString("A builder is used to efficiently build a string using Write methods.")
fmt.Printf("The first ouput(%d):\n%q\n", builder1.Len(), builder1.String())
fmt.Println()
builder1.WriteByte(' ')
builder1.WriteString("It minimizes memory copying. The zero value is ready to use.")
builder1.Write([]byte{'\n', '\n'})
builder1.WriteString("Do not copy a non-zero Builder.")
fmt.Printf("The second output(%d):\n\"%s\"\n", builder1.Len(), builder1.String())
fmt.Println()
// Grow 方法会生成一个字节切片作为新的内容容器,容量为原容器容量的二倍加 n
// 然后把原容器中的所有字节全部拷贝到新容器中
// Grow 方法也可能什么都不做,前提是:当前容器中的未用容量 >= n
fmt.Println("Grow the builder ...")
builder1.Grow(10)
fmt.Printf("The length of contents in the builder is %d.\n", builder1.Len())
fmt.Println()
// Reset 方法可以让 Builder 值重回到零值状态,就像从未被使用过
fmt.Println("Reset the builder ...")
builder1.Reset()
fmt.Printf("The third output(%d):\n%q\n", builder1.Len(), builder1.String())
}
strings.Builder 使用时的约束
package main
import (
"strings"
)
func main() {
var builder1 strings. Builder
builder1.Grow(1) // 已经使用了 builder1
// Builder 使用后不应该复制
// 否则,在任何副本上调用方法都会引发 panic
// 是为了避免多个同源的 Builder 值在拼接内容时产生冲突
f1 := func(b strings.Builder) {
// b.Grow(1) // 会引发 panic
}
f1(builder1)
ch1 := make(chan strings.Builder, 1)
ch1 <- builder1
builder2 := <-ch1
//builder2.Grow(1) // 会引发 panic
_ = builder2
builder3 := builder1
//builder3.Grow(1) // 会引发 panic
_ = builder3
// 已使用的 Builder 的指针值可以复制
// 但因此产生的操作冲突需自行避免
f2 := func(bp *strings.Builder) {
(*bp).Grow(1) // 不会引发 panic,但不是并发安全的
builder4 := *bp
//builder4.Grow(1) // 会引发 panic
_ = builder4
}
f2(&builder1)
// 已使用的 Builder 值在复制前调用 Reset() 方法即可
builder1.Reset()
builder5 := builder1
builder5.Grow(1) // 不会引发 panic
}
strings.Read 类型的值可以被高效读取的原因
Reader 值实现高效读取的关键在于它内部的已读计数。计数值代表下一次读取的起始索引位置。
package main
import (
"strings"
"fmt"
"io"
)
func main() {
// Reader 值会保存已读取的字节的计数
// 已读计数代表着下一次读取的起始索引位置
reader1 := strings.NewReader(
"NewReader returns a new Reader reading from s. " +
"It is similar to bytes.NewBuffersString but more efficient and read-only.")
fmt.Printf("The size of reader: %d\n", reader1.Size())
// 计算出的已读计数
fmt.Printf("The reading index in reader: %d\n\n", reader1.Size() - int64(reader1.Len()))
buf1 := make([]byte, 47)
n, _ := reader1.Read(buf1)
fmt.Printf("%d bytes were read. (call Read)\n", n)
fmt.Printf("The reading index in reader: %d\n", reader1.Size() - int64(reader1.Len()))
fmt.Println()
// ReadAt 既不会依据已读计数进行读取,也不会在读取后更新它
// 因此可以自由地读取所属 Reader 值中的任何内容
buf2 := make([]byte, 21)
offset1 := int64(64)
n, _ = reader1.ReadAt(buf2, offset1)
fmt.Printf("%d bytes were read. (call ReadAt, offset: %d)\n", n, offset1)
fmt.Printf("The reading index in reader: %d\n", reader1.Size() - int64(reader1.Len()))
fmt.Println()
// Reader 值的 Seek 方法也会更新该值的已读计数
// Seek 的主要作用是设定下一次读取的起始索引位置
// 如果将 io.SeekCurrent 的值作为第二个参数,还有依据当前的已读计数
// 以及第一个参数 offset 的值计数新的计数值
offset2 := int64(17)
expectedIndex := reader1.Size() - int64(reader1.Len()) + offset2
fmt.Printf("Seek with offset %d and whence %d ...\n", offset2, io.SeekCurrent)
readingIndex, _ := reader1.Seek(offset2, io.SeekCurrent)
fmt.Printf("The reading index in reader: %d (returned by Seek)\n", readingIndex)
fmt.Printf("The reading index in reader: %d (computed by me)\n\n", expectedIndex)
n, _ = reader1.Read(buf2)
fmt.Printf("%d bytes were read. (call Read)\n", n)
fmt.Printf("The reading index in reader: %d\n", reader1.Size() - int64(reader1.Len()))
}