slice使用了解
切片
什么是slice
Go中的切片,是我们经常用到的数据结构。有着比数组更灵活的用法,那么作者就去探究下什么是切片。
我们先来了解下切片的数据结构
type slice struct {
array unsafe.Pointer // 指针
len int // 长度
cap int // 容量
}
切片一共三个属性:指针,指向底层的数组;长度,表示切片可用元素的个数,也就是说使用下标 对元素进行访问的时候,下标不能超过的长度;容量,底层数组的元素个数,容量》=长度。
底层的数组是可以被多个切片同时指向的,因此对一个切片元素的操作可能会影响到其他的切片。
slice的创建使用
序号 | 方式 | 代码示例 |
---|---|---|
1 | 直接声明 | var slice []int |
2 | new | slice := *new([]int) |
3 | 字面量 | slice := []int{1,2,3,4,5} |
4 | make | slice := make([]int, 5, 10) |
5 | 从切片或数组截取 | slice := array[1:5] 或 slice := sourceSlice[1:5] |
第一种创建出来的 slice 其实是一个 nil slice。它的长度和容量都为0。和nil比较的结果为true。
这里比较混淆的是empty slice,它的长度和容量也都为0,但是所有的空切片的数据指针都指向同一个地址 0xc42003bda0。空切片和 nil 比较的结果为false。
下面是它的内部结构:
创建方式 | nil切片 | 空切片 |
---|---|---|
方式一 | var s1 []int | var s2 = []int{} |
方式二 | var s4 = *new([]int) | var s3 = make([]int, 0) |
长度 | 0 | 0 |
容量 | 0 | 0 |
和nil比较 | true | false |
nil 切片和空切片很相似,长度和容量都是0,官方建议尽量使用 nil 切片。
- 字面量
直接初始化表达式进行创建
s1 := []int{, , , ,,}
- make
slice := make([]int, , ) // 长度为5,容量为10
slice使用的一点规范
根据 Uber Go代码风格指南
nil 是一个有效的 slice
nil
是一个长度为 0 的 slice。意思是,
使用
nil
来替代长度为 0 的 slice 返回Bad Good if x == "" {
return []int{}
}if x == "" {
return nil
}检查一个空 slice,应该使用
len(s) == 0
,而不是nil
。Bad Good func isEmpty(s []string) bool {
return s == nil
}func isEmpty(s []string) bool {
return len(s) == 0
}The zero value (a slice declared with
var
) is usable immediately withoutmake()
.零值(通过
var
声明的 slice)是立马可用的,并不需要make()
。Bad Good nums := []int{}
// or, nums := make([]int) if add1 {
nums = append(nums, 1)
} if add2 {
nums = append(nums, 2)
}var nums []int if add1 {
nums = append(nums, 1)
} if add2 {
nums = append(nums, 2)
}
slice和数组的区别
slice的底层是数组,slice是对数组的封装,它描述一个数组的片段。两者都可以通过下标访问单个元素。
数组是定长的,长度定义好,不能改变。在Go中数组是不常见的,因为长度是类型的一部分,限制了它的表达 能力,比如[3]int 和 [4]int 就是不同的类型。
切片可以动态的扩容,非常灵活。切片的类型和长度没有关系。
slice的append是如何发生的
先看看append函数的原型:
func append(slice []Type, elems ...Type) []Type
append 函数的参数长度可变,因此可以追加多个值到 slice 中,还可以用 ... 传入 slice,直接追加一个切片。
slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)
append函数返回值是一个新的slice,Go编译器不允许调用了append函数后不使用返回值。
append(slice, elem1, elem2)
append(slice, anotherSlice...)
上面是不能编译通过的
使用 append 可以向 slice 追加元素,实际上是往底层数组添加元素。但是底层数组的长度是固定的,如果索引 len-1 所指向的元素已经是底层数组的最后一个元素,就没法再添加了。
这时,slice 会迁移到新的内存位置,新底层数组的长度也会增加,这样就可以放置新增的元素。同时,为了应对未来可能再次发生的 append 操作,新的底层数组的长度,也就是新 slice 的容量是留了一定的 buffer 的。否则,每次添加元素的时候,都会发生迁移,成本太高。
新slice预留buffer大小是有一定规律的。
// 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.
func growslice(et *_type, old slice, cap int) slice {
if raceenabled {
callerpc := getcallerpc()
racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
}
if msanenabled {
msanread(old.array, uintptr(old.len*int(et.size)))
} if et.size == {
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}
} newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for < newcap && newcap < cap {
newcap += newcap /
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= {
newcap = cap
}
}
} var overflow bool
var lenmem, newlenmem, capmem uintptr
const ptrSize = unsafe.Sizeof((*byte)(nil))
switch et.size {
case :
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
overflow = uintptr(newcap) > _MaxMem
newcap = int(capmem)
case ptrSize:
lenmem = uintptr(old.len) * ptrSize
newlenmem = uintptr(cap) * ptrSize
capmem = roundupsize(uintptr(newcap) * ptrSize)
overflow = uintptr(newcap) > _MaxMem/ptrSize
newcap = int(capmem / ptrSize)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem = roundupsize(uintptr(newcap) * et.size)
overflow = uintptr(newcap) > maxSliceCap(et.size)
newcap = int(capmem / et.size)
} // The check of overflow (uintptr(newcap) > maxSliceCap(et.size))
// in addition to capmem > _MaxMem is needed to prevent an overflow
// which can be used to trigger a segfault on 32bit architectures
// with this example program:
//
// type T [1<<27 + 1]int64
//
// var d T
// var s []T
//
// func main() {
// s = append(s, d, d, d, d)
// print(len(s), "\n")
// }
if cap < old.cap || overflow || capmem > _MaxMem {
panic(errorString("growslice: cap out of range"))
} var p unsafe.Pointer
if et.kind&kindNoPointers != {
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(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(); i < lenmem; i += et.size {
typedmemmove(et, add(p, i), add(old.array, i))
}
}
} return slice{p, old.len, newcap}
}
其中这一段是重点的代码,我们可以看到
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for < newcap && newcap < cap {
newcap += newcap /
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= {
newcap = cap
}
}
}
复制Slice和Map注意事项
slice 和 map 包含指向底层数据的指针,因此复制的时候需要当心。
接收 Slice 和 Map 作为入参
需要留意的是,如果你保存了作为参数接收的 map 或 slice 的引用,可以通过引用修改它。
Bad | Good |
---|---|
func (d *Driver) SetTrips(trips []Trip) { |
func (d *Driver) SetTrips(trips []Trip) { |
返回 Slice 和 Map
类似的,当心 map 或者 slice 暴露的内部状态是可以被修改的。
Bad | Good |
---|---|
type Stats struct { |
type Stats struct { |
总结
- 切片是对底层数组的一个抽象,描述了它的一个片段。
- 切片实际上是一个结构体,它有三个字段:长度,容量,底层数据的地址。
- 多个切片可能共享同一个底层数组,这种情况下,对其中一个切片或者底层数组的更改,会影响到其他切片。
- append 函数会在切片容量不够的情况下,调用 growslice 函数获取所需要的内存,这称为扩容,扩容会改变元素原来的位置。
- 扩容策略并不是简单的扩为原切片容量的 2 倍或 1.25 倍,还有内存对齐的操作。扩容后的容量 >= 原容量的 2 倍或 1.25 倍。
- 当直接用切片作为函数参数时,可以改变切片的元素,不能改变切片本身;想要改变切片本身,可以将改变后的切片返回,函数调用者接收改变后的切片或者将切片指针作为函数参数。
参考
- 【快速理解Go数组和切片的内部实现原理】 https://i6448038.github.io/2018/08/11/array-and-slice-principle/
- 【GO代码风格指南 Uber Go】 https://github.com/uber-go/guide
- 【深度解密Go语言之Slice】 https://mp.weixin.qq.com/s/MTZ0C9zYsNrb8wyIm2D8BA
slice使用了解的更多相关文章
- Matlab slice方法和包络法绘制三维立体图
前言:在地球物理勘探,流体空间分布等多种场景中,定位空间点P(x,y,x)的物理属性值Q,并绘制三维空间分布图,对我们洞察空间场景有十分重要的意义. 1. 三维立体图的基本要件: 全空间网格化 网格节 ...
- jQuery之常用且重要方法梳理(target,arguments,slice,substring,data,trigger,Attr)-(一)
1.jquery data(name) data() 方法向被选元素附加数据,或者从被选元素获取数据. $("#btn1").click(function(){ $(" ...
- js url.slice(star,end) url.lastIndexOf('/') + 1, -4
var url = '"http://60.195.252.25:15518/20151228/XXSX/作三角形的高.mp4")' document.title = url.sl ...
- JavaScript中的slice,splice,substr,substring,split的区别
万恶的输入法,在sublime中会显示出繁体字,各位看官见谅. 1.slice()方法:该方法在数组和string对象中都拥有. var a = [1,2,3,4,5,6]; var s = 'thi ...
- Max double slice sum 的解法
1. 上题目: Task description A non-empty zero-indexed array A consisting of N integers is given. A tripl ...
- js中substr,substring,slice。截取字符串的区别
substr(n1,n2) n1:起始位置(可以为负数) n2:截取长度(不可以为0,不可以为负数,可以为空) 当n1为正数时,从字符串的n1下标处截取字符串(起始位置),长度为n2. 当n1为负数时 ...
- JS 中 Array.slice() 和 Array.splice()方法
slice slice()就是对应String的substring()版本,它截取Array的部分元素,然后返回一个新的Array: var arr = ['A', 'B', 'C', 'D', 'E ...
- 【javascript 技巧】Array.prototype.slice的妙用
Array.prototype.slice的妙用 开门见山,关于Array 的slice的用法可以参考这里 http://www.w3school.com.cn/js/jsref_slice_arra ...
- golang中的slice翻转存在以及map中的key判断
//slice翻转 func stringReverse(src []string){ if src == nil { panic(fmt.Errorf("the src can't be ...
- jQuery中slice()用法总结
<!DOCTYPE html> <html> <head lang="en"> <meta charset="utf-8&quo ...
随机推荐
- Python中常见的报错名称
Python中常见的报错名称 1.SyntaxError 语法错误.看看是否用Python关键字命名变量,有没有使用中文符号,运算符.逻辑运算符等符号是不是使用不规范. 2.IndentationEr ...
- System.Text.Json 序列化对所有 JSON 属性名称使用 camel 大小写
asp.net core3.x 新增的序列号接口System.Text.Json 序列化时,如果要对所有 JSON 属性名称使用 camel 大小写 将 JsonSerializerOptions.P ...
- 114 Flatten Binary Tree to Linked List [Python]
114 Flatten Binary Tree to Linked List Given a binary tree, flatten it to a linked list in-place. 将二 ...
- settings插拔式源码
创建一个文件夹notify __init__.py import settings import importlib def send_all(content): for path_str in se ...
- 【分布式锁】07-Zookeeper实现分布式锁:Semaphore、读写锁实现原理
前言 前面已经讲解了Zookeeper可重入锁的实现原理,自己对分布式锁也有了更深的认知. 我在公众号中发了一个疑问,相比于Redis来说,Zookeeper的实现方式要更好一些,即便Redis作者实 ...
- Oracle12C的卸载过程
1.找到自己的Oracle12C安装目录,一般的安装目录为D:\app\u01\product\12.1.0\dbhome_1\deinstall ,双击deintall.dat文件进行卸载. 2.耐 ...
- 分布式配置中心Apollo
1,什么是分布式配置中心 项目中配置文件比较繁杂,而且不同环境的不同配置修改相对频繁,每次发布都需要对应修改配置,如果配置出现错误,需要重新打包发布,时间成本较高,因此需要做统一的分布式注册中心,能做 ...
- SpringBoot,SpringMvc, SpringCloud
1,SpringBoot VS SpringMvc VS SpringBoot SpringBoot: SpringBoot 是一个快速开发的框架,能够快速的整合第三方框架,简化XML配置,全部采用注 ...
- 瀑布流vue-waterfall的高度设置
最近用vue做项目,用到了瀑布流vue-waterfall,其中遇到高度的设置问题,大概介绍下,希望可以帮到一些人 1.安装 npm install --save vue-waterfall 2.引入 ...
- ftp 无法显示远程文件夹
翻阅了网上前辈们的答案,都未能解决,所以就研究了一下 不需要防火墙的情况,关闭防火墙即可 下面使用的iptables防火墙验证的,其他的请自行验证 研究了好久,发现ftp使用端口波动很大,大概在300 ...