39 | bytes包与字节串操作(下)
该思维导图由 AI 生成,仅供参考
知识扩展
问题 1:bytes.Buffer的扩容策略是怎样的?
- 深入了解
- 翻译
- 解释
- 总结
本文详细介绍了bytes.Buffer的扩容策略和可能导致内容泄露的方法。在扩容策略方面,文章指出Buffer值可以手动或自动扩容,扩容时会优先考虑减少内存分配和内容拷贝,只有在容量无法满足要求时才会创建新的内容容器。此外,文章还提到了Bytes方法和Next方法可能导致内容泄露,因为它们返回的结果值在一段时期内会与内容容器共用同一个底层数组,可能导致外界操纵相关联Buffer值的内容,造成严重的数据安全问题。总结来看,bytes.Buffer类型不仅可以拼接、截断字节序列,还可以顺序读取子序列,使用字节切片作为内容容器,并实时记录已读字节的计数。文章强调了已读计数在Buffer值中的关键作用,以及扩容方法的原则和内容泄露可能带来的严重后果。读者通过本文可以快速了解bytes.Buffer的特点和注意事项,对于使用和操作bytes.Buffer类型将有很大帮助。
《Go 语言核心 36 讲》,新⼈⾸单¥59
全部留言(20)
- 最新
- 精选
- moonfox文章中说:“如果当前内容容器的容量的一半,仍然大于或等于其现有长度再加上另需的字节数的和,cap(b.buf)/2 >= len(b.buf)+need”。 如果是这样的话,那说明还有很多的未使用的空间,起码有一半,那就不用扩容,直接追加就可以了。所以这里的代码是错的,源码(1.16.2)中是 n <= c/2-m ,即cap(b.buf) >= b.Len() + need,也就是未读字节长度 + 另需的字节数 小于等于容量的一半时,才会把未读字节copy到buf的头部,只有在未读字节较少的时候,才发生copy,如果有太多的未读字节,就不copy到头部了(费时)
作者回复: 源码: n <= c/2-m 相当于: n <= cap(b.buf)/2 - b.Len() 相当于: n + b.Len() <= cap(b.buf)/2 得出: cap(b.buf)/2 >= len(b.buf) + need (为了在省略掉多余上下文的同时保持清晰,我在这里使用了更容易理解的代码 cap(b.buf) 和 len(b.buf) ,以及英文单词 need) 所以我这里没写错啊。你后面描述的不是跟我说的一个意思是么?? 不过,如果这里让你产生了误解,说明我的文字有所欠缺,我改进一下吧。
2021-06-0231 - Lywane老师,Buffer的Write,WriteString方法返回一个int,一个err,int是参数长度,err永远是nil,感觉这个返回没啥用啊。这么设计是为了实现某些接口么?
作者回复: 对,主要是实现接口,而且也保留返回错误值的权利和义务。
2020-04-081 - 疯琴请问老师,用 bytes.Buffer 而不用字节切片是因为它有计数器和一些方法使得操作更方便,还有高效的扩容策略么。这么说对么?
作者回复: 因为有缓冲区啊,比较适合分步构建字符串值。
2020-01-071 - 窗外老师你好,为什么我本地的src/runtime包下的stringtoslicebyte方法里面tmpBuf的默认长度是32。 所以文中例子,输出的容量是32
作者回复: 你可以把前因后果都摆上来。这篇文章里有 tmpBuf 吗?
2019-08-1141 - killer2023年了都,大佬还在看评论吗?写了4年go了总感觉差点意思,最近把郝大佬的文章翻出来每篇仔细阅读、做笔记、看源码,另外也在团队给大家分享golang的学习之路特别充实; 对stringtoslicebyte做了debug,没想到cap为8是这么来的 func stringtoslicebyte(buf *tmpBuf, s string) []byte { var b []byte if buf != nil && len(s) <= len(buf) { *buf = tmpBuf{} b = buf[:len(s)] } else { b = rawbyteslice(len(s))// bytes.Buffer 初始化 buf 为空,需要调用 rawbyteslice 初始化 slice } copy(b, s) return b } func rawbyteslice(size int) (b []byte) { cap := roundupsize(uintptr(size))// roundupsize 计算容量,重点关注这里 p := mallocgc(cap, nil, false)// 分配内存 if cap != uintptr(size) { memclrNoHeapPointers(add(p, uintptr(size)), cap-uintptr(size)) } *(*slice)(unsafe.Pointer(&b)) = slice{p, size, int(cap)} return } // Returns size of the memory block that mallocgc will allocate if you ask for the size. func roundupsize(size uintptr) uintptr { if size < _MaxSmallSize { if size <= smallSizeMax-8 { // 以大佬例子为例:contents := "ab" buffer1 := bytes.NewBufferString(contents),size长度为2,肯定小于smallSizeMax-8 return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]]) // divRoundUp 函数算出来 为1, size_to_class8[1]=1;class_to_size[1]=8返回的uintptr为8,这里做了一个约定类似于查表 } else { return uintptr(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]]) } } if size+_PageSize < size { return size } return alignUp(size, _PageSize) } // 这里算出来是1 func divRoundUp(n, a uintptr) uintptr { // a is generally a power of two. This will get inlined and // the compiler will optimize the division. return (n + a - 1) / a // (8+2-1)/ 8=1 }
作者回复: 嗯嗯,虽然已经这么多年了,但是几乎每次开电脑的时候还是会打开这个页面。不错的,好好看源码,加油!
2023-12-20归属地:四川 - costaLongcontents := "ab" buffer1 := bytes.NewBufferString(contents) fmt.Printf("The capacity of new buffer with content %q: %d\n", contents, buffer1.Cap()) // 内容容器的容量:8 单独执行这段代码输出的结果是:The capacity of new buffer with content "ab": 32 请问是原因呢
作者回复: 没看明白,“单独执行这段代码”是什么意思?
2022-02-17 - rename如果当前内容容器的容量的一半,仍然大于或等于其现有长度再加上所需的字节数的和,即:cap(b.buf)/2 >= len(b.buf)+need 这边len(b.buf)用b.Len()似乎更准确?才是获取未读部分的实际长度
作者回复: 当然是要看 buf 本身的长度了。这是 bytes.Buffer 内部的算法,你可以看一看源码。
2019-07-142 - 嘎嘎源码里给了推荐的构建方法 // To build strings more efficiently, see the strings.Builder type. func (b *Buffer) String() string { if b == nil { // Special case, useful in debugging. return "<nil>" } return string(b.buf[b.off:]) }
作者回复: 这个Buffer类型比strings.Builder类型出现要早。我觉得后者质量更高一些。你可以参看一下后者的String方法。
2019-03-15 - 失了智的沫雨如果只看strings.Builder 和bytes.Buffer的String方法的话,strings.Builder 更高效一些。 我们可以直接查看两个String方法的源代码,其中strings.Builder String方法中 *(*string)(unsafe.Pointer(&b.buf)) 是直接取得buf的地址然后转换成string返回。 而bytes.Buffer的String方法是 string(b.buf[b.off:]) 对buf 进行切片操作,我认为这比直接取址要花费更多的时间。 测试函数: func BenchmarkStrings(b *testing.B) { str := strings.Builder{}/bytes.Buffer{} str.WriteString("test") for i := 0; i < b.N; i++ { str.String() } } 结果为 BenchmarkStrings-8 2000000000 0.66 ns/op BenchmarkBuffer-8 300000000 5.64 ns/op 所以strings.Builder的String方法更高效2018-11-11334
- 🐻https://github.com/golang/go/blob/master/src/strings/builder_test.go#L319-L366 发现最后的问题,Go 的标准库中,已经给出了相关的测试代码了。2019-04-267