你说你熟悉Slice,这道slice题你能答对吗?
wptr33 2025-07-10 21:28 4 浏览
每当你花费大量时间使用某种特定工具时,深入了解它并了解如何高效地使用它是很值得的。
最近,看到一个关于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
相关推荐
- 台积电提出SRAM存内计算新方法,能效比可达89TOPS/W
-
芯东西(公众号:aichip001)编译|高歌编辑|云鹏芯东西3月16日消息,近期,台积电的研究人员在ISSCC2021会议上公布了一种改良的SRAM存储器阵列,该SRAM阵列采用22nm工...
- Golang中如何判断两个slice是否相等?
-
在Golang中,要判断两个slice是否相等是不能直接使用==运算符的(==只能说明两个slice是否指向同一个底层数组)。如果两个slice的底层数组相同,但长度或容量不同...
- JS入门基础知识(js基础知识总结笔记)
-
JS对象操作对象增删改查创建对象letobj={}新增属性obj.a=1修改属性obj.a='a'...
- 趣谈JS二进制:File、Blob、FileReader、ArrayBuffer、Base64
-
大家好,我是Echa。好久没跟粉丝们细聊JavaScript那点事了。做一名全栈工程师,JS基础还是要打牢,这样的话不管底层业务逻辑以及第三方框架怎么变化,都离不开基础。本文文章属于基础篇,阅读有点...
- 告别 substr() 和 substring()?更可靠的 JavaScript 字符串截取方法
-
JavaScript提供了三个主要的字符串截取方法:...
- golang第九天,切片(slice)介绍(golang 切片作为参数)
-
什么是切片golang切片是对数组的抽象。go的数组长度不可改变,在特定场景中这样的集合就不太适用,go中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追...
- Go语言零到一:数组(go struct数组)
-
引言...
- 你说你熟悉Slice,这道slice题你能答对吗?
-
每当你花费大量时间使用某种特定工具时,深入了解它并了解如何高效地使用它是很值得的。...
- Python 3.14七大新特性总结:从t-string模板到GIL并发优化
-
Python3.14已进入测试阶段,根据PEP745发布计划,该版本已停止引入新功能,也就是说新特征就应该已经固定下来了。所以本文基于当前最新的beta2版本,深入分析了Python3.14中...
- Python 幕后:Python导入import的工作原理
-
更多互联网精彩资讯、工作效率提升关注【飞鱼在浪屿】(日更新)Python最容易被误解的方面其中之一是import。...
- Python元类实现自动化编程的正确姿势
-
元类是Python中用于创建类的类。通过元类机制,开发者可在运行时动态创建和修改类,为框架开发、设计模式实现和高级架构设计提供核心支持。在Python语言的高级特性中,元类占据着独特而重要的地位。作...
- Python字符串详解与示例(python字符串类型及操作)
-
艾瑞巴蒂字符串的干货来了,字符串是程序中最常见的数据类型之一,用来表示数据文本,下面就来介绍下字符串的特性,操作和方法,和一些示例来吧道友:1.字符串的创建在python中字符串可以永单引号(...
- 恕我直言!你对Python里的import一无所知
-
文章来源:https://mp.weixin.qq.com/s/4WAOU_Lzy651IE-2zZSFfQ原文作者:写代码的明哥...
- Python基础:字符串操作(python字符串的用法)
-
字符串是Python中最常用的数据类型之一,用于表示文本数据。我们将学习如何对字符串进行常见的操作,包括创建、访问、修改和处理字符串。通过掌握这些技巧,您将能够更好地处理和操作文本数据。让我们开始吧!...
- Python 中 字符串处理的高效方法,不允许你还不知道
-
以下是Python中字符串处理的高效方法...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
-
- 台积电提出SRAM存内计算新方法,能效比可达89TOPS/W
- Golang中如何判断两个slice是否相等?
- JS入门基础知识(js基础知识总结笔记)
- 趣谈JS二进制:File、Blob、FileReader、ArrayBuffer、Base64
- 告别 substr() 和 substring()?更可靠的 JavaScript 字符串截取方法
- golang第九天,切片(slice)介绍(golang 切片作为参数)
- Go语言零到一:数组(go struct数组)
- 你说你熟悉Slice,这道slice题你能答对吗?
- Python 3.14七大新特性总结:从t-string模板到GIL并发优化
- Python 幕后:Python导入import的工作原理
- 标签列表
-
- 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)