• 1.先做个小实验
func main(){
s1:=make([]int,0,10)
s1=[]int{1,2,3}
ss:=make([]int,0,10)
ss = s1[1:]
for i:=0;i<len(ss);i++{
ss[i] +=10
}
fmt.Println(s1) // [1 12 13]
ss =append(ss,4)
for i:=0;i<len(ss);i++{
ss[i] +=10
}
fmt.Println(s1) // [1 12 13] 而不是 [1,22,23] t:=[]int{0}
printPoint(t) // 0xc4200140a8 cap(s)= 1
t = append(t,1)
printPoint(t) // 0xc4200140c0 0xc4200140c8 cap(s)= 2
t = append(t,2)
printPoint(t) // 0xc4200160e0 0xc4200160e8 0xc4200160f0 cap(s)= 4
t = append(t,3)
printPoint(t) // 0xc4200160e0 0xc4200160e8 0xc4200160f0 0xc4200160f8 cap(s)= 4
} func printPoint(s []int){
for i:=0;i<len(s);i++{
fmt.Print(unsafe.Pointer(&s[i])," ") }
fmt.Println("cap(s)=",cap(s))
}

发现slice在进行append操作时会跟据原来的slice容量,如果append完成后新slice的容量超过原来slice的容量,则需要扩容,并且将旧的slice数据全部迁移到新的slice开辟的地址里。

  • 2.在runtime目录下找到slice.go,定位到growslice(et *_type, old slice, cap int)这个函数
type slice struct {
array unsafe.Pointer
len int
cap int
} // growslice handles slice growth during append.
// It is passed the slice element type, the old slice, and the desired new minimum capacity,
// and it returns a new slice with at least that capacity, with the old data
// copied into it.
// The new slice's length is set to the old slice's length,
// NOT to the new requested capacity.
// This is for codegen convenience. The old slice's length is used immediately
// to calculate where to write new values during an append.
// TODO: When the old backend is gone, reconsider this decision.
// The SSA backend might prefer the new length or to return only ptr/cap and save stack space. // 与append(slice,s)对应的函数growslice
// 通过切片的类型,旧切片的容量和数据得出新切片的容量,新切片跟据容量重新申请一块地址,把旧切片的数据拷贝到新切片中 func growslice(et *_type, old slice, cap int) slice { // 单纯地扩容,不写数据
if et.size == 0 {
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
// append should not create a slice with nil pointer but non-zero len.
// We assume that append doesn't need to preserve old.array in this case.
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
// 扩容规则 1.新的容量大于旧的2倍,直接扩容至新的容量
// 2.新的容量不大于旧的2倍,当旧的长度小于1024时,扩容至旧的2倍,否则扩容至旧的5/4倍
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
for newcap < cap {
newcap += newcap / 4
}
}
} // 跟据切片类型和容量计算要分配内存的大小
var lenmem, newlenmem, capmem uintptr
const ptrSize = unsafe.Sizeof((*byte)(nil))
switch et.size {
case 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
newcap = int(capmem)
case ptrSize:
lenmem = uintptr(old.len) * ptrSize
newlenmem = uintptr(cap) * ptrSize
capmem = roundupsize(uintptr(newcap) * ptrSize)
newcap = int(capmem / ptrSize)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem = roundupsize(uintptr(newcap) * et.size)
newcap = int(capmem / et.size)
} // 异常情况,旧的容量比新的容量还大或者新的容量超过限制了
if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {
panic(errorString("growslice: cap out of range"))
} var p unsafe.Pointer
if et.kind&kindNoPointers != 0 { // 为新的切片开辟容量为capmem的地址空间
p = mallocgc(capmem, nil, false)
// 将旧切片的数据搬到新切片开辟的地址中
memmove(p, old.array, lenmem)
// The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).
// Only clear the part that will not be overwritten.
// 清理下新切片中剩余地址,不能存放堆栈指针 // memclrNoHeapPointers clears n bytes starting at ptr.
//
// Usually you should use typedmemclr. memclrNoHeapPointers should be
// used only when the caller knows that *ptr contains no heap pointers
// because either:
//
// 1. *ptr is initialized memory and its type is pointer-free.
//
// 2. *ptr is uninitialized memory (e.g., memory that's being reused
// for a new allocation) and hence contains only "junk".
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
p = mallocgc(capmem, et, true)
if !writeBarrier.enabled {
memmove(p, old.array, lenmem)
} else {
for i := uintptr(0); i < lenmem; i += et.size {
typedmemmove(et, add(p, i), add(old.array, i))
}
}
} return slice{p, old.len, newcap}
}
  • 3.slice作为函数参数
