Go语言模型:string的底层数据结构与高效操作详解
来源:脚本之家
时间:2023-01-07 12:07:55 480浏览 收藏
对于一个Golang开发者来说,牢固扎实的基础是十分重要的,golang学习网就来带大家一点点的掌握基础知识点。今天本篇文章带大家了解《Go语言模型:string的底层数据结构与高效操作详解》,主要介绍了String、底层,希望对大家的知识积累有所帮助,快点收藏起来吧,否则需要时就找不到了!
Golang的string类型底层数据结构简单,本质也是一个结构体实例,且是const不可变。
string的底层数据结构
通过下面一个例子来看:
package main import ( "fmt" "unsafe" ) // from: string.go 在GoLand IDE中双击shift快速找到 type stringStruct struct { array unsafe.Pointer // 指向一个 [len]byte 的数组 length int // 长度 } func main() { test := "hello" p := (*str)(unsafe.Pointer(&test)) fmt.Println(&p, p) // 0xc420070018 &{0xa3f71 5} c := make([]byte, p.length) for i := 0; i string, "hello" test2 := test + " world" // 字符串是不可变类型,会生成一个新的string实例 p2 := (*str)(unsafe.Pointer(&test2)) fmt.Println(&p2, p2) // 0xc420028030 &{0xc42000a2e5 11} fmt.Println(test2) // hello, world }
string的拼接与修改
+操作
string类型是一个不可变类型,那么任何对string的修改都会新生成一个string的实例,如果是考虑效率的场景就要好好考虑一下如何修改了。先说一下最长用的+操作,同样上面的例子,看一下+操作拼接字符串的反汇编:
25 test2 := test + " world" 0x00000000004824d7 : lea 0x105a2(%rip),%rax # 0x492a80 0x00000000004824de : mov %rax,(%rsp) 0x00000000004824e2 : callq 0x40dda0# 调用newobject函数 0x00000000004824e7 : mov 0x8(%rsp),%rax 0x00000000004824ec : mov %rax,0xa0(%rsp) 0x00000000004824f4 : mov 0xa8(%rsp),%rax 0x00000000004824fc : mov 0x8(%rax),%rcx 0x0000000000482500 : mov (%rax),%rax 0x0000000000482503 : mov %rax,0x8(%rsp) 0x0000000000482508 : mov %rcx,0x10(%rsp) 0x000000000048250d : movq $0x0,(%rsp) 0x0000000000482515 : lea 0x30060(%rip),%rax # 0x4b257c 0x000000000048251c : mov %rax,0x18(%rsp) 0x0000000000482521 : movq $0x6,0x20(%rsp) 0x000000000048252a : callq 0x43cc00 # 调用concatstring2函数
因为当前go[2018.11 version: go1.11]的不是遵循默认的x86 calling convention用寄存器传参,而是通过stack进行传参,所以go的反汇编不像c的那么容易理解,不过大概看懂+背后的操作还是没问题的,看一下runtime源码的拼接函数:
func concatstring2(buf *tmpBuf, a [2]string) string { return concatstrings(buf, a[:]) } // concatstrings implements a Go string concatenation x+y+z+... // The operands are passed in the slice a. // If buf != nil, the compiler has determined that the result does not // escape the calling function, so the string data can be stored in buf // if small enough. func concatstrings(buf *tmpBuf, a []string) string { idx := 0 l := 0 count := 0 for i, x := range a { n := len(x) if n == 0 { continue } if l+n分析runtime的concatstrings实现,可以看出+最后新申请buf,拷贝原来的string到buf,最后返回新实例。那么每次的+操作,都会涉及新申请buf,然后是对应的copy。如果反复使用+,就不可避免有大量的申请内存操作,对于大量的拼接,性能就会受到影响了。
bytes.Buffer
通过看源码,bytes.Buffer 增长buffer时是按照2倍来增长内存,可以有效避免频繁的申请内存,通过一个例子来看:
func main() { var buf bytes.Buffer for i := 0; i对应的byte包库函数源码
// @file: buffer.go func (b *Buffer) WriteString(s string) (n int, err error) { b.lastRead = opInvalid m, ok := b.tryGrowByReslice(len(s)) if !ok { m = b.grow(len(s)) // 高效的增长策略 -> let capacity get twice as large } return copy(b.buf[m:], s), nil } // @file: buffer.go // let capacity get twice as large !!! func (b *Buffer) grow(n int) int { m := b.Len() // If buffer is empty, reset to recover space. if m == 0 && b.off != 0 { b.Reset() } // Try to grow by means of a reslice. if i, ok := b.tryGrowByReslice(n); ok { return i } // Check if we can make use of bootstrap array. if b.buf == nil && n maxInt-c-n { panic(ErrTooLarge) } else { // Not enough space anywhere, we need to allocate. buf := makeSlice(2*c + n) copy(buf, b.buf[b.off:]) b.buf = buf } // Restore b.off and len(b.buf). b.off = 0 b.buf = b.buf[:m+n] return m }string.join
这个函数可以一次申请最终string的大小,但是使用得预先准备好所有string,这种场景也是高效的,一个例子:
func main() { var strs []string for i := 0; i对应库的源码:
// Join concatenates the elements of a to create a single string. The separator string // sep is placed between elements in the resulting string. func Join(a []string, sep string) string { switch len(a) { case 0: return "" case 1: return a[0] case 2: // Special case for common small values. // Remove if golang.org/issue/6714 is fixed return a[0] + sep + a[1] case 3: // Special case for common small values. // Remove if golang.org/issue/6714 is fixed return a[0] + sep + a[1] + sep + a[2] } // 计算好最终的string的大小 n := len(sep) * (len(a) - 1) // for i := 0; istrings.Builder (go1.10+)
看到这个名字,就想到了Java的库,哈哈,这个Builder用起来是最方便的,不过是在1.10后引入的。其高效也是体现在2倍速的内存增长, WriteString函数利用了slice类型对应append函数的2倍速增长。
一个例子:
func main() { var s strings.Builder for i := 0; i对应库的源码
@file: builder.go // WriteString appends the contents of s to b's buffer. // It returns the length of s and a nil error. func (b *Builder) WriteString(s string) (int, error) { b.copyCheck() b.buf = append(b.buf, s...) return len(s), nil }总结
Golang的字符串处理还是挺方便的,有垃圾回收和一些内置的语言级写法支持,让复杂字符串操作没有那么繁琐了,比起C/C++高效了不少。
补充:go string的内部实现
go string 内部实现
这个string的探索
来来个例子
func boo(a int, b int)(int, string){ return a + b, "abcd" }81079 000000000044dfa0: 81080 44dfa0:>------48 c7 44 24 18 00 00 >--movq $0x0,0x18(%rsp) 81081 44dfa7:>------00 00- 81082 44dfa9:>------0f 57 c0 >--xorps %xmm0,%xmm0 81083 44dfac:>------0f 11 44 24 20 >--movups %xmm0,0x20(%rsp) 81084 44dfb1:>------48 8b 44 24 08 >--mov 0x8(%rsp),%rax 81085 44dfb6:>------48 03 44 24 10 >--add 0x10(%rsp),%rax 81086 44dfbb:>------48 89 44 24 18 >--mov %rax,0x18(%rsp) 81087 44dfc0:>------48 8d 05 d4 eb 01 00 >--lea 0x1ebd4(%rip),%rax # 46cb9b 81088 44dfc7:>------48 89 44 24 20 >--mov %rax,0x20(%rsp) 81089 44dfcc:>------48 c7 44 24 28 04 00 >--movq $0x4,0x28(%rsp) 81090 44dfd3:>------00 00- 81091 44dfd5:>------c3 >--retq--- 其中
81087 44dfc0:>------48 8d 05 d4 eb 01 00 >--lea 0x1ebd4(%rip),%rax # 46cb9b81088 44dfc7:>------48 89 44 24 20 >--mov %rax,0x20(%rsp) 81089 44dfcc:>------48 c7 44 24 28 04 00 >--movq $0x4,0x28(%rsp) 81090 44dfd3:>------00 00- 81091 44dfd5:>------c3 >--retq--- lea 0x1ebd4(%rip),%rax得到char*, mov %rax,0x20(%rsp)复制给返回值, movq $0x4,0x28(%rsp)把长度也填进去, 其实可以看到string就是c里面的char* 和len的组合
以上为个人经验,希望能给大家一个参考,也希望大家多多支持golang学习网。如有错误或未考虑完全的地方,望不吝赐教。
到这里,我们也就讲完了《Go语言模型:string的底层数据结构与高效操作详解》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于golang的知识点!
-
455 收藏
-
418 收藏
-
215 收藏
-
423 收藏
-
498 收藏
-
233 收藏
-
322 收藏
-
181 收藏
-
316 收藏
-
244 收藏
-
300 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习