数组、切片、映射是Golang的最重要的数据结构,下面是对这3种数据结构的一点个人总结:
一、数组
- 数组是切片和映射的基础数据结构。
- 数组是一个长度固定的数据类型,存储着一段具有相同数据类型元素的连续内存块。
- 因为数组占用的内存是连续分配的,所以对数组的操作速度很快。
- 声明数组的方式:4种
- var array1 [5]int
- array1 := [5]int{3,5,6,3,2}
- array1 := [...]int{3,4,7,8,1} //根据数组字面量中元素的个数来确定数组的长度
- array1 := [5]int{0:3,3:5,4:8} //只初始化指定索引的元素,其余元素保持零值
- 数组元素的类型可以为任何内置类型,也可以是某种结构类型,也可以是指针类型。
- 数组变量的类型包括数组长度和元素的类型,只有两部分都相同的数组才可相互赋值。
- 多维数组:数组本身只有一个维度,只能通过组合多个数组来创建多维数组
- var array [4][2]int
- array := [4][2]int{2:{20,21},3:{41,25}}
- array := [4][2]int{2:{1:21},3:{0:41}}
- array[2][1] = 10
- 在函数间传递数组:由于在函数间传递变量时,传递的总是变量的值的副本,所以在传递数组变量时将拷贝整个数组!在定义函数时,对于较大的数据类型应该把参数设计为指针类型,这样在调用函数时,只需在栈上分配给每个指针8字节的内存,但这意味着会改变指针指向的值(共享的内存),其实大部分情况下应该使用切片类型,而不是数组。
二、切片slice
- 切片slice是引用类型,它引用了其指针字段所指向的底层数组的一部分或全部。
- 创建和初始化:
- slice1 := make( []string, 5 ) //创建一个长度、容量都为5的string类型的切片
- slice1 := make( []string, 3, 5 ) //创建一个长度为3,容量为5的string类型的切片
- slice2 := []string{ "red","blue","green" } //长度和容量均为3的切片
- slice2 := []int{ 99:1 } //长度和容量均为100,并初始化第100个元素为1
- 切片的长度可以按需自动增长或缩小:
- 动态增长是通过append()函数实现的,最大长度不得超过容量值
- 缩小则是通过对它再次切片来实现,通过再次切片获得的新切片将和原切片共享底层数组,它们的指针指向同一个底层数组。
- 再次切片:假设原切片slice容量为k,新切片newSlice可见的底层数组部分为原切片的0索引元素位置开始到底层数组末尾
- newSlice := slice[ i : j ] //新切片包含原切片slice的可见的底层数组部分的索引为 i 的元素开始到第j个元素,即索引为j-1的元素,新切片的长度为j-i,容量为k-i
- newSlice := slice[ i : j : n ] //新切片包含原切片slice的可见的底层数组部分的索引为 i 的元素开始到第j个元素,即索引为j-1的元素,新切片的长度为j-i,容量为n-i(第三个参数n-1表示新切片可扩展到的最后一个可见的底层数组部分的元素索引,这样就达到了限制容量的目的,注意:n必须>=j)
- 新切片无法访问它所指向的底层数组的第一个元素之前的部分(第一个索引之前的部分)
- 例子:ss:=[]int{10,20,30,40,50} newss:=ss[2:4:5] //newss为[30,40],容量为3
- 切片类型有3个字段:
- 指针:指向切片所包含的第一个元素在底层数组中的地址
- 长度:切片所包含的底层数组的元素的个数(切片可访问的元素的个数)
- 容量:切片允许增长到的最大元素个数,即底层数组的长度
- 由于切片只是引用了底层数组,底层数组的数据并不属于切片本身,所以一个切片只需要24字节的内存(在64位机器上):指针字段8字节、长度字段8字节、容量字段8字节。所以在函数之间直接传递切片是高效的,只需分配24字节的栈内存。
- nil切片和空切片:
- nil切片:只声明,但未初始化的切片,如var slice1 []int,nil切片可用来描述一个不存在的切片
- 空切片:长度和容量均为0的切片,创建空切片时未对元素分配任何内存,可用来表示空集合,如slice1 := make( []int, 0 ),slice2 := []int{}
- 对nil切片和空切片调用内置函数append、len、cap的效果一样
- append()函数:
slice = append(slice, elem1, elem2) //一次可追加多个值
slice = append(slice, anotherSlice...) //使用“...”将anotherSlice的所有元素追加到slice里 - 当slice还有可用的容量时,append()操作将可用的元素合并到切片的长度,并对其赋值,最后返回一个全新的切片(和旧切片共享同一个底层数组);
- 如果slice的容量不足时,append()操作会创建一个新的底层数组,并将被引用的旧值复制到新数组里,然后追加新的值;
- 原切片容量不足时,且小于1000,则新切片的容量为原容量的2倍,若大于1000,则容量的增长因子变为1.25;
- 由于容量不足时,append操作会返回一个具有自己独立的底层数组的新切片,即与原切片不共享同一底层数组,对新切片元素的修改将不会影响原切片的底层数组,技巧:在创建切片时设置长度等于容量,这样就可以强制在第一次append操作时创建新的底层数组,达到与原数组分离的目的,如newSlice := oldSlice[2:3:3]
- 切片的迭代如:for index, value range slice{ .... },index为当前迭代到的索引位置,value是当前索引位置元素的临时副本,index和value变量的内存地址是不变的,只是指向的值在不断更新。
- len函数可返还切片的长度、cap函数可返还切片的容量
- 多维切片:切片和数组一样,本身是一维的,可以组合多个切片来形成多维切片,如:slice := [][]int{ {12},{34,23} },slice[0]为{12},slice[1]为{34,23}
三、映射map
- 映射map:是一个存储键值对的无序集合,它能基于键快速检索数据,键就像索引一样,指向与该键关联的值;
- 映射是无序的,每次迭代它时顺序可能不一样,因为映射内部使用了散列表;
- 映射的散列表包含一组桶,每个桶里存储着一部分键值对;
- 映射内部使用了两个数组:
- 第一个数组:存储着用于选择桶的散列键的高八位值,该数组用于区分每个键值对要存在哪个桶里;
- 第二个数组:每个桶里都有一个字节数组,先依次存储了该桶里的所有键,之后存储了该桶的所有值;
- 在存储、删除、查找映射的键值对时,会把指定的键传给映射的散列函数,该函数把键转换为一个散列值,然后把该散列值与第一个数组比对来选择哪个桶,再到桶里的字节数组里查找对应的键和值;
- 创建和初始化映射:
- dict1 := make(map[string]int) //空映射,等同于dict1 := map[string]int{}
dict2 := map[string]int{"srf":143,"robt":342} - 映射的键:可以是任何能用“==”做比较的类型,但不可以是切片、函数、以及包含切片的类型,因为他们具有引用语义。而映射的值则可以是任意类型;
- nil映射是只声明而未初始化的映射,无法直接使用,如var dict map[string]int。空映射则可以直接使用;
- 从映射中取值:
- value := dict2["srf"] //键存在时返回对应的值,不存在时返回类型的零值
- value, ok := dict2["srf"] //ok为键是否存在的布尔标志
if ok { ...... } - 遍历映射:
- for key := range dict2 { ..... } //只接收键
- for key, value := range dict2 { ...... } //同时接收键和值
- 遍历映射的键值对时的顺序是随机,若要有序的获得映射的键值对,则需要先遍历出映射的键存到一个切片中,然后排序该切片,最后遍历该切片,按切片中元素的顺序去映射中取对应的值
- delete(dict2,"srf") 从映射中删除指定的键值对;
- 在函数间传递映射与传递切片一样,只是传递映射本身的副本,而不会复制映射所引用的所有底层数据结构,对该映射副本所做的修改将会反映到所有对这个映射的引用。
- 多维映射:即值为映射类型的映射。使用时应注意,作为值的映射也需要初始化后才能使用,如:
var m1 = make(map[int]map[string]string)
m1[13]= map[string]string{"srf":"yes"}
有疑问加站长微信联系(非本文作者)