37 | strings包与字符串操作
37 | strings包与字符串操作
讲述:黄洲君
时长12:59大小5.94M
问题解析
知识扩展
问题 1:strings.Builder类型在使用上有约束吗?
问题 2:为什么说strings.Reader类型的值可以高效地读取字符串?
总结
思考题
赞 10
提建议
精选留言(16)
- Realm2018-11-051 string拼接的结果是生成新的string,需要把原字符串拷贝到新的string中;Builder底层有个[]byte,按需扩容,不必每次拼接都需要拷贝; 2 Reader的优势是维护一个已读计数器,知道下一次读的位置,读得更快.
作者回复: 嗯,是的。
34 - 虢國技醬2019-12-09二刷了一遍,又看了一遍源码;我觉得对于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参数不恰当时,会因为多字节字符串切片导致两头可能出现乱码展开18
- jimmy2019-01-17strings.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) } 有什么特殊的点吗? 谢谢展开
作者回复: 省去了类型转换的开销,效率会高很多。
11 - 南方有嘉木2018-11-27请问容量增加n个字节,为什么是原来的2倍再加上n呢7
- Garry2019-04-02老师,我在看strings 源码的时候发现了 func noescape(p unsafe.Pointer) unsafe.Pointer { x := uintptr(p) return unsafe.Pointer(x ^ 0) } 这个函数 最后用了个x ^ 0,但是这么操作的最后结果不还是x么,为何还要这样操作呢展开
作者回复: 为了产生一个新值啊,要跟这个函数的参数值划清界限。
共 2 条评论5 - Geek_a8be592020-07-23看到源码有处不理解如下: 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的堆栈上还是分配在公共的堆上),所以不存在拷贝来拷贝去的问题。
5 - 乖,摸摸头2020-03-10strings.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内会造成额外的维护成本。
共 2 条评论4 - kingkang2019-01-04请问byte数组转string出现乱码怎么处理?
作者回复: 如果字节数组的内容不是UTF-8编码的Unicode字符,这样直接转就会出现乱码。先要搞清楚两个问题:1. 这个字节数组的内容会是可打印的字符吗?2. 如果是可打印的字符,那它使用什么编码的?
2 - Cloud2018-11-05很实用!2
- 博博2019-05-22Builder类型中的addr *Builder 字段的意义是什么呢?
作者回复: 这个 addr 字段的意义是,保存其所属值所在的内存地址。如此一来,一旦这个值被拷贝了,使用内存地址比较的方式就可以检测出来。
共 2 条评论2 - lesserror2021-08-24郝林老师,能分析以下这段代码的执行步骤吗?没怎么看懂: *(*string)(unsafe.Pointer(&b.buf))
作者回复: unsafe.Pointer(&b.buf) -> 得到一个 Pointer 值 -> 将这个 Pointer 值的类型转换为 *string(即 string 的指针类型) -> 在这个 *string 类型的指针值之上求值(也就是求它指向的那个值) -> 得到一个 string 类型的值 (即一个字符串值)
- lesserror2021-08-24郝林老师,麻烦看看以下问题: 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 。
- 微微一怒很倾城2020-08-16对于builder的主要性能优势,我的理解是原始的string拼接,因为每次都要生成新的string,所以每次都要重新分配内存和拷贝,但是builder就不需要,只需要第一次分配,然后后面就不停地拼接和拷贝
- Ke2020-07-16strings.Builder使用之后,可以从strings.Builder复制出来新变量,但是这个变量无法在使用WriteString或者Grow之类的操作,只能读取,并且就算原始的strings.Builder变量做了reset,这个新变量的读取也不受影响
- Geek_1ed70f2019-03-14读源代码讲得好深....
- 虢國技醬2019-03-04打卡