it编程 > 前端脚本 > Golang

Go语言中字符串赋值中的问题与解决方法

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.builderbytes.buffer 底层都是一个字节数组,但是 bytes.buffer 在转换字符串的时候,需要重新申请内存空间,而strings.builder 是直接将底层的 bytes 转换成字符串进行返回

字符串内存泄露

对字符进行截取时指向同一块内存空间

如何避免:

定义一个很长的字符串 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 位,这样 s2s 的起始地址是一样的

这种解决很容易导致内存泄露,因为字符串 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 不会改变地址

所以 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字符串赋值内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)
打赏 微信扫一扫 微信扫一扫

您想发表意见!!点此发布评论

推荐阅读

Go 并发编程Goroutine的实现示例

02-14

基于go中fyne gui的通达信数据导出工具详解

02-14

Go语言进行多时区时间转换的示例代码

02-14

go 集成nacos注册中心、配置中心的过程详解

02-14

Go语言使用kafka-go实现Kafka消费消息

02-14

golang通过反射手动实现json序列化的方法

02-14

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论