func main(){
s:=make([]int,0,5)
s=append(s,1,2,3,4)
printPoint(s) // 1 0xc420018120 2 0xc420018128 3 0xc420018130 4 0xc420018138 cap(s)= 5 &s= 0xc42000a060
processSlice(s) //11 0xc420018120 12 0xc420018128 13 0xc420018130 14 0xc420018138 11 0xc420018140 cap(s)= 5 &s= 0xc42000a080
} func processSlice(ss []int){
for i:=0;i<len(ss);i++{
ss[i] +=10
}
ss=append(ss,11)
printPoint(ss)
}
func printPoint(s []int){
for i:=0;i<len(s);i++{
fmt.Print(s[i],unsafe.Pointer(&s[i])," ") }
fmt.Println("cap(s)=",cap(s),"&s=",unsafe.Pointer(&s))
}

函数中的形参slice是实参的拷贝,指向切片的指针不同,由于sice没有扩容,函数里面的slice和主函数的实参slice指向的数组地址是一样的

func main(){
s:=make([]int,0,4)
s=append(s,1,2,3,4)
printPoint(s) // 1 0xc42008c000 2 0xc42008c008 3 0xc42008c010 4 0xc42008c018 cap(s)= 4 &s= 0xc42008a020
processSlice(s) // 11 0xc420092000 12 0xc420092008 13 0xc420092010 14 0xc420092018 11 0xc420092020 cap(s)= 8 &s= 0xc42008a040 } func processSlice(ss []int){
for i:=0;i<len(ss);i++{
ss[i] +=10
}
ss=append(ss,11)
printPoint(ss)
}
func printPoint(s []int){
for i:=0;i<len(s);i++{
fmt.Print(s[i],unsafe.Pointer(&s[i])," ") }
fmt.Println("cap(s)=",cap(s),"&s=",unsafe.Pointer(&s))
}

函数中的形参slice是实参的拷贝,指向切片的指针不同,由于sice扩容了,函数里面的slice和主函数的实参slice指向的数组地址是不一样的

  • 4.总结
  1. 不要轻易的对切片append,如果新的切片容量比旧的大的话,需要进行growslice操作,新的地址开辟,数据拷贝
  2. 尽量对切片设置初始容量值以避免growslice,类似make([]int,0,100)
  3. 切片是一个结构体,保存着切片的容量,实际长度以及数组的地址
  4. 切片作为函数参数传入会进行引用拷贝,生成一个新的切片,指向同一个数组

