先上结论

1、内置append函数在现有数组的长度 < 1024 时 cap 增长是翻倍的,再往上的增长率则是 1.25,至于为何后面会说。
2、Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
3、在函数内用append时,append会自动以倍增的方式扩展slice_2的容量,但是扩展也仅仅是函数内slice_2的长度和容量,slice_1的长度和容量是没变的,所以在函数外打印时看起来就是没变。
4、当程序要求slice的容量超大并且需要频繁的更改slice的内容时,就不应该用slice,改用list更合适。
5、Go 在 append 和 copy 方面的开销是可预知+可控的,应用上简单的调优有很好的效果。这个世界上没有免费的动态增长内存,各种实现方案都有设计权衡。

 append新建对象,s2指向了新对象,函数退出新对象释放

原来的s1还是s1,append没有影响,但是s2修改的操作有影响,因为s2直接操作了s1的内存

前言

用过go语言的亲们都知道,slice(中文翻译为切片)在编程中经常用到,它代表变长的序列,序列中每个元素都有相同的类型,类似一个动态数组,利用append可以实现动态增长,利用slice的特性可以很容易的切割slice,它们是怎么实现这些特性的呢?现在我们来探究一下这些特性的本质是什么。

先了解一下slice的特性

定义一个slice:

s := []int{1,2,3,4,5} fmt.Println(s) // [1 2 3 4 5]

一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。

slice的扩容:

s := []int{1,2,3,4,5} s = append(s, 6) fmt.Println(s) // [1 2 3 4 5 6]

内置append函数在现有数组的长度 < 1024 时 cap 增长是翻倍的,再往上的增长率则是 1.25,至于为何后面会说。

slice的切割:

s := []int{1,2,3,4,5,6}
s1 := s[0:2]
fmt.Println(s1)  // [1 2]
s2 := s[4:] fmt.Println(s2) // [5 6] s3 := s[:4] fmt.Println(s3) // [1 2 3 4]

slice作为函数参数:

