1 | Golang:基础数据结构

三个常用的数据类型的大致实现,有点绕,但是很有收获!切片 Slice数据结构在64位架构的机器上,一个切片需要24字节的内存:指针字段需要8字节,长度和容量字段分别需要8字节。var slice []int 创建的数据结构如下:使用对新切片的长度与容量的计算规则如下:对底层数组容量是k的切片slic

三个常用的数据类型的大致实现,有点绕,但是很有收获!

切片 Slice

数据结构

64位架构的机器上,一个切片需要24字节的内存:指针字段需要8字节,长度和容量字段分别需要8字节

var slice []int 创建的数据结构如下:

切片数据结构

使用

对新切片的长度与容量的计算规则如下:

对底层数组容量是k的切片slice[i:j]来说
  • 长度: j - i
  • 容量: k - i
func showSliceLenAndCap() {
	//创建一个整型切片
	//其长度和容量都是5个元素
	slice := []int{10, 20, 30, 40, 50}
	fmt.Println("capacity: ", cap(slice), " length: ", len(slice))

	//创建一个新切片
	//其长度为2个元素,容量为4个元素
	newSlice := slice[1:3]
	fmt.Println("capacity: ", cap(newSlice), " length: ", len(newSlice))
}

运行结果为:

切片长度与容量

其实也很好理解,对slice[i:j] 来说,i 为新切片的第一个元素,在就切片中的数组下标,k 是原数组最大的容量,所以新切片的容量为 k - i;对 j 来说,可以视为一个前闭后开区间,即 [i, j),也就是不包含原数组中的第 j 位,所以长度为 j - i

新旧切片的联系

当多个切片基于同一个数组时,修改共享部分的数据,在其他切片中也能看见改变。

func showSharedArrayBySlice() {
	slice := []int{10, 20, 30, 40, 50}
	slice1 := slice[0:3]
	slice2 := slice[1:4]

	slice1[1] = 666
  fmt.Println("slice: ", slice)
	fmt.Println("slice1: ", slice1)
	fmt.Println("slice2: ", slice2)
	fmt.Println(slice2[0])
}

运行结果为:

切片共享底层数组

扩容

切片扩容使用 append,长度一定改变,但容量未必改变。

append 可能会导致底层数组的值被覆盖,表现为别的共享此底层数组的切片,发现内容被改变。(不一定会发生,前置条件为必须是共享的底层数组

如何创建一个不共享底层数组的切片

让新产生的切片的长度、容量相同,第一次 append 的时候,发现容量不够,会进行自动扩容,使用新的底层数组,这样新增的元素,就不会影响其他共享了数组的切片

length 在 1000 以下,扩容时 x2;以上则 x1.25。

下面是上述小点的代码表述:

func showSliceAppend() {
	slice := []int{10, 20, 30, 40, 50}
	slice1 := slice[1:2:2]
	slice2 := slice[0:3]

	showSliceLenAndCapValue(slice1)
	showSliceLenAndCapValue(slice2)

	slice1 = append(slice1, 66)
	slice2 = append(slice2, 77)

	fmt.Println("slice:  ", slice)
	fmt.Println("slice1: ", slice1)
	fmt.Println("slice2: ", slice2)
}

func showSliceLenAndCapValue(slice []int) {
	fmt.Println("capacity: ", cap(slice), " length: ", len(slice))
}

运行结果如下:

append slice

迭代

range 获取到的是一个元素副本。也就是说,range 的两个返回值中的 value 的地址在内存中只有一个。

映射 Map

关于 Golang 中 map 的实现,有篇非常透彻的文章。与 Java 中的 HashMap 实现类似,都采用拉链法,但一个很大的不同之处在于,一个桶只能放8个键值对,超过8个,会放入溢出桶

在Go语言里,通过键来索引映射时,即便这个键不存在也总会返回一个值。在这种情况下,返回的是该值对应的类型的零值。

在函数间传递映射并不会制造出该映射的一个副本。实际上,当传递映射给一个函数,并对这个映射做了修改时,所有对这个映射的引用都会察觉到这个修改。

Read more

容器镜像(4):镜像的常用工具箱

容器镜像(4):镜像的常用工具箱

前几篇在讲多架构镜像时已经用过 skopeo 和 crane 做镜像复制,这篇系统整理这两个工具的完整能力,同时介绍几个日常操作镜像时同样好用的工具。 一、skopeo:不依赖 Daemon 的镜像瑞士军刀 skopeo 的核心价值是绕过 Docker daemon,直接与 Registry API 交互。上一篇用它做镜像复制和离线传输,但它的能力远不止于此。 1.1 安装 # Ubuntu / Debian sudo apt install -y skopeo skopeo --version # skopeo version 1.15.1 1.2 inspect:免拉取检查镜像元数据 docker inspect 需要先把镜像拉到本地,skopeo inspect 直接向 Registry

容器镜像(3):多架构镜像构建

容器镜像(3):多架构镜像构建

一、什么是多架构镜像 1.1 OCI Image Index 上一篇介绍了单平台镜像的结构:一个 Manifest 指向 Config 和若干 Layer blob。多架构镜像在此之上多了一层——OCI Image Index(也叫 Manifest List),是一个轻量的索引文件,把多个单平台 Manifest 组织在一起: $ docker manifest inspect golang:1.22-alpine { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests&

容器镜像(2):containerd 视角下的镜像

容器镜像(2):containerd 视角下的镜像

一、为什么需要了解 containerd 如果你只用 docker run 跑容器,从来不关心底层,那可以不了解 containerd。但如果你在用 Kubernetes,或者想真正理解"容器运行时"是什么,containerd 是绕不开的。 事实上,当你执行 docker run 的时候,containerd 早就在后台悄悄工作了——Docker 从 1.11 版本开始,就把核心运行时剥离出来交给 containerd 负责。 1.1 Docker 的架构演变 早期的 Docker(1.10 及之前)是一个"大一统"的单体程序:一个 dockerd