文章首发:公众号 newbmiao
Dig101: dig more, simplified more and know more

Slice作为go常用的数据类型,在日常编码中非常常见。 相对于数组的定长不可变,slice使用起来就灵活了许多。

0x01 slice 到底是什么?

首先我们看下源码中slice结构的定义

// src/runtime/slice.go
type slice struct {array unsafe.Pointerlen   intcap   int
}

slice数据结构如上,Data指向底层引用的数组内存地址, len是已用长度,cap是总容量。 为验证如上所述,我们尝试声明一个slice a,获取 a的sliceHeader头信息,并用%p获取&a, sh, a, a[0]的地址 看看他们的地址是否相同。

a := make([]int, 1, 3)
//reflect.SliceHeader 为 slice运行时数据结构
sh := (*reflect.SliceHeader)(unsafe.Pointer(&a))
fmt.Printf("slice header: %#vnaddress of a: %p &a[0]: %p |  &a: %p sh:%p ", sh, a, &a[0],&a, sh)//slice header: &reflect.SliceHeader{Data:0xc000018260, Len:1, Cap:3}
//address of a: 0xc000018260 &a[0]: 0xc000018260 | &a: 0xc00000c080 sh:0xc00000c080

结果发现a和&a[0]地址相同。 这个好理解,切片指向地址即对应底层引用数组首个元素地址 而&a和sh及sh.Data指向地址相同。这个是因为这三个地址是指slice自身地址。 这里【slice自身地址不同于slice指向的底层数据结构地址】, 清楚这一点对于后边的一些问题会更容易判断。

这里作为一个小插曲,我们看下当fmt.Printf("%p",a)时发生了什么 内部调用链 fmtPointer -> Value.Pointer 然后根据Pointer方法对应slice的注释如下

// If v's Kind is Slice, the returned pointer is to the first
// element of the slice. If the slice is nil the returned value
// is 0.  If the slice is empty but non-nil the return value is non-zero.

发现没,正是我们上边说的,slice不为空时,返回了第一个元素的地址 有点迷惑性是不是,但其实作为使用slice的我们,更关心的是底层指向的数据不是么。

再一点就是,基于go中所有赋值和参数传递都是值传递,对于大数组而言,拷贝一个指向他的slice就高效多了 上一篇Go之for-range排坑指南 有过描述, 详见 0x03 对大数组这样遍历有啥问题?

总结下, slice是一个有底层数组引用的结构里,有长度,有容量。

就这么简单? 不,光这样还不足以让它比数组更好用。 slice还支持非常方便的切片操作和append时自动扩容,这让他更加flexible

0x02 slice能比较么?

答案是【只能和nil比较】

s := make([]int, 5)
a := s
println(s == a)
//invalid operation: s == a (slice can only be compared to nil)

这个也其实好理解,当你比较两个slice,你是想比较他们自身呢?(必然不同啊,因为有值拷贝) 还是比较他们底层的数组?(那长度和容量也一起比较么) 确实没有什么意义去做两个slice的比较。

0x03 花样的切片操作

slice通过三段参数来操作:x[from:len:cap] 即对x从from索引位置开始,截取len长度,cap大小的新切片返回 但是len和cap不能大于x原来的len和cap 三个参数都可省略,默认为x[0:len(x):cap(x)] 切片操作同样适用于array 如下都是通过src[:]常规对切片(指向的底层数组)或数组的引用

s:=make([]int,5)
x:=s[:]arr:=[5]int{}
y:=arr[:]

配合copy和append,slice的操作还有很多,官方wikiSlice Tricks 有更丰富的例子 比如更通用的拷贝 b = append(a[:0:0], a...) 比如cut或delete时增加对不使用指针的nil标记释放(防止内存泄露)

//Cut
copy(a[i:], a[j:])
for k, n := len(a)-j+i, len(a); k < n; k++ {a[k] = nil // or the zero value of T
}
a = a[:len(a)-j+i]//Delete
if i < len(a)-1 {copy(a[i:], a[i+1:])
}
a[len(a)-1] = nil // or the zero value of T
a = a[:len(a)-1]

不熟悉的话,建议好好练习一下去感受

0x04 append 时发生了什么?

总的来说,append时会按需自动扩容 - 容量足够,无扩容则直接拷贝待 append 的数据到原 slice 底层指向的数组 之后(原slice的len之后),并返回指向该数组首地址的新slice(len改变) - 容量不够,有扩容则拷贝原有 slice 所指向部分数据到新开辟的数组,并对待 append 的数据附加到其后,并返回新数组首地址的新slice​(底层数组,len,cap均改变)