package mainimport "fmt"func main() {slice_1 := []int{1, 2, 3, 4, 5} fmt.Printf("main-->data:\t%#v\n", slice_1) fmt.Printf("main-->len:\t%#v\n", len(slice_1)) fmt.Printf("main-->cap:\t%#v\n", cap(slice_1)) test1(slice_1) fmt.Printf("main-->data:\t%#v\n", slice_1) test2(&slice_1) fmt.Printf("main-->data:\t%#v\n", slice_1) } func test1(slice_2 []int) { slice_2[1] = 6666 // 函数外的slice确实有被修改 slice_2 = append(slice_2, 8888) // 函数外的不变 fmt.Printf("test1-->data:\t%#v\n", slice_2) fmt.Printf("test1-->len:\t%#v\n", len(slice_2)) fmt.Printf("test1-->cap:\t%#v\n", cap(slice_2)) } func test2(slice_2 *[]int) { // 这样才能修改函数外的slice *slice_2 = append(*slice_2, 6666) }

结果:

main-->data:    []int{1, 2, 3, 4, 5}
main-->len: 5
main-->cap: 5 test1-->data:  []int{1, 6666, 3, 4, 5, 8888} test1-->len:  6 test1-->cap:  12 main-->data:  []int{1, 6666, 3, 4, 5} main-->data:  []int{1, 6666, 3, 4, 5, 6666}

这里要注意注释的地方,为何slice作为值传递参数,函数外的slice也被更改了?为何在函数内append不能改变函数外的slice?要回答这些问题就得了解slice内部结构,详细请看下面.

slice的内部结构

其实slice在Go的运行时库中就是一个C语言动态数组的实现,在$GOROOT/src/pkg/runtime/runtime.h中可以看到它的定义:

struct    Slice{    // must not move anythingbyte*    array;        // actual datauintgo    len;        // number of elements uintgo cap; // allocated number of elements };

这个结构有3个字段,第一个字段表示array的指针,就是真实数据的指针(这个一定要注意),所以才经常说slice是数组的引用,第二个是表示slice的长度,第三个是表示slice的容量,注意:len和cap都不是指针

现在就可以解释前面的例子slice作为函数参数提出的问题:

函数外的slice叫slice_1,函数的参数叫slice_2,当函数传递slice_1的时候,其实传入的确实是slice_1参数的复制,所以slice_2复制了slise_1,但要注意的是slice_2里存储的数组的指针,所以当在函数内更改数组内容时,函数外的slice_1的内容也改变了。在函数内用append时,append会自动以倍增的方式扩展slice_2的容量,但是扩展也仅仅是函数内slice_2的长度和容量,slice_1的长度和容量是没变的,所以在函数外打印时看起来就是没变。

append的运作机制

在对slice进行append等操作时,可能会造成slice的自动扩容。其扩容时的大小增长规则是:

  • 如果新的slice大小是当前大小2倍以上,则大小增长为新大小

  • 否则循环以下操作:如果当前slice大小小于1024,按每次2倍增长,否则每次按当前大小1/4增长。直到增长的大小超过或等于新大小。

  • append的实现只是简单的在内存中将旧slice复制给新slice

至于为何会这样,你要看一下golang的源码slice就知道了:

newcap := old.cap
if newcap+newcap < cap {newcap = cap
} else {for { if old.len < 1024 { newcap += newcap } else { newcap += newcap / 4 } if newcap >= cap { break } } }

为何不用动态链表实现slice?

  • 首先拷贝一断连续的内存是很快的,假如不想发生拷贝,也就是用动态链表,那你就没有连续内存。此时随机访问开销会是:链表 O(N), 2倍增长块链 O(LogN),二级表一个常数很大的O(1)。问题不仅是算法上开销,还有内存位置分散而对缓存高度不友好,这些问题i在连续内存方案里都是不存在的。除非你的应用是狂append然后只顺序读一次,否则优化写而牺牲读都完全不 make sense. 而就算你的应用是严格顺序读,缓存命中率也通常会让你的综合效率比拷贝换连续内存低。

  • 对小 slice 来说,连续 append 的开销更多的不是在 memmove, 而是在分配一块新空间的 memory allocator 和之后的 gc 压力(这方面对链表更是不利)。所以,当你能大致知道所需的最大空间(在大部分时候都是的)时,在make的时候预留相应的 cap 就好。如果所需的最大空间很大而每次使用的空间量分布不确定,那你就要在浪费内存和耗 CPU 在 allocator + gc 上做权衡。

  • Go 在 append 和 copy 方面的开销是可预知+可控的,应用上简单的调优有很好的效果。这个世界上没有免费的动态增长内存,各种实现方案都有设计权衡。

什么时候该用slice?

在go语言中slice是很灵活的,大部分情况都能表现的很好,但也有特殊情况。
当程序要求slice的容量超大并且需要频繁的更改slice的内容时,就不应该用slice,改用list更合适。

参考资料:

https://segmentfault.com/a/1190000005812839?utm_source=tuicool&utm_medium=referral

http://www.cnblogs.com/howDo/archive/2013/04/25/GoLang-Array-Slice.html

golang list: https://golang.org/pkg/container/list/

【GoLang】深入理解slice len cap什么算法? 参数传递有啥蹊跷?相关推荐

  1. golang append时slice len 和 cap

    2019独角兽企业重金招聘Python工程师标准>>> 声明: 源slice= src 添加slice = app 结果slice=tar append时 len tar === l ...

  2. 斯坦福大学马腾宇:无法理解现有的深度学习算法?那就设计一个能理解的

    2020-01-22 05:41:34 作者 | 丛末 编辑 | Camel 本科毕业于清华姚班.博士毕业于普林斯顿大学,师从 Sanjeev Arora 教授,马腾宇作为 AI 学界一颗冉冉升起的新 ...

  3. 从另一个角度理解分布式系统与CAP定理

    从另一个角度理解分布式系统与CAP定理 参考:性能之殇(七)-- 分布式计算.超级计算机与神经网络共同的瓶颈 分布式计算的本质 分布式系统的产生,来源于源于人们日益增长的性能需求与落后的x86架构之间 ...

  4. ​通俗理解神经网络BP反向传播算法

    转载自  ​通俗理解神经网络BP反向传播算法 通俗理解神经网络BP反向传播算法 在学习深度学习相关知识,无疑都是从神经网络开始入手,在神经网络对参数的学习算法bp算法,接触了很多次,每一次查找资料学习 ...

  5. [Vue][面试]你怎么理解vue中的diff算法?

    你怎么理解vue中的diff算法? #####源码分析1:必要性,lifecycle.js–mountComponent() vue中一个组件一个watcher实例,而组件中可能存在很多个data中的 ...

  6. Golang 入门 : 切片(slice)

    切片(slice)是 Golang 中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合.切片是围绕动态数组的概念构建的,可以按需自动增长和缩小.切片的动态增长是通过内置函数 append( ...

  7. Golang学习笔记——Slice

    切片和数组很类似,甚至你可以理解成数组的子集.但是`切片有一个数组所没有的特点,那就是切片的长度是可变的`. 严格地讲,切片有`容量(capacity)`和`长度(length)`两个属性. 首先我们 ...

  8. 一文理解分布式常见的一致性算法

    导语 | 后台服务架构经过了集中式.SOA.微服务和服务网格四个阶段,目前互联网界大都使用微服务和服务网格.服务从集中式.中心化向分布式.去中心化不断演进,服务也变得更灵活,能够自动扩缩容.快速版本迭 ...

  9. react 返回一个页面_Fiber 内部: 深入理解 React 的新 reconciliation 算法

    最近在看 React, 发现一篇深度好文, 忍不住就翻译了. React 是一个用于构建用户界面的库, 它的核心是跟踪组件状态变化并将它们更新到页面上. 在 React 中, 我们称这个过程为 rec ...

最新文章

  1. 解决win7不能上网的问题
  2. app启动调用的api
  3. python如何获取信息_如何使用Python获取系统信息?
  4. git push代码到远程新分支
  5. C#分布式事务(TransactionScope )
  6. 使用Flash读取COOKIE
  7. 异步socket优雅的关闭-CancelIO和SO_LINGER
  8. 微信小程序实现数组排序(向上向下移动)
  9. 一个简单的JDBC通用工具
  10. 《统一沟通-微软-实战》-6-部署-2-中介服务器-5-语音路由-语音策略
  11. (LINQ 学习系列)(3)学习Linq的几个基础知识
  12. 时间操作(JavaScript版)—最简单比较两个时间格式数据的大小
  13. matlab常用函数总结
  14. ps随机排列_[PS]圆点随机不重叠排列脚本
  15. redmi路由器是linux,拯救小米路由器硬盘数据的方法及软件下载
  16. 盘点:那些年的游戏公司
  17. Glide4 设置默认图片和错误图片,即设置占位图
  18. HTML5 案例学习笔记
  19. 数据扩充与数据预处理
  20. Windows安装You-get详细教程和问题解决分享

热门文章

  1. python 导出mysql 视图_【Python基础】mysql数据库视图是什么
  2. 深大计算机与科学,陆楠 - 深圳大学 - 计算机与软件学院
  3. python登录代码思路_用python登录Dr.com思路以及代码分享
  4. Java项目:食品溯源系统(java+Springboot+Maven+mybatis+Vue+mysql+wd)
  5. java servlet 多线程_Servlet的多线程和线程安全
  6. ie8加载js太慢_js ie8 慢
  7. jQuery添加DOM节点常用的5种方法
  8. ios 常见性能优化
  9. 'This NSPersistentStoreCoordinator has no persistent stores 报错
  10. 优化实战:不要随便将字段折腾来折腾去的