先上结论

、内置append函数在现有数组的长度 <  时 cap 增长是翻倍的,再往上的增长率则是 1.25,至于为何后面会说。
、Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
、在函数内用append时,append会自动以倍增的方式扩展slice_2的容量,但是扩展也仅仅是函数内slice_2的长度和容量,slice_1的长度和容量是没变的,所以在函数外打印时看起来就是没变。
、当程序要求slice的容量超大并且需要频繁的更改slice的内容时,就不应该用slice,改用list更合适。
、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 main

import "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 anything
byte* array; // actual data
uintgo 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 数组以及slice切片

    老虞学GoLang笔记-数组和切片   数组 Arrays 数组是内置(build-in)类型,是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值.在初始化后长度是固定的,无法修改其 ...

  2. golang中,slice的几个易混淆点

    slice在golang中是最常用的类型,一般可以把它作为数组使用,但是比数组要高效呀.不过,我感觉这个东西用的不好坑太多了.还是需要了解下他底层的实现 slice的结构定义 type slice s ...

  3. Golang中的Slice与数组

    1.Golang中的数组 数组是一种具有固定长度的基本数据结构,在golang中与C语言一样数组一旦创建了它的长度就不允许改变,数组的空余位置用0填补,不允许数组越界. 数组的一些基本操作: 1.创建 ...

  4. golang的array/slice

    相同点 由相同类型的元素组合构成 元素有序排列,0为第一个元素下标 基本使用方法相同 区别 array声明时需要指定容量大小,而且无法修改 slice可通过append增加元素,当容量不够时,会自动扩 ...

  5. 理解分布式一致性与Raft算法

    理解分布式一致性与Raft算法 永远绕不开的CAP定理 出于可用性及负载方面考虑,一个分布式系统中数据必然不会只存在于一台机器,一致性简单地说就是分布式系统中的各个部分保持数据一致 但让数据保持一致往 ...

  6. golang ----array and slice

    Go Slices: usage and internals Introduction Go's slice type provides a convenient and efficient mean ...

  7. golang学习之slice基本操作

    slice的增删改查: //删除 func remove(slice []interface{}, i int) []interface{} { // copy(slice[i:], slice[i+ ...

  8. Golang 入门 : 切片(slice)

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

  9. 转 Golang 入门 : 切片(slice)

    https://www.jianshu.com/p/354fce23b4f0 切片(slice)是 Golang 中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合.切片是围绕动态数组的概 ...

随机推荐

  1. memcache相同主域名下的session共享

    本配置适合具有相同主域名的多台服务器进行session共享. 例如:www.lee.com , bbs.lee.com(多个子域名). 配置session保存在memcache: ini_set(&q ...

  2. js的DOM对象

    1.js的Array对象           ** 创建数组(三种)                          - var arr1 = [1,2,3];                    ...

  3. knockout-validation不自动插入错误消息

    <div data-bind="validationOptions:{insertMessages:false}"> <div class="valid ...

  4. 浅谈T-SQL中的特殊联结

    引言 上一篇博客我们介绍了交叉联接,内联接,外联接3种基本的联接操作.这一篇文章我们介绍一些特殊的联接操作. 组合联接 组合联接就是联接条件涉及到联接两边的多个列的查询.当需要根据主键-外键关系来联接 ...

  5. 关于JS的几点TIPS

    作为前端基本工作每天都会用到JS...但是我们对JS真的都了解吗,或者说有什么tips是我们不知道的呢.. So..此文关于JS的几点tips..... 一:定时器(可传多个参数) 首先是一个一般的定 ...

  6. phpmyadmin中访问时出现2002 无法登录 MySQL 服务器

    phpmyadmin中访问时出现2002 无法登录 MySQL 服务器! 解决方法如下: 修改phpmyadmin目录中libraries文件夹下的config.default.php文件 $cfg[ ...

  7. ECshop安装及报错解决方案总结

    一.安装ECshop ECShop是一款B2C独立网店系统 ,适合企业及个人快速构建个性化网上商店.系统是基于PHP语言及MYSQL数据库构架开发的跨平台开源程序.2006年3月推出以来1.0版以来, ...

  8. Java网络编程学习

    服务器是指提供信息的计算机或程序,客户机是指请求信息的计算机或程序,而网络用于连接服务器与客户机,实现两者相互通信.但有时在某个网络中很难将服务器与客户机区分开.我们通常所说的“局域网”(Local ...

  9. 部署Apache网站访问统计-AWStats分析系统

    环境根据:http://www.cnblogs.com/zzzhfo/p/5925786.html 1.安装AWStats软件包 将软件包解压到httpd服务器中的/usr/lcoal/目录下 [ro ...

  10. hexo问题篇(偶尔抽抽疯)

    hexo安安稳稳的跑了很久,然后 ....让人心碎的hexo问题,华丽丽的摔倒在坑里,只因update了hexo version最是哪一句 hexo server让人欲哭无泪 -问题场景 设备: Ma ...