如下代码所示,容量不够时触发了扩容重新开辟底层数组,x 和 s 底层指向的数组已不是同一个

s := make([]int, 5)
x := append(s, 1)
fmt.Printf("x dataPtr: %p len: %d cap: %dns dataPtr: %p len: %d cap: %d", x, len(x), cap(x), s, len(s), cap(s))
// x dataPtr: 0xc000094000 len: 6 cap: 10
// s dataPtr: 0xc000092030 len: 5 cap: 5

0x05 append内部优化

具体查阅源码,你会发现编译时将append分为三类并优化

除按需扩容外 - x = append(y, make([]T, y)...) 使用memClr提高初始化效率 - x = append(l1, l2...) 或者 x = append(slice, string) 直接复制l2 - x = append(src, a, b, c) 确定待append数目下,直接做赋值优化

具体编译优化如下 注释有简化,详见internal/gc/walk.go: append

switch {case isAppendOfMake(r):
// x = append(y, make([]T, y)...) will rewrite to// s := l1// n := len(s) + l2// if uint(n) > uint(cap(s)) {//   s = growslice(T, s, n)// }// s = s[:n]// lptr := &l1[0]// sptr := &s[0]// if lptr == sptr || !hasPointers(T) {//   // growslice did not clear the whole underlying array // (or did not get called)//   hp := &s[len(l1)]//   hn := l2 * sizeof(T)//   memclr(hp, hn)// }//使用memClr提高初始化效率r = extendslice(r, init)
case r.IsDDD(): // DDD is ... syntax
// x = append(l1, l2...) will rewrite to// s := l1// n := len(s) + len(l2)// if uint(n) > uint(cap(s)) {//   s = growslice(s, n)// }// s = s[:n]// memmove(&s[len(l1)], &l2[0], len(l2)*sizeof(T))//直接复制l2r = appendslice(r, init) // also works for append(slice, string).
default:
// x = append(src, a, b, c) will rewrite to// s := src// const argc = len(args) - 1// if cap(s) - len(s) < argc {//     s = growslice(s, len(s)+argc)// }// n := len(s)// s = s[:n+argc]// s[n] = a// s[n+1] = b// ...//确定待append数目下,直接做赋值优化r = walkappend(r, init, n)
}

这里关于append实现有几点可以提下

扩容的策略是什么?

答案是【总的来说是至少返回要求的长度n最大则为翻倍】 具体情况是: - len<1024时2倍扩容 - 大于且未溢出时1.25倍扩容 - 溢出则直接按申请大小扩容 - 最后按mallocgc内存分配大小适配来确定len. (n-2n之间)

​扩容留出最多一倍的余量,主要还是为了减少可能的扩容频率。​ mallocgc内存适配实际是go内存管理做了内存分配的优化, 当然内部也有内存对齐的考虑。 雨痕Go学习笔记第四章内存分配,对这一块有很详尽的分析,值得一读。

至于为啥要内存对齐可以参见Golang 是否有必要内存对齐?,一篇不错的文章。

扩容判断中uint的作用是啥?

//n为目标slice总长度,类型int,cap(s)类型也为int
if uint(n) > uint(cap(s))s = growslice(T, s, n)
}

答案是【为了避免溢出的扩容】

int有正负,最大值math.MaxInt64 = 1<<63 - 1 uint无负数最大值math.MaxUint64 = 1<<64 - 1 uint正值是int正值范围的两倍,int溢出了变为负数,uint(n)则必大于原s的cap,条件成立 到growslice内部,对于负值的n会panic,以此避免了溢出的扩容

内存清零初始化: memclrNoHeapPointers vs typedmemclr?

答案是【这个取决于待清零的内存是否已经初始化为type-safe(类型安全)状态,及类型是否包含指针】

具体来看,memclrNoHeapPointers使用场景是 - 带清零内存是初始化过的,且不含指针 - 带清零内存未初始化过的,里边内容是“垃圾值”(即非type-safe),需要初始化并清零

其他场景就是typedmemclr, 而且如果用于清零的Type(类型)包含指针,他会多一步WriteBarrier(写屏障),用于为GC(垃圾回收)运行时标记对象的内存修改,减少STW(stop the world)

所以memclrNoHeapPointers第一个使用场景为啥不含指针就不用解释了。

想了解更多可以看看zero-initialization-versus-zeroing 以及相关源码的注释memclrNoHeapPointers和typedmemclr

本文代码见 NewbMiao/Dig101-Go

欢迎关注我,不定期深挖技术

