go--- strings 包与字符串操作

与 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()))
}	

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值