面试复习提纲

Go

数据结构

Go 本身只有 数组切片映射字符串 几种数据结构

数组

数组是基本的顺序表,在初始化时需要声明长度与类型,而后会申请 类型长度 × 数据个数 的内存空间。
从底层来看,数组是一段定长的内存,借助于下标可以实现指定位置数据的快速访问。
不同长度的数组之间不能互相赋值
为了提升实际效率,小于 4 个的数组会直接在栈上初始化(展开成多行赋值),而多余 4 个的则会在静态存储区初始化后拷贝到栈上

切片

切片功能上实现了可变长度的数组,可以按需自动增长和缩小。在申请切片时,除去长度外,还可以标明容量。容量为为暂未使用的空间,尽管已经分配给该数组,但是在未主动扩容前,只能访问长度部分的内存。
切片将会分配cap长度的内存,并将len长度的内存标为可用。对于这len长度的部分,可以当作数组一样使用。每当新插入数据时,则会从cap获取空间到len中。这样可以避免频繁的申请内存及复制。

插入元素时,如果容量不足以容纳新的元素,就会触发扩容。当切片长度小于 1024 时,每次扩容都会直接翻倍;而超过 1024 时,则会变为 1.25 倍。但如果插入的元素很多,则会根据插入元素的数量进行扩容。

需要注意的是,如果对切片取子切片,实际上是共用的同一段地址,也即如果b = a[2:4],那么修改a[3]会同时改变b[2]
下一个需要注意的是,如果a由于append触发扩容后,由于被分配了新的内存,这时再修改a[3]就不会再影响b[2]
如果分别对b进行append(),并且未触发扩容,那么其会覆盖a的值
也即子切片实际上是原本切片的一个“窗口”,要避免产生这个窗口,可以使用copy(b, a[2:4])

同样,由于切片取子切片会复用内存,如果从一个大切片取出一个小的子切片,那么直到子切片被释放前,大切片都不会被释放,这可能会浪费大量的内存。

具体可见下面代码的输出

package main

import "fmt"

func main() {
	a := make([]int, 5, 7)
	for i := 0; i < len(a); i++ {
		a[i] = i
	}
	// b := make([]int, 2)
	// copy(b, a[2:4])
	b := a[2:4]
	fmt.Println(a, len(a), cap(a)) // [0 1 2 3 4] 5 7
	fmt.Println(b, len(b), cap(b)) // [2 3] 2 5
	a[3] = 33
	fmt.Println(a, len(a), cap(a)) // [0 1 2 33 4] 5 7