百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

你说你熟悉Slice,这道slice题你能答对吗?

wptr33 2025-07-10 21:28 6 浏览

每当你花费大量时间使用某种特定工具时,深入了解它并了解如何高效地使用它是很值得的。

最近,看到一个关于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语言中,数组是一个固定长度的、存储同构类型元素的连续序列。根据描述,可以得知,数组有两个属性:类型、长度。这两个属性相同的数组是等价的。

比如下面的abc是三个不同的数组。其中,ab长度相同,但类型不同,ac类型相同,但长度不同。

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中字符串处理的高效方法...