你说你熟悉Slice,这道slice题你能答对吗?
wptr33 2025-07-10 21:28 46 浏览
每当你花费大量时间使用某种特定工具时,深入了解它并了解如何高效地使用它是很值得的。
最近,看到一个关于slice的面试题,感觉挺有意思。如下:
var test, another []uint8
fmt.Println("len1:", len(test), cap(test))
test = make([]uint8, 5, 10)
fmt.Println("len2:", len(test), cap(test))
another = append(test, 1, 2, 3)
fmt.Println("len3:", len(another), cap(another))
fmt.Println("len4:", len(test), cap(test))
copy(test, []uint8{6, 6, 6, 6})
fmt.Println("content1:", another, test)
another = append(another, 1, 2, 3)
fmt.Println("len5:", len(another), cap(another))
copy(test, []uint8{5, 5})
fmt.Println("content2:", another[0], test[0])
这道面试题并不难,但是囊括了slice的方方面面。建议先停下来,仔细想下,并实际运行下,看看自己是否对slice完全了解了,没条件实际运行的读者可以看文末实际输出。
也可以在评论区写出你的答案,我们一起讨论。
slice到底是什么?和数组有什么关系?
slice是Go语言提供的重要数据类型,也是Gopher日常编码中最常使用的类型之一,在go语言规范中,有这么一句话:在使用过程中,应该尽量避免使用数组,使用slice替代数组。这是为什么呢?
在Go语言中,数组是一个固定长度的、存储同构类型元素的连续序列。根据描述,可以得知,数组有两个属性:类型、长度。这两个属性相同的数组是等价的。
比如下面的a、b、c是三个不同的数组。其中,a、b长度相同,但类型不同,a、c类型相同,但长度不同。
var a [5]int
var b [6]byte
var c [6[int
在Go语言中传递数组是纯粹的值拷贝,对于元素类型长度较大或元素个数较多的数组,如果直接以数组类型参数传递到函数中会有不小的性能损耗。
根据Golang编码规范,推荐使用slice,其实,在Go语言中,数组充当的更多是“幕后使者”的角色,作为一种底层存储数据结构,而slice,则走到台前,作为底层数据结构的访问。
切片打开了访问底层数组的新姿势
从上图可以看出,操作slice实际上就是操作数组。
slice是引用类型,更适合用来做函数参数之间传递。这是因为slice作为函数参数传递的时候,实际传递的是slice的内部表示,带来的性能损耗很小且恒定,甚至是忽略不计。
slice在Golang中的内部表示如下:
type slice struct {
array unsafe.Pointer // 指向底层数组的指针
len int // 长度,即slice中当前元素的个数
cap int // 容量
}
一般,我们这么创建slice:
s := make([]byte,5)
runtime下slice的表示
使用make创建slice的时候,如果没有制定cap的值,则cap=len。
除了使用make来初始化slice外,还可以通过数组slice化的方式,即通过语法u[low:high]的方式,这种方式也被称为slice的reslicing。
u := [10]int{11,12,13,14,15,16,17,18,19,20}
s := u[3,7]
表示如下:
slice共用数组
你发现什么了吗?
如果没有,再看一张图
多个slice可以共同一个底层数组
高级特质
如果slice仅仅是这点能力,显然与他的身份不匹配。
slice有一个重要的特点:动态扩容。即可以通过内置函数append对slice进行赋值操作。
var s []byte
s = append(s,1)
slice是如何进行动态扩容的呢?别急,我们来看一段代码。
var s []int
s = append(s,11)
fmt.Println(len(s),cap(s)) // 1 1
s = append(s,12)
fmt.Println(len(s),cap(s)) // 2 2
s = append(s,13)
fmt.Println(len(s),cap(s)) // 3 4
s = append(s,14)
fmt.Println(len(s),cap(s)) // 4 4
s = append(s,15)
fmt.Println(len(s),cap(s)) // 5 8
从上面代码可以看出,len的值符合我们预期,是线性增长的,但cap的值就不怎么规律了。直接看代码可能不直观,通过下图可以更清晰的看出多次append操作,slice是如何扩容的。
slice扩容过程
现在可以得出结论:在当前底层数组容量无法满足的时候,会动态分配新的数组,新数组的长度会按照一定的算法扩增,并在新数组建立好之后,将原来数组的元素拷贝到新数组上来,原来数组在后续的GC中回收掉。
扩容算法如下:
func growslice(et *_type, old slice, cap int) slice {
...
if et.size == 0 {
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
const threshold = 256
if old.cap < threshold {
newcap = doublecap
} else {
for 0 < newcap && newcap < cap {
newcap += (newcap + 3*threshold) / 4
}
if newcap <= 0 {
newcap = cap
}
}
}
...
}
不得不提的copy函数
copy函数是Go语言提供的内置函数,用来将源slice的元素复制到目标slice中。
格式如下:
copy( destSlice, srcSlice []T) int
copy函数有两个参数:第一个是目标slice,第二个是源slice。也就是将 srcSlice 复制到 destSlice。返回值为实际发生复制元素的个数。
下面代码展示了copy函数的使用过程
s1 := []int{1, 2, 3, 4, 5}
s2 := []int{5, 4, 3}
copy(s2, s1) // 只会复制s1的前3个元素到s2中 s2=[1 2 3]
copy(s1, s2) // 只会复制s2的3个元素到s1的前3个位置 s1=[1 2 3 4 5]
两个 slice 可以共享同一个底层数组,甚至有重叠也没有问题
附上开头的答案,你对了吗?有没有反直觉的地方?
len1: 0 0
len2: 5 10
len3: 8 10
len4: 5 10
content1: [6 6 6 6 0 1 2 3] [6 6 6 6 0]
len5: 11 24
content2: 6 5相关推荐
- oracle数据导入导出_oracle数据导入导出工具
-
关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...
- 继续学习Python中的while true/break语句
-
上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个...
- python continue和break的区别_python中break语句和continue语句的区别
-
python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...
- 简单学Python——关键字6——break和continue
-
Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
-
用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...
- Python 中 break 和 continue 傻傻分不清
-
大家好啊,我是大田。...
- python中的流程控制语句:continue、break 和 return使用方法
-
Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...
- L017:continue和break - 教程文案
-
continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...
- 作为前端开发者,你都经历过怎样的面试?
-
已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...
- 面试被问 const 是否不可变?这样回答才显功底
-
作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...
- 2023金九银十必看前端面试题!2w字精品!
-
导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。...
- 前端面试总结_前端面试题整理
-
记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...
- 由浅入深,66条JavaScript面试知识点(七)
-
作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录...
- 2024前端面试真题之—VUE篇_前端面试题vue2020及答案
-
添加图片注释,不超过140字(可选)...
- 今年最常见的前端面试题,你会做几道?
-
在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...
- 一周热门
- 最近发表
-
- oracle数据导入导出_oracle数据导入导出工具
- 继续学习Python中的while true/break语句
- python continue和break的区别_python中break语句和continue语句的区别
- 简单学Python——关键字6——break和continue
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
- Python 中 break 和 continue 傻傻分不清
- python中的流程控制语句:continue、break 和 return使用方法
- L017:continue和break - 教程文案
- 作为前端开发者,你都经历过怎样的面试?
- 面试被问 const 是否不可变?这样回答才显功底
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)
