热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一文了解golangslice和string的重用

golangslice和string重用相比于cc++,golang的一个很大的改进就是引入了gc机制,不再需要用户自己管理内存,大大减少了程序由于内存泄露而引入的bug,但是同时

本文摘自php中文网,作者藏色散人,侵删。

相比于 c/c++,golang 的一个很大的改进就是引入了 gc 机制,不再需要用户自己管理内存,大大减少了程序由于内存泄露而引入的 bug,但是同时 gc 也带来了额外的性能开销,有时甚至会因为使用不当,导致 gc 成为性能瓶颈,所以 golang 程序设计的时候,应特别注意对象的重用,以减少 gc 的压力。而 slice 和 string 是 golang 的基本类型,了解这些基本类型的内部机制,有助于我们更好地重用这些对象

slice 和 string 内部结构

slice 和 string 的内部结构可以在 $GOROOT/src/reflect/value.go 里面找到

1

2

3

4

5

6

7

8

9

10

type StringHeader struct {

    Data uintptr

    Len  int

}

 

type SliceHeader struct {

    Data uintptr

    Len  int

    Cap  int

}

可以看到一个 string 包含一个数据指针和一个长度,长度是不可变的

slice 包含一个数据指针、一个长度和一个容量,当容量不够时会重新申请新的内存,Data 指针将指向新的地址,原来的地址空间将被释放

从这些结构就可以看出,string 和 slice 的赋值,包括当做参数传递,和自定义的结构体一样,都仅仅是 Data 指针的浅拷贝

slice 重用

append 操作

1

2

3

4

5

6

7

8

9

10

si1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}

si2 := si1

si2 = append(si2, 0)

Convey("重新分配内存", func() {

    header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1))

    header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2))

    fmt.Println(header1.Data)

    fmt.Println(header2.Data)

    So(header1.Data, ShouldNotEqual, header2.Data)

})

si1 和 si2 开始都指向同一个数组,当对 si2 执行 append 操作时,由于原来的 Cap 值不够了,需要重新申请新的空间,因此 Data 值发生了变化,在 $GOROOT/src/reflect/value.go 这个文件里面还有关于新的 cap 值的策略,在 grow 这个函数里面,当 cap 小于 1024 的时候,是成倍的增长,超过的时候,每次增长 25%,而这种内存增长不仅仅数据拷贝(从旧的地址拷贝到新的地址)需要消耗额外的性能,旧地址内存的释放对 gc 也会造成额外的负担,所以如果能够知道数据的长度的情况下,尽量使用 make([]int, len, cap) 预分配内存,不知道长度的情况下,可以考虑下面的内存重用的方法

内存重用

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

si1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}

si2 := si1[:7]

Convey("不重新分配内存", func() {

    header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1))

    header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2))

    fmt.Println(header1.Data)

    fmt.Println(header2.Data)

    So(header1.Data, ShouldEqual, header2.Data)

})

 

Convey("往切片里面 append 一个值", func() {

    si2 = append(si2, 10)

    Convey("改变了原 slice 的值", func() {

        header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1))

        header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2))

        fmt.Println(header1.Data)

        fmt.Println(header2.Data)

        So(header1.Data, ShouldEqual, header2.Data)

        So(si1[7], ShouldEqual, 10)

    })

})

si2 是 si1 的一个切片,从第一段代码可以看到切片并不重新分配内存,si2 和 si1 的 Data 指针指向同一片地址,而第二段代码可以看出,当我们往 si2 里面 append 一个新的值的时候,我们发现仍然没有内存分配,而且这个操作使得 si1 的值也发生了改变,因为两者本就是指向同一片 Data 区域,利用这个特性,我们只需要让 si1 = si1[:0] 就可以不断地清空 si1 的内容,实现内存的复用了

PS: 你可以使用 copy(si2, si1) 实现深拷贝

string

1

2

3

4

5

6

7

8

9

10

11

Convey("字符串常量", func() {

    str1 := "hello world"

    str2 := "hello world"

    Convey("地址相同", func() {

        header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1))

        header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2))

        fmt.Println(header1.Data)

        fmt.Println(header2.Data)

        So(header1.Data, ShouldEqual, header2.Data)

    })

})

这个例子比较简单,字符串常量使用的是同一片地址区域

1

2

3

4

5

6

7

8

9

10

11

12

Convey("相同字符串的不同子串", func() {

    str1 := "hello world"[:6]

    str2 := "hello world"[:5]

    Convey("地址相同", func() {

        header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1))

        header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2))

        fmt.Println(header1.Data, str1)

        fmt.Println(header2.Data, str2)

        So(str1, ShouldNotEqual, str2)

        So(header1.Data, ShouldEqual, header2.Data)

    })

})

相同字符串的不同子串,不会额外申请新的内存,但是要注意的是这里的相同字符串,指的是 str1.Data == str2.Data && str1.Len == str2.Len,而不是 str1 == str2,下面这个例子可以说明 str1 == str2 但是其 Data 并不相同

1

2

3

4

5

6

7

8

9

10

11

12

Convey("不同字符串的相同子串", func() {

    str1 := "hello world"[:5]

    str2 := "hello golang"[:5]

    Convey("地址不同", func() {

        header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1))

        header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2))

        fmt.Println(header1.Data, str1)

        fmt.Println(header2.Data, str2)

        So(str1, ShouldEqual, str2)

        So(header1.Data, ShouldNotEqual, header2.Data)

    })

})

实际上对于字符串,你只需要记住一点,字符串是不可变的,任何字符串的操作都不会申请额外的内存(对于仅内部数据指针而言),我曾自作聪明地设计了一个 cache 去存储字符串,以减少重复字符串所占用的空间,事实上,除非这个字符串本身就是由 []byte 创建而来,否则,这个字符串本身就是另一个字符串的子串(比如通过 strings.Split 获得的字符串),本来就不会申请额外的空间,这么做简直就是多此一举。

更多golang相关技术文章,请访问golang教程栏目!

以上就是一文了解golang slice和string的重用的详细内容,更多文章请关注编程笔记!!


推荐阅读
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文详细说明了在JavaScript中解决alert弹出窗口文本换行问题的方法。通过给alert弹出的文本添加换行符,可以实现在弹窗中显示多行文本的效果。同时,提供了相关代码示例和注意事项,帮助读者更好地理解和应用这一解决方法。 ... [详细]
  • Day2列表、字典、集合操作详解
    本文详细介绍了列表、字典、集合的操作方法,包括定义列表、访问列表元素、字符串操作、字典操作、集合操作、文件操作、字符编码与转码等内容。内容详实,适合初学者参考。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
author-avatar
大航
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有