go int 最大值_Dig101 - Go之灵活的slice相关推荐

  1. kotlin int最大值_Kotlin程序查找三个数字中的最大值

    kotlin int最大值 Input 3 integer numbers, we have to find the largest of these input numbers. 输入3个整数,我们 ...

  2. c++语言int最大值,c++ 关于如何获取int型的最大值

    之前有一个题,因为求最小值时minn初始赋值不够大,导致一直没过,卡了很久. 之后我就发愤图强学了这方面的内容: int型的最大值是0x7fffffff, 可以算一下 0x7FFFFFFF 是多少 每 ...

  3. mysql的int最大值_MySQL中int最大值深入讲解

    导语 前两天看到的问题,展开写一下. 字节 我们都知道计算机是以二进制为基础.存储的基本单位是 Bit,也称为比特.二进制位.1bit 可以表示 0 或者 1 两个数字,是可能存在的最小的信息量,任何 ...

  4. mysql中int最大多少,MySQL中int最大值深入讲解

    MySQL中int最大值深入讲解 导语 前两天看到的问题,展开写一下. 字节 我们都知道计算机是以二进制为基础.存储的基本单位是 Bit,也称为比特.二进制位.1bit 可以表示 0 或者 1 两个数 ...

  5. int 最大值的5种表示方式

    一时兴起,想到了五种int最大值的表示方式 函数库里宏定义:INT_MAX 十六进制表示:0x7fffffff 2^31 - 1 位运算表示:(1 << 31) - 1 二进制全1然后右移 ...

  6. MySQL 中 int 最大值

    导语 前两天看到的问题,展开写一下. 字节 我们都知道计算机是以二进制为基础.存储的基本单位是 Bit,也称为比特.二进制位.1bit 可以表示 0 或者 1 两个数字,是可能存在的最小的信息量,任何 ...

  7. python3 获取int最大值

    python2 中获取int最大值 import sys i = sys.maxint print i 但是在python3中,报错: AttributeError: module 'sys' has ...

  8. int最大值java_Java 中一个int型数组的求最大值最小值 | 学步园

    小友今日看单元测试,发现的一个测试用例的代码.而测试的是某个类的最大值,最小值方法. 在此把测试类发上来.保留此地,如有人对此有兴趣,看以看下.闲话少说: public class MaxMinToo ...

  9. php int最大值探究

    官方最大值 PHP_INT_MAX 官方最大值为 32平台:2147483647 64平台:9223372036854775807 The largest integer supported in t ...

最新文章

  1. Duilib界面库学习笔记
  2. 一个比较简单、非常简单的计算器
  3. USTC English Club Note20171014(2)
  4. 鸿蒙系统可以替代安卓吗,华为今天发布的鸿蒙系统,到底能不能替代安卓?
  5. 当输入www.baidu.com会发生什么
  6. textview字体切换颜色_百元耳机支持主从切换,关键双耳双麦还能立体降噪
  7. ubuntu 10.04 虚拟机建立tftp服务器
  8. html换行添加前缀,为什么使用css3要加前缀?
  9. 重读领域驱动设计——如何说好一门通用语言
  10. VMware 虚拟机安装
  11. SDL如何嵌入到QT中?!
  12. opencv:VS无法导入源文件(环境配置不全解决100%有效)
  13. 影响中国软件开发20人
  14. java、class、jar、aar、dex、smali、apk之间的关系
  15. 或且非 java_Java且或非的符号
  16. Windows Server 2003的32位企业版支持4G以上内存
  17. 利用CUDA流重叠计算和数据传输
  18. Aspose.CAD使用教程:使用 C# 将 DGN 转换为 JPEG、PNG 或 TIFF 图像
  19. 政务大数据系列9:政务大数据的价值链
  20. 凤凰大视野----冷暖人生: 记录平凡人的普通人生

热门文章

  1. 【Paper】2021_Observer-Based Controllers for Incrementally Quadratic Nonlinear Systems With Disturbanc
  2. 2.1 0/1分类问题-机器学习笔记-斯坦福吴恩达教授
  3. 内核启动流程分析(四)源码浅析
  4. 做一个基于python的树莓派MCU性能-温度监控仪表盘
  5. 坚果pro2s7.0.1系统更新截图(订阅号长截图测试)PhotoScape图片编辑器应用
  6. 第三方工具生成密钥对连接GCP服务器(putty生成密钥远程连接服务器)
  7. PDF转Word技巧,看这篇足够
  8. 刷脸秒入仓,阿里云云盾实人认证助力心怡科技迎战双11
  9. 说说对npm的开发模式和生产模式的理解
  10. ASP.NET MVC5+EF6+EasyUI 后台管理系统(63)-WebApi与Unity注入