28人参与 • 2025-02-14 • Golang
使用 +
号
使用 +
号拼接字符串的方式,每次拼接都会创建一个新的字符串,然后将原来的字符串复制到新的字符串中,这样会导致大量的内存分配和复制操作,性能较差。
字符串格式化函数 fmt.sprintf
函数
预分配 bytes.buffer
缓冲区
func buffercapresize() { var str = "abcd" var buf bytes.buffer // 预分配内存 buf.grow(4 * 10000) // 如果没有这一行,当长度不够了就会扩容 cap := 0 for i := 0; i < 10000; i++ { if buf.cap() != cap { println("cap:", buf.cap()) cap = buf.cap() } buf.writestring(str) } }
strings.builder
构建器[]byte
strings.join
函数strings.builder
和 bytes.buffer
底层都是一个字节数组,但是 bytes.buffer
在转换字符串的时候,需要重新申请内存空间,而strings.builder
是直接将底层的 bytes
转换成字符串进行返回
string(b.buf[b.off:])
直接强转unsafe.string(unsafe.slice(b.buf), len(b.buf))
零拷贝转换对字符进行截取时指向同一块内存空间
如何避免:
string
strings.builder
对新字符串进行重新构造定义一个很长的字符串 s := strings.repeat("a", 1<<20)
,
①
②
④
打印的是同一个地址 ③
打印的是一个 nil
字符串在赋值的时候不会发生拷贝,只是改变底层的指针指向
原始的字符串 s
即使被重新赋值为空字符串,但是 s2
依然指向原来的字符串,所以原始的地址不会被释放
func main() { ptr := (*reflect.stringheader)(unsafe.pointer(&s)) fmt.println("s pointer:", unsafe.pointer(ptr.data)) // ① 0xc000180000 assign() } func assign() { s2 := s ptr := (*reflect.stringheader)(unsafe.pointer(&s2)) fmt.println("assign:", unsafe.pointer(ptr.data)) // ② 0xc000180000 s := "" ptr = (*reflect.stringheader)(unsafe.pointer(&s)) fmt.println("s pointer", unsafe.pointer(ptr.data)) // ③ nil ptr = (*reflect.stringheader)(unsafe.pointer(&s2)) fmt.println("assign", unsafe.pointer(ptr.data)) // ④ 0xc000180000 _ = s2 }
不管是通过引用赋值,还是值赋值,最终都是指向同一个地址
func main() { ptr := (*reflect.stringheader)(unsafe.pointer(&s)) fmt.println("s pointer:", unsafe.pointer(ptr.data)) // ① 0xc000180000 assignpointer() } func assignpointer() { s2 := &s // 通过引用赋值 ptr := (*reflect.stringheader)(unsafe.pointer(s2)) fmt.println("assignpointer:", unsafe.pointer(ptr.data)) // ② 0xc000180000 _ = s2 }
s2
是截取 s
字符串的前 20
位,这样 s2
和 s
的起始地址是一样的
这种解决很容易导致内存泄露,因为字符串 s
申请的空间是非常大的,s
在不使用的情况下,也是不会被回收的,因为 s2
指向了 s
的地址
func main() { ptr := (*reflect.stringheader)(unsafe.pointer(&s)) fmt.println("s pointer:", unsafe.pointer(ptr.data)) // ① 0xc000100000 stringslice() } func stringslice() { s2 := s[:20] ptr := (*reflect.stringheader)(unsafe.pointer(&s2)) fmt.println("stringslice:", unsafe.pointer(ptr.data)) // ② 0xc000100000 _ = s2 }
字符串传递到函数内部,不管是指针传递还是值传递,字符串实际内容在内存中的地址是相同的
func main() { ptr := (*reflect.stringheader)(unsafe.pointer(&s)) fmt.println("s pointer:", unsafe.pointer(ptr.data)) // ① 0x49b7ba f1(s) f2(&s) } func f1(s string) string { ptr := unsafe.stringdata(s) fmt.println("f1:", ptr) // ② 0x49b7ba return s } func f2(s *string) *string { ptr := unsafe.stringdata(*s) fmt.println("f2:", ptr) // ③ 0x49b7ba return s }
你可能会发现,网上说传递指针才不会发生拷贝,传递值是会发生拷贝,但为什么现在无论是传递指针还是传递值,字符串都没有发生拷贝
这是因为 &s
打印的是函数参数 s
在栈上的地址,每次函数调用都会不同
func main() { ptr := (*reflect.stringheader)(unsafe.pointer(&s)) fmt.println("s pointer:", unsafe.pointer(ptr.data)) // ① 0x49b7ba fmt.println("s 地址:", &s) // 0x528650 f1(s) f2(&s) } func f1(s string) string { fmt.println("f1 s:", &s) // 0xc000014070 ptr := unsafe.stringdata(s) fmt.println("f1:", ptr) // 0x49b7ba return s } func f2(s *string) *string { fmt.println("f2 s:", s) // 0x528650 ptr := unsafe.stringdata(*s) fmt.println("f2:", ptr) // 0x49b7ba return s }
1.强转
func stringslice1(s string) string { fmt.println("string:", unsafe.stringdata(s)) // 0xc000100000 s1 := string([]byte(s[:20])) ptr := unsafe.stringdata(s1) fmt.println("stringslice1:", ptr) // 0xc0000bc000 return s1 }
2.改变首字符,就能改变 s1
的地址
func stringslice2(s string) string { fmt.println("string:", unsafe.stringdata(s)) // 0xc000100000 s1 := (" " + s[:20])[1:] ptr := unsafe.stringdata(s1) fmt.println("stringslice2:", ptr) // 0xc000018199 return s1 }
3.使用 stringsbuilder
改变 s1
的地址
func stringsliceusebuilder(s string) string { fmt.println("string:", unsafe.stringdata(s)) // 0xc000100000 var b strings.builder b.grow(20) b.writestring(s[:20]) s1 := b.string() ptr := unsafe.stringdata(s1) fmt.println("stringsliceusebuilder:", ptr) // 0xc0000b0000 return s1 }
虽然同是切片操作,但是 s1
会改变地址,而 s2
不会改变地址
s1
强转为字符串的指针类型s2
:先对 s
进行取指,取指之后将它转成字符串切片指针类型,然后在获取指针的内容所以 s2
的方法是零拷贝转换
func main() { ptr := (*reflect.stringheader)(unsafe.pointer(&s)) fmt.println("s pointer:", unsafe.pointer(ptr.data)) // 0xc000180000 } func stringtobytes() { s1 := []byte(s) fmt.println("s1: ", unsafe.slicedata(s1)) // 0xc000280000 s2 := *(*[]byte)(unsafe.pointer(&s)) fmt.println("s2: ", unsafe.slicedata(s2)) // 0xc000180000 }
到此这篇关于go语言中字符串赋值中的问题与解决方法的文章就介绍到这了,更多相关go字符串赋值内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论