Go 语言核心 36 讲
郝林
《Go 并发编程实战》作者,前轻松筹大数据负责人
79610 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 55 讲
Go 语言核心 36 讲
15
15
1.0x
00:00/00:00
登录|注册

37 | strings包与字符串操作

操作冲突和并发安全问题
不可复制
重用值
手动扩容
自动扩容策略
拼接方法
高效利用内存
内容容器
字符串拼接的内存分配压力
字节数组存储
string值的不可变性
可将内容重置,可重用值
减少了内存分配和内容拷贝的次数
已存在的内容不可变,但可以拼接更多的内容
思考题
Reader值的高效读取
使用约束
Builder值的优势
Seek方法
方法更新已读计数
读取机制
已读计数
高效读取字符串
使用约束
Builder值的优势
与string值的比较
优势
总结
strings.Reader类型
strings.Builder类型
unicode/utf8包
unicode包
for语句
rune类型
UTF-8编码格式
Unicode编码规范
strings包与字符串操作
参考文章

该思维导图由 AI 生成,仅供参考

在上一篇文章中,我介绍了 Go 语言与 Unicode 编码规范、UTF-8 编码格式的渊源及运用。
Go 语言不但拥有可以独立代表 Unicode 字符的类型rune,而且还有可以对字符串值进行 Unicode 字符拆分的for语句。
除此之外,标准库中的unicode包及其子包还提供了很多的函数和数据类型,可以帮助我们解析各种内容中的 Unicode 字符。
这些程序实体都很好用,也都很简单明了,而且有效地隐藏了 Unicode 编码规范中的一些复杂的细节。我就不在这里对它们进行专门的讲解了。
我们今天主要来说一说标准库中的strings代码包。这个代码包也用到了不少unicode包和unicode/utf8包中的程序实体。
比如,strings.Builder类型的WriteRune方法。
又比如,strings.Reader类型的ReadRune方法,等等。
下面这个问题就是针对strings.Builder类型的。我们今天的问题是:与string值相比,strings.Builder类型的值有哪些优势?
这里的典型回答是这样的。
strings.Builder类型的值(以下简称Builder值)的优势有下面的三种:
已存在的内容不可变,但可以拼接更多的内容;
减少了内存分配和内容拷贝的次数;
可将内容重置,可重用值。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Go语言中的strings包提供了丰富的字符串操作功能,本文主要介绍了strings.Builder和strings.Reader两个重要类型。strings.Builder类型在字符串拼接方面具有优势,能够在保持内容不变的前提下进行高效的字符串拼接,同时具有严格的使用约束。另一方面,strings.Reader类型则专注于高效地读取字符串内容,通过内部的已读计数实现快速读取和位置设定。文章还提到了这两个类型的使用约束和优势,以及strings包中其他有用的函数。总的来说,本文通过对strings.Builder和strings.Reader的介绍,清晰地展现了strings包在字符串操作中的优势和特点。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 语言核心 36 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(16)

  • 最新
  • 精选
  • Realm
    1 string拼接的结果是生成新的string,需要把原字符串拷贝到新的string中;Builder底层有个[]byte,按需扩容,不必每次拼接都需要拷贝; 2 Reader的优势是维护一个已读计数器,知道下一次读的位置,读得更快.

    作者回复: 嗯,是的。

    2018-11-05
    37
  • jimmy
    strings.Builder里边的String方法是 // String returns the accumulated string. func (b *Builder) String() string { return *(*string)(unsafe.Pointer(&b.buf)) } 这样实现的, 请问老师为什么不是 // String returns the accumulated string. func (b *Builder) String() string { return string(b.buf) } 有什么特殊的点吗? 谢谢

    作者回复: 省去了类型转换的开销,效率会高很多。

    2019-01-17
    14
  • Garry
    老师,我在看strings 源码的时候发现了 func noescape(p unsafe.Pointer) unsafe.Pointer { x := uintptr(p) return unsafe.Pointer(x ^ 0) } 这个函数 最后用了个x ^ 0,但是这么操作的最后结果不还是x么,为何还要这样操作呢

    作者回复: 为了产生一个新值啊,要跟这个函数的参数值划清界限。

    2019-04-02
    2
    6
  • Geek_a8be59
    看到源码有处不理解如下: func noescape(p unsafe.Pointer) unsafe.Pointer { x := uintptr(p) return unsafe.Pointer(x ^ 0) } 这个方法的意思避免逃逸分析,不太理解请指教? 第一:为什么经过这么转换会避免逃逸? 第二:避免逃逸有什么好处,既然会逃逸肯定会到heap上,如果避免逃逸那这个变量怎么使用呢,或者说是这样再stack上又分配了一个新的变量么?

    作者回复: 第一个问题: 因为这个函数的结果值是一个在“内部”生成的新值,不再与那个参数值有任何关系。关键在于 uintptr 的中转。 首先你得理解,逃逸分析是什么。Go语言的编译器如果发现一个goroutine中的程序持有存放在其他goroutine的堆栈中的指针,那么就会把这个指针指向的值分配到堆上。这么做主要是为了方便goroutine堆栈的自动伸缩。 注意,把值分配到堆上会给GC带来更大压力。因为堆的区域是公共的,不能像goroutine堆栈那样(在goroutine运行完成后)被自动清除。 第二个问题: 避免逃逸分析这种做法只应该在Go语言内部使用。因为这是一把双刃剑。它可以避免增加GC的压力,但是如果使用不当,在运行时系统伸缩goroutine堆栈时就会出问题。 逃逸分析之所以称为分析,是因为它有一个分析的过程。这不是在程序运行时做的,而是在编译时做的(决定一个值是分配在当前goroutine的堆栈上还是分配在公共的堆上),所以不存在拷贝来拷贝去的问题。

    2020-07-23
    5
  • 乖,摸摸头
    strings.Reader这里我一直有个疑问, 它读写 很对地方都是 if r.i >= int64(len(r.s)) { return 0, nil } 为什么在 strings.NewReader 的时候 不直接求出 len(r.s)的长度,而是每次去算长度,这样不会有性能浪费吗?

    作者回复: 你是在说“为什么每次都len(r.s),而不把长度信息存储在某个地方”么? 这样其实根本不会浪费性能。因为字符串值中本身就存着字符串的长度,详见Go语言源码文件src/runtime/string.go中的类型定义stringStruct。 况且,把这种信息记录在reader内会造成额外的维护成本。

    2020-03-10
    2
    4
  • 博博
    Builder类型中的addr *Builder 字段的意义是什么呢?

    作者回复: 这个 addr 字段的意义是,保存其所属值所在的内存地址。如此一来,一旦这个值被拷贝了,使用内存地址比较的方式就可以检测出来。

    2019-05-22
    2
    2
  • kingkang
    请问byte数组转string出现乱码怎么处理?

    作者回复: 如果字节数组的内容不是UTF-8编码的Unicode字符,这样直接转就会出现乱码。先要搞清楚两个问题:1. 这个字节数组的内容会是可打印的字符吗?2. 如果是可打印的字符,那它使用什么编码的?

    2019-01-04
    2
  • lesserror
    郝林老师,麻烦看看以下问题: 1. "不过,由于string值的不可变,其中的指针值也为内存空间的节省做出了贡献"。 这句话该怎么理解呢? 2. 文中的这段代码: f2 := func(bp *strings.Builder) { (*bp).Grow(1) // 这里虽然不会引发panic,但不是并发安全的。 builder4 := *bp //builder4.Grow(1) // 这里会引发panic。 _ = builder4 } f2(&builder1) 不是说:“虽然已使用的Builder值不能再被复制,但是它的指针值却可以。” 那这段代码:builder4.Grow(1) 。 为何还会引发panic呢?

    作者回复: builder4 := *bp 会把 bp 指向的原值复制一份并赋给变量 builder4。当我们调用 (*bp).Grow(1) 时调用的还是原值的方法,但是调用 builder4.Grow(1) 就是在调用原值的副本的方法了。非空的 strings.Builder 值是禁止被复制的,所以副本在发现自己是副本之后抛出了 panic 。

    2021-08-24
    1
  • lesserror
    郝林老师,能分析以下这段代码的执行步骤吗?没怎么看懂: *(*string)(unsafe.Pointer(&b.buf))

    作者回复: unsafe.Pointer(&b.buf) -> 得到一个 Pointer 值 -> 将这个 Pointer 值的类型转换为 *string(即 string 的指针类型) -> 在这个 *string 类型的指针值之上求值(也就是求它指向的那个值) -> 得到一个 string 类型的值 (即一个字符串值)

    2021-08-24
  • 虢國技醬
    二刷了一遍,又看了一遍源码;我觉得对于Builder和Reader理解应该注意: 1,结构: 1.1 Builder结构体内部内容容器是一个切片buf还有一个addr(复制检测用的指针) 1.2 Reader结构体内部内容容器是一个string的s和一个内部计数器i 2. Builder 2.1 想法方法内部先调用copyCheck方法进行值复制的检测(即老师说的使用后在复制引发panic就是这个方法) 2.2 内容容器是切片,相关拼接方法内部应用的是append函数,这些方法使用时间可以结合slice和append的原理 2.3 公开方法Grow进行是否扩容判断逻辑,然后调用内部方法grow执行切片扩容,扩容策略:原内容容器切片容量 * 2 + Grow参数n;用这个容量make申请新的内存空间,然后copy原内容容器切片底层数组值 3. Reader 3.1 读取方法底层是对内容容器s字符串的切片操作,这里要注意在多字节字符读取时,字符串的切片操作可能会导致拿到的字符串有乱码的风险, 3.2 对于Read、ReadAt这些将字符串读取到传入的切片参数时,底层应用的是copy函数,so最终读出的字符串字节切片长度是copy函数两个参数中较小的一个参数的长度。同时Read、ReadAt这些方法的off参数不恰当时,会因为多字节字符串切片导致两头可能出现乱码
    2019-12-09
    20
收起评论
显示
设置
留言
16
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部