golang slice 使用及源码分析的更多相关文章

  1. golang初探与命令源码分析

    前段时间有群友在群里问一个go语言的问题: 就是有一个main.go的main函数里调用了另一个demo.go里的hello()函数.其中main.go和hello.go同属于main包.但是在mai ...

  2. golang.org/x/mobile/exp/gl/glutil/glimage.go 源码分析

    看这篇之前,建议先看之前几篇,这几篇是基础. Go Mobile 例子 basic 源码分析 http://www.cnblogs.com/ghj1976/p/5183199.html OpenGL ...

  3. Golang package轻量级KV数据缓存——go-cache源码分析

    作者:Moon-Light-Dream 出处:https://www.cnblogs.com/Moon-Light-Dream/ 转载:欢迎转载,但未经作者同意,必须保留此段声明:必须在文章中给出原文 ...

  4. leveldb源码分析之Slice

    转自:http://luodw.cc/2015/10/15/leveldb-02/ leveldb和redis这样的优秀开源框架都没有使用C++自带的字符串string,redis自己写了个sds,l ...

  5. Go语言源码分析之unsafe

    Go语言源码分析之unsafe 1.什么是unsafe unsafe 库让 golang 可以像C语言一样操作计算机内存,但这并不是golang推荐使用的,能不用尽量不用,就像它的名字所表达的一样,它 ...

  6. Backbone源码分析(三)

    Backbone源码分析(一) Backbone源码分析(二) Backbone中主要的业务逻辑位于Model和Collection,上一篇介绍了Backbone中的Model,这篇文章中将主要探讨C ...

  7. angular源码分析:angular中脏活累活承担者之$parse

    我们在上一期中讲 $rootscope时,看到$rootscope是依赖$prase,其实不止是$rootscope,翻看angular的源码随便翻翻就可以发现很多地方是依赖于$parse的.而$pa ...

  8. 大熊君大话NodeJS之 ------ Connect中间件第二季(源码分析)

    一,开篇分析 大家好,大熊君又回来了,今天这篇文章主要是对"Connect"中间件以及相关辅助中间件,做一个源码分析系列,我想上一篇文章大家也看了, 介绍了使用方式及用途,而这篇也 ...

  9. SparkMLlib之 logistic regression源码分析

    最近在研究机器学习,使用的工具是spark,本文是针对spar最新的源码Spark1.6.0的MLlib中的logistic regression, linear regression进行源码分析,其 ...

随机推荐

  1. C# 显示纯文本对齐封装(控制显示字体长度)

    坑: 用户在写多行的纯文本上来了一个对齐的表格..如下: 原因:不同的字体下,中文,英文大写,英文小写,字符,尤其是空格..字体占用的长度是不一样的,然后显示出来就是乱的.. 然而客户要求在不同的字体 ...

  2. Linux正则表达式练习

    练习一 1.生成30位的随机口令 [root@centos7 ~]#cat /dev/urandom | tr -dc "[:alnum:]" | head -c30 RJL5qc ...

  3. Hive 本地调试方法

    关键词:hive, debug 本地调试(local debug) Hive 可分为 exec (hive-exec,主要对应源码里的ql目录) 和 metastore 两部分,其中exec对外有两种 ...

  4. HTTP Status 500 - Request processing failed; nested exception is org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)

    今天整合ssm框架 时 遇到的问题 困扰我好长时间     原因就是  mapper文件 没有被加载进来 但是 为什么没有被加载进来呢  因为中间的配置文件出了一些问题 网上大多数说法是   在pom ...

  5. [VUE]object.defineProperty的基本使用

    1.object.defineProperty 给一个对象定义一个新的属性或者在修改一个对象现有的属性,并返回这个对象 语法: Object.defineProperty(参数1,参数2,参数3) 参 ...

  6. pip和cmd常用命令

    1.pip常用命令 显示模块的详情  pip    show 安装模块   pip    install    模块名称 卸载模块    pip    uninstall    模块名称 查看当前环境 ...

  7. python Tags 母板 组件 静态文件相关 自定义simpletag inclusion_tag

    一.Tags(一)for 1.基本用法 <ul> {% for user in user_list %} <li>{{ user.name }}</li> {% e ...

  8. Rewrite JSON project with Fetch

    上传 JSON 数据 使用fetch()来发布json编码的数据. var url = 'https://example.com/profile'; var data = {username: 'ex ...

  9. SSIS - 11.For循环容器

    一.For循环容器中的3个循环变量 For循环容器,类似于编程语言中的For,用于重复执行容器内的任务,直到条件返回为False.与编程语言类似,For循环容器也需要定义以下3种循环属性: 注: 必须 ...

  10. SSIS - 8.FTP 任务

    FTP全称为 File Transfer Protocol(文件传输协议),是通过TCP网络将文件从一个服务器传输到另一个服务器.在SSIS包中,FTP任务是用来实现FTP功能的. 一.创建FTP连接 ...