Go常用数据结构的高性能实战—— 切片篇(一)

84 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第36天,点击查看活动详情

前言

在上一篇文章中 Go常用数据结构的高性能实战—— string 篇(二) 我们学习了 strings.Join 原理以及 string 的可变性。在分析 strings.Join 原理时,主要是应用了切片和它的 append 操作追加字符串实现的字符串拼接。今天,我们就来唠一唠切片这个数据结构和它的高性能实战吧~

切片简介

切片是一种数据结构,这种数据结构便于使用和管理数据集合。切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数 append 来实现的。这个函数可以快速且高效地增长切片。还可以通过对切片再次切片来缩小一个切片的大小。因为切片的底层内存也是在连续块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。

切片的内存布局

跟 string 一样,切片也有它独特的内存布局:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
  • array 表示一个指向数组的指针
  • len 表示切片的长度
  • cap 表示切片的最大容量

从一个切片生成另一个切片

我们知道,切片其实是一块操作底层数组的数据结构。如果从一个切片生成另一个切片,那么这两个切片指向的底层数组其实是一样的。例如:

slice1 := []byte("zhongger")
slice2 := slice1[1 : len(slice1)-1]
fmt.Printf("slice1 : %s \n", slice1)
fmt.Printf("slice2 : %s \n", slice2)

输出结果:

slice1 : zhongger 
slice2 : hongge 

这其实是一个浅拷贝,这两个切片的底层数组其实是一样的。

由切片生成的切片,执行了 append 操作

看看下面这段代码

slice1 := []byte("zhongger")
slice2 := slice1[1 : len(slice1)-1]
slice2 = append(slice2, 'z')
fmt.Printf("slice1 : %s \n", slice1)
fmt.Printf("slice2 : %s \n", slice2)

输出结果一定让人感到惊讶

slice1 : zhonggez 
slice2 : honggez

为啥会这样呢?这个问题先抛出来给大家,下一篇文章我会具体的分析它的底层逻辑~