在很多情况下,我们需要保存大量的数据,或者说保存一组数据,单纯地使用普通的数据类型不再能满足需求。Go语言提供了内置的四种常用数据结构:数组、切片(slice)、字典(map)、列表(list),用来保存一组数据。
1. 数组 (array)
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成,数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推,数组一档定义后,长度不能更改。
因为数组的长度是固定的,所以在Go语言中很少直接使用数组,一般都是使用切片来代替数组, 切片(slice)是可以增长和收缩的动态序列,功能也更灵活,要理解切片工作原理的话需要先理解数组。
1.1. 数组的声明
语法格式:var name [size]type
- var:定义数组变量使用的关键字。
- name:数组声明及使用时的变量名。
- size:数组的元素数量(数组长度),可以是一个表达式,但是一定要在编译时就能获得确定值,不能含有到运行时才能确认大小的数值。
- type:可以是任意基本类型,包括数组本身,类型为数组本身时,可以实现多维数组。
示例:
package mainimport "fmt"func main() {var stringarr [2]stringstringarr[0] = "hello"stringarr[1] = "Golang"fmt.Println("stringarr = ", stringarr)}//stringarr = [hello Golang]
1.2. 初始化数组
var arr = [3]int{1, 2, 3}//或arr := [3]int{1, 2, 3}
如果数组长度不确定,可以使用...代替数组长度,编译器会自动根据元素个数确定数组的长度:
var arr = [...]int{1, 2, 3, 4, 5}//或arr := [...]int{1, 2, 3, 4, 5}
如果指定了数组长度,还可以对指定位置的元素进行初始化:
func main() {arr := [5]float64{0: 1.0, 4: 5.0}fmt.Println("arr = ", arr)}//arr = [1 0 0 0 5]
1.3. 访问数组元素
使用下标索引的方式,即可对数组进行访问或者修改指定的数组元素的值:
func main() {arr := [2]string{"Hello", "Golang!"}fmt.Println("arr[0] = ", arr[0])fmt.Println("arr[1] = ", arr[1])}//arr[0] = Hello//arr[1] = Golang!
1.4. 获取数组的长度
将数组作为参数传递给len()函数,可以获得数组的长度:
func main() {arr := [2]string{"Hello", "Golang!"}fmt.Println("arr[0] = ", arr[0])fmt.Println("arr[1] = ", arr[1])fmt.Println("数组长度为:", len(arr))}//arr[0] = Hello//arr[1] = Golang!//数组长度为: 2
1.5. 数组比较
Go语言的数组比较,是使用 == 的方式,如果数组的元素个数不相同或者元素类型不相同,那么不能比较数组。
在两个数组类型相同(包括数组的长度,数组中元素的类型)的情况下,可以直接通过较运算符(==和!=)来判断两个数组是否相等,只有当两个数组的所有元素都是相等的时候数组才是相等的,不能比较两个类型不同的数组,否则程序将无法完成编译。
func main() {a := [2]int{1, 2}b := [...]int{1, 2}c := [3]int{1, 2, 3}fmt.Println(a == b) // truefmt.Println(a == c) // invalid operation: a == c (mismatched types [2]int and [3]int)}
1.6. 多维数组
Go同样支持多维数组,声明多维数组的语法格式为:var name [size1][size2]...[sizen]type
// 声明一个4行2列的数组var array [4][2]int// 声明并初始化一个4行2列的数组array1 := [4][2]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}}// 三维数组array2 := [3][3]int{{0, 1, 2},{3, 4, 5},{6, 7, 8}}
1.7. 数组是值类型
Go中的数组是值类型,而不是引用类型。当对数组进行传递时,传递的是原始数组的副本。比如:将数组a传递给变量b,通过变量b对数组进行更改,原数组并不受影响。
func main() {a := [3]int{1, 2, 3}b := ab[0] = 4b[1] = 5b[2] = 6fmt.Println(a == b)fmt.Println(a)fmt.Println(b)}//false//[1 2 3]//[4 5 6]
同样地,在把数组作为参数传递给函数时,依然是值传递,而原始数组不受影响。
2. 切片 (slice)
Go 数组的长度固定不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(动态数组),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用的传递机制。
切片数据结构包含 Go 语言需要操作底层数组的元数据,分别是指向底层数组的指针、切片访问的元素的个数len(即长度)和切片允许增长到的元素个数cap(即容量)。
2.1. 切片的声明
切片的声明有两种方式:
- 通过声明一个未指定大小的数组来定义切片:
var name []type,只需要声明切片的类型而不需要声明长度。 - 使用
make()函数创建切片:var name []type = make([]type, len),可以简写为name := make([]type, len),也可以为切片指定容量name := make([]type, len, capacity),len为切片的当前长度,capacity是可选参数,在不声明capacity的情况下,默认capacity = len。使用
make()函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。
2.2. 初始化切片
切片在未初始化时,默认为nil,长度为0,一般有如下两种形式对切片进行初始化:
- 声明且初始化切片
s := []int{1, 2, 3},[]表示是切片类型,初始值为1, 2, 3,其中capacity = len = 3。
- 使用数组初始化切片
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。从连续内存区域生成切片是常见的操作,格式如下:slice := arr[startIndex : endIndex],不包含结束位置的元素。
func main() {// 数组aa := [5]int{1, 2, 3, 4, 5}// 切片截取b := a[1:2]c := a[:5]d := a[1:]e := a[:]f := a[0:0]fmt.Println("a[1:2] = ", b)fmt.Println("a[ :5] = ", c)fmt.Println("a[1: ] = ", d)fmt.Println("a[ : ] = ", e)fmt.Println("a[0:0] = ", f)}//a[1:2] = [2]//a[ :5] = [1 2 3 4 5]//a[1: ] = [2 3 4 5]//a[ : ] = [1 2 3 4 5]//a[0:0] = []
从数组或切片生成新的切片具有如下特点:
- 取出的元素数量为:结束位置 - 开始位置,取出元素不包含结束位置对应的索引,切片最后一个元素使用
slice[len(slice)]获取; - 当缺省开始位置时,表示从开头到结束位置;
- 当缺省结束位置时,表示从开始位置到末尾;
- 两者同时缺省时,与数组本身等效;
- 两者同时为 0 时,等效于空切片,一般用于切片复位。
2.3.
切片的长度是切片中元素的数量,切片的容量是切片最大能容纳的元素数量,可以通过append()和copy()len()函数获取切片的长度,通过cap()函数获取切片的容量。除此之外,还可以使用append()向切片追加一个或者多个元素,然后返回一个新的切片。copy()函数将原切片的元素复制到目标切片,并且返回复制的元素的个数。append()函数会改变切片所引用的数组,从而影响到引用同一数组的其它切片。 当切片中没有剩余空间,即cap-len == 0时,此时将动态分配新的数组空间进行扩容,切片在扩容时,容量的扩展是按原切片容量的 2 倍数进行扩充,返回的切片指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的切片则不受影响。 ```go package main
import “fmt”
func printSlice(x []int) { fmt.Printf(“len = %d cap = %d slice = %v\n”, len(x), cap(x), x) } func main() { var s []int printSlice(s) // 允许追加空切片 s = append(s, 0) printSlice(s)
// 向切片添加一个元素s = append(s, 1)printSlice(s)// 同时添加多个元素s = append(s, 2, 3, 4)printSlice(s)// 创建切片s1,是s容量的两倍s1 := make([]int, len(s), (cap(s))*2)// 拷贝s的内容到s1copy(s1, s)printSlice(s1)
}
> 注:`copy()`方法是进行值复制,切片s1与切片s两者不存在联系,切片s发生变化时,s1不会随着变化。在上述的示例代码中,向切片添加元素都是添加到尾部,其实可以使用`append()`函数加上切片索引的形式实现在切片的任意位置插入元素。```gopackage mainimport "fmt"func main() {var s = []string{"a", "c"}// 在index处添加一个元素s = append(s[:1], append([]string{"b"}, s[1:]...)...)fmt.Println(s)// 在头部插入元素s = append([]string{"0"}, s...)fmt.Println(s)// 在index处插入切片var newSlice = []string{" ", " ", " "}s = append(s[:1], append(newSlice, s[1:]...)...)fmt.Println(s)}//[a b c]//[0 a b c]//[0 a b c]
在上述代码中,s[:1]返回的是切片的第一个元素,s[1:]返回的是从二个元开始的整个切片,切片截取搭配上append()函数可以实现非常灵活的操作。
注:在向可变参数的函数中传递切片时,需要在切片后面加上…
2.4. 删除切片元素
切片是一个引用类型(类似于一个指针),本身不保存数据,对切片做的任何修改都将反映到它所指向的底层数组。切片是引用类型,只能与 nil判定相等,不能互相判定相等。
切片和C语言指针类似,指针可以做运算偏移,但可能造成内存操作越界,切片在指针的基础上增加了大小,约束了切片对应的内存区域,切片使用中无法对切片内部的地址和大小进行手动调整,因此切片比指针更安全、强大。
func main() {a := [5]int{1, 2, 3, 4, 5}// 数组avar b []int // 声明一个切片bif (b == nil) {fmt.Println("切片为空...")}b = a[:]for i := range b {b[i]++}fmt.Println("a[1:2] = ", a)}//切片为空...//a = [2 3 4 5 6]
Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是在开头位置删除、在中间位置删除和在尾部删除,其中删除切片尾部的元素速度最快。
在开头位置删除
// 删除开头的元素可以直接移动数据指针a = []int{1, 2, 3}a = a[1:] // 删除开头1个元素a = a[N:] // 删除开头N个元素// 也可以不移动数据指针,但是将后面的数据向开头移动,可以用append原地完成a = []int{1, 2, 3}a = append(a[:0], a[1:]...) // 删除开头1个元素a = append(a[:0], a[N:]...) // 删除开头N个元素// 还可以用copy()函数来删除开头的元素a = []int{1, 2, 3}a = a[:copy(a, a[1:])] // 删除开头1个元素a = a[:copy(a, a[N:])] // 删除开头N个元素
在中间位置删除
// 删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用append或copy原地完成a = []int{1, 2, 3, ...}a = append(a[:i], a[i+1:]...) // 删除中间1个元素a = append(a[:i], a[i+N:]...) // 删除中间N个元素a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素
在尾部位置删除
a = []int{1, 2, 3}a = a[:len(a)-1] // 删除尾部1个元素a = a[:len(a)-N] // 删除尾部N个元素
示例:删除切片指定位置的元素。
package mainimport "fmt"func main() {seq := []string{"a", "b", "c", "d", "e"}// 指定删除位置index := 2// 查看删除位置之前的元素和之后的元素fmt.Println(seq[:index], seq[index+1:])// 将删除点前后的元素连接起来seq = append(seq[:index], seq[index+1:]...)fmt.Println(seq)}//[a b] [d e]//[a b d e]
切片删除元素的操作过程:
Go语言中删除切片元素的本质是,以被删除元素为分界点,将前后两个部分的内存重新连接起来。
2.5. 切片扩容
在使用append()函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变,会在内存中创建一个新的底层数组,切片指向这个新的的底层数组,而不再指向原数组,所以如果发生扩容,对切片的更改并不会影响原数组。切片在扩容时,容量的扩展规律是按原容量的 2 倍数进行扩充。
package mainimport "fmt"func main() {var arr = [5]int{1, 2, 3, 4, 5}s1 := arr[:] // 指向arr数组s2 := arr[:] // 指向arr数组// 改变s1切片的元素,底层数组改变,s2切片也改变s1[1] = 0fmt.Println("arr = ", arr)fmt.Println("s2 = ", s2)// 默认len == capfmt.Printf("长度:%d, 容量: %d\n", len(s1), cap(s1))// 向s1切片添加新元素,发生扩容s1 = append(s1, 6, 7, 8)fmt.Printf("长度:%d, 容量: %d\n", len(s1), cap(s1))fmt.Println("arr = ", arr)fmt.Println(s1)}

注:当容量不足时,添加元素会进行扩容,扩容容量为原切片的两倍;扩容会创建一个新的数组,切片指向新数组,原数组并不会发生变化。
往一个切片中不断添加元素扩容的过程,类似于公司搬家,公司发展初期,资金紧张,人员很少,所以只需要很小的房间即可容纳所有的员工,随着业务的拓展和收入的增加就需要扩充工位,但是办公地的大小是固定的,无法改变,因此公司只能选择搬家,每次搬家就需要将所有的人员转移到新的办公点。
- 员工和工位就是切片中的元素。
- 办公地就是分配好的内存。
- 搬家就是重新分配内存。
- 无论搬多少次家,公司名称始终不会变,代表外部使用切片的变量名不会修改。
- 由于搬家后地址发生变化,因此内存“地址”也会有修改。
3. 字典 (map)
Map 是一种无序的键值对的集合,可以通过 key 来快速对应的 value 数据,key 可以是所有任何可以使用==进行比较的数据类型(基本类型),比如数字型、布尔型、字符串类型等,value 可以是任意的类型。可以使用for...range进行迭代,不过,Map 是无序的,我们无法决定它的返回顺序。Map同样是引用类型,长度不固定,可以通过len()函数返回键值对的数量。3.1. map声明
Map的声明有两种方式,可以使用内建函数make()或map关键字进行声明:// 使用map关键字var name map[keyType]valueType// 使用make函数name := make(map[keyType]valueType)
注:
[keyType]和valueType之间允许有空格,未初始化的map的值为nil,不能直接使用和赋值。
| var | 声明变量使用的关键字 |
|---|---|
| name | map 变量的变量名 |
| map | 声明 map 变量的关键字 |
| keyType | map 的键的类型 |
| valueType | map 的值的类型 |
3.2. 初始化map
- 声明同时初始化 ```go package main
import “fmt”
func main() { // 声明+初始化 cityMap := map[int]string{ 1: “广州”, 2: “上海”, 3: “北京”, 4: “杭州”, } // 遍历map for key, value := range cityMap { fmt.Println(key, value) } }
> 注:每次遍历map的返回的结果顺序都是不固定的。2. 先声明后初始化```gopackage mainimport "fmt"func main() {// 声明一个mapvar cityMap map[string]string// 初始化mapcityMap["广州"] = "小蛮腰"cityMap["上海"] = "东方明珠"cityMap["北京"] = "故宫"cityMap["杭州"] = "西湖"// 遍历mapfor key, value := range cityMap {fmt.Println(key, value)}}//运行时错误:panic: assignment to entry in nil map
错误原因:map不同于array和基础类型,在声明时会初始化一个默认值,map是引用类型,如果未在声明时进行初始化,默认值是nil,不指向任何内存地址,所以nil map不能赋值,对nil map赋值会导致运行时错误。解决办法:可以在map声明后,通过make()函数为其分配内存地址后再进行赋值。
package mainimport "fmt"func main() {// 声明一个map,默认值为nil,不能直接赋值var cityMap map[string]string// 使用make函数为nil map分配内存cityMap = make(map[string]string)// 初始化mapcityMap["广州"] = "小蛮腰"cityMap["上海"] = "东方明珠"cityMap["北京"] = "故宫"cityMap["杭州"] = "西湖"// 遍历mapfor key, value := range cityMap {fmt.Println(key, value)}}

拓展:同为引用类型的slice,在使用
append()向nil slice添加元素并不会发生错误,原因在于append()函数底层的扩容机制(详情可参考2.5 切片扩容),append()函数将元素追加到切片的尾部时,如果数组太小无法进行追加,则会分配一个更大容量的数组,slice指向这个新数组。同理,将向nil slice追加元素时,会为nil slice重新分配新的数组,让nil slice指向这个数组的内存地址。nil map问题官方文档解释如下: This variable m is a map of string keys to int values:var m map[string]intMap types are reference types, like pointers or slices, and so the value of m above is nil; it doesn’t point to an initialized map. A nil map behaves like an empty map when reading, but attempts to write to a nil map will cause a runtime panic; don’t do that. To initialize a map, use the built in make function:m = make(map[string]int)nil slice问题官方文档解释如下: nil map doesn’t point to an initialized map. Assigning value won’t reallocate point address. The append function appends the elements x to the end of the slice s, If the backing array of s is too small to fit all the given values a bigger array will be allocated. The returned slice will point to the newly allocated array.
- 使用
make()初始化 ```go package main
import “fmt”
func main() { // 使用make声明并分配内存 cityMap := make(map[int]string) // 初始化map cityMap[1] = “北京” cityMap[2] = “上海” cityMap[3] = “广州” cityMap[4] = “深圳” // 遍历map for key, value := range cityMap { fmt.Println(key, value) } }
> 注:不能使用`make()`初始化map并同时进行赋值,错误示例如下:```go// 使用make同时声明和为map赋值(错误)cityMap := make(map[string]string){"广州": "小蛮腰","上海": "东方明珠","北京": "故宫","杭州": "西湖",}
总结: map的赋值一共有三种方式:
- 在使用map关键字声明时同时进行赋值初始化
- 使用map关键字声明后,使用
make()函数为其分配内存地址进行初始化,再进行赋值- 使用
make()函数同时声明和初始化,再进行赋值
3.3. 特殊类型作为value值
- 切片作为value值
正常情况下,key和value一一对应,但有些情况下,可能需要一个key对应多个value值,此时可以通过将value定义为切片类型来实现。
package mainimport "fmt"func main() {hobbyMap := make(map[string][]string)hobbyMap["张三"] = []string{"唱歌", "跳舞"}hobbyMap["李四"] = []string{"跑步", "游泳"}for k, v := range hobbyMap {fmt.Printf("%s的爱好是:%v\n", k, v)}}//张三的爱好是:[唱歌 跳舞]//李四的爱好是:[跑步 游泳]
- map作为value值
示例:
- 使用
map[string]map[string]sting的map类 型 - key:表示用户名,是唯一的,不可重复
- 如果某个用户名存在,就将其密码修改”888888”,如果不存在就增加这个用户信息(包括昵称nickname和密码 pwd)
- 编写一个函数
modifyUser(users map[string]map[string]sting, name string)完成上述功能 ```go package main
import “fmt”
func modifyUser(users map[string]map[string]string, name string) {
if users[name] != nil {// 用户存在密码改为888888users[name]["pwd"] = "888888"} else {// 用户不存在,添加用户信息users[name] = make(map[string]string, 2)users[name]["pwd"] = "888888"users[name]["nickname"] = "小小" + name //示意}
}
func main() {
users := make(map[string]map[string]string, 10)users["张三"] = make(map[string]string, 2)users["张三"]["nickname"] = "zs"users["张三"]["pwd"] = "1111111"modifyUser(users, "李四")modifyUser(users, "王五")for k, v := range users {fmt.Println(k, v)}
}
//李四 map[nickname:小小李四 pwd:888888]
//张三 map[nickname:zs pwd:1111111]
//王五 map[nickname:小小王五 pwd:888888]
3. struct作为value值<a name="cyGjb"></a>## 3.4. 增删改查1. 增加和更新`map[key] = value`,如果key不存在于map中,则会对map增加一个键值对;如果key已经存在,则会对map中key对应的value值进行更新。2. 删除Go语言提供了一个内置函数`delete()`,用于删除容器内的元素,`delete()`函数的语法格式:<br />`delete(mapName, key)`,函数无返回值,当删除不存在的key时不会报错。<br />示例:```gopackage mainimport "fmt"func main() {cityMap := make(map[int]string)cityMap[1] = "北京"cityMap[2] = "上海"cityMap[3] = "广州"cityMap[4] = "深圳"// 删除元素delete(cityMap, 1)for key, value := range cityMap {fmt.Println(key, value)}}

注:使用
delete()每次只能删除一对键值对,Golang并没有提供清空所有元素的方法,如果需要对map进行清空,可以对key进行遍历逐个删除或者map = make(...),则原来的map指向的底层数据结构会被Golang的垃圾回收机制进行回收。
- 查找
可以通过key获取map中对应的value值,语法格式为:map[key]。如果key存在,则返回对应的value值;如果key不存在,则返回value类型的默认值,如value的类型为int,当key不存在时,返回的为0,在Golang中操作map,无论key是否存在都不会出现panic或者返回error。
但是在很多情况下,需要判断key:value是否存在,为此,Golang通过在使用map[key]时返回第二参数来标识key是否存在,语法格式为:value, ok := map[key],当key存在时,返回的第二参数为true;当key不存在时,返回的第二参数为false。
func main() {dict := map[string]int{"key1": 1, "key2": 2}value, ok := dict["key1"]if ok {fmt.Printf(value)} else {fmt.Println("key1不存在...")}}
3.5. map遍历
可以通过for...range对map进行遍历,遍历时,同时返回key和value。
在某些情况下,如果只需要获得value,可以使用下划线_来替代key,示例如下:
package mainimport "fmt"func main() {dict := map[int]string{1: "张三",2: "李四",3: "王五",}// 只遍历value值for _, value := range dict {fmt.Println(value)}}//李四//王五//张三
在只需要遍历key时,可以使用如下形式:for key := range dict,无需使用匿名变量:
package mainimport "fmt"func main() {dict := map[int]string{1: "张三",2: "李四",3: "王五",}// 只遍历key值for key := range dict {fmt.Println(key)}}//1//2//3
注:Golang中的map默认是无序的,遍历的结果顺序与填充的顺序无关,可能每次遍历返回的结果都不同,如果需要返回特定顺序的结果,需要进行排序。步骤如下:
- 先将map中的key存入切片中
- 对切片进行排序
- 遍历切片,按照key输出map的value
package mainimport ("fmt""sort")func main() {dict := make(map[int]int)dict[2] = 2dict[5] = 8dict[1] = 10dict[6] = 1dict[4] = 9// 声明一个切片保存map的keyvar slice []int// 将map数据遍历保存到slice中for key := range dict {slice = append(slice, key)}// 对切片进行排序sort.Ints(slice)// 打印切片fmt.Println(slice)// 根据排序的key输出valuefor _, key := range slice {fmt.Printf("map[%v] = %v\n", key, dict[key])}}
3.6. map切片
当切片的数据类型为map时,称之为 slice of map,此时map的个数就能动态变化。
示例:使用map切片,map中保存学生的个人信息,包含name和age。
package mainimport "fmt"func main() {// map切片,放入2个mapslice := make([]map[string]string, 2)// 增加第一个学生的信息if slice[0] == nil {slice[0] = make(map[string]string, 2)slice[0]["name"] = "张三"slice[0]["age"] = "18"}// 增加第二个学生的信息if slice[1] == nil {slice[1] = make(map[string]string, 2)slice[1]["name"] = "李四"slice[1]["age"] = "20"}fmt.Println(slice)}//[map[age:18 name:张三] map[age:20 name:李四]]
3.7. 其他
- map是引用类型,遵守引用类型传递机制,一个函数在接收map后,对其进行修改,会直接修改原来的map,或者将一个map变量赋值给另一个变量时,它们都指向同一个底层map,相互之间会产生影响。
- map的容量是不固定的,可以动态变化,当向map中增加一个元素时,会自动进行扩容。
- map经常使用struct结构体作为value值,可以实现更为复杂的数据存储和管理。
3.8. sync.Map
Go语言中map如果在并发读的情况下是线程安全的,如果是在并发写的情况下,则是线程不安全的。Golang 为我们提供了一个 sync.Map 是并发写安全的。
Golang 中的 map 的 key 和 value 的类型必须是一致的,但 sync.Map 的 key 和 value 不一定是要相同的类型,不同的类型也是支持的。
此部分将在Go语言并发章节中详细介绍。4. 列表 (list)
列表是一种非连续的存储容器,由多个节点组成,节点通过一些变量记录彼此之间的关系,列表有多种实现方法,如单链表、双链表等。
在Golang中,列表使用container/list包来实现,内部的实现原理是双链表,列表能够高效地进行任意位置的元素插入和删除操作。
Golang 中的列表可以存储任意数据类型的值,列表的初始化有两种方式,分别为:使用list.New()函数初始化和使用 var 关键字初始化。注:列表的元素可以是任意类型,这既带来了便利,也引来一些问题,例如给列表中放入了一个
interface{}类型的值,取出值后,如果要将interface{}转换为其他类型将会发生宕机。
4.1. 列表初始化
- 使用
list.New()初始化列表
listName := list.New(),通过list.New() 初始化了一个名为 listName 的列表。
- 使用
var关键字初始化列表
var listName = list.List,通过 list.List 初始化了一个名为 listName 的列表。
在上面的两种初始化方式中,列表没有限制其内保存成员的类型,即任意类型的成员可以同时存在列表中。列表是非引用类型,和切片、字典不同,列表在声明后可以直接使用,而切片以及字典在声明后不能直接使用,需要经过初始化。
4.2. 增删改查
- 获取元素
元素的获取可以使用内置的 Front() 函数获取头结点,使用 Back() 函数获取尾结点,使用 Prev() 获取前一个结点,使用 Next() 获取下一个结点。列表的底层数据结构为双链表,不支持随机访问,只能顺序访问:
列表的遍历
列表的遍历需要配合Front()函数和Next()函数对列表进行遍历访问:
package mainimport ("container/list""fmt")func main() {list := list.New()// 在列表尾部插入三个元素list.PushBack(1)list.PushBack(2)list.PushBack(3)// 遍历列表for i := list.Front(); i != nil; i = i.Next() {fmt.Println(i.Value)}}//1//2//3
- 插入元素或链表
Golang的列表元素的插入有四种情景,分别为:在指定元素前插入、在指定元素后插入、在列表头部插入和在列表尾部插入,除了插入元素外,还能将整个列表插入到另一个列表中,分别为:在头部插入列表和在尾部插入列表。以上几种情况对应下面6种方法:
PushBack(v any) *Element |
添加元素v到尾部,返回插入的元素 |
|---|---|
PushBefore(v any) *Element |
添加元素v到头部,返回插入的元素 |
PushBackList(other *List) |
在尾部链接一个其他链表 |
PushFrontList(other *List) |
在头部链接一个其他链表 |
InsertAfter(v any, mark *Element) *Element |
在节点mark之后插入元素v,返回插入的元素 |
InsertBefore(v any, mark *Element) *Element |
在节点mark之前插入元素v,返回插入的元素 |
在头部、尾部插入元素list双链表支持从队列前方或后方插入元素,前方插入使用PushFront()方法,后方插入使用PushBack()方法,两个方法均返回一个*list.Element的结构。
package mainimport ("container/list""fmt")func main() {list := list.New()// 列表头部插入元素list.PushFront(1)list.PushFront('A') // 对应的数值为65list.PushFront("3")// 列表尾部插入元素list.PushBack([]int{4, 5, 6})// 遍历列表for i := list.Front(); i != nil; i = i.Next() {fmt.Println(i.Value)}}//3//65//1//[4 5 6]
注:当向头部插入元素时,后插入的在列表前面(头插法)。
在指定元素前后进行插入元素
在头部、尾部插入列表
package mainimport ("container/list""fmt")func main() {// 列表1list1 := list.New()list1.PushBack(1)list1.PushBack(2)list1.PushBack(3)// 列表2list2 := list.New()list2.PushBack(0)list2.PushBack(0)list2.PushBack(0)fmt.Println("在列表1头部插入列表2...")list1.PushFrontList(list2)for i := list1.Front(); i != nil; i = i.Next() {fmt.Print(i.Value)}fmt.Println()fmt.Println("在列表1尾部插入列表2...")list1.PushBackList(list2)for i := list1.Front(); i != nil; i = i.Next() {fmt.Print(i.Value)}}//在列表1头部插入列表2...//000123//在列表1尾部插入列表2...//000123000
- 删除元素
Golang 的列表的删除元素使用 remove()函数,删除的元素不能为空,如果为空,会报异常。Remove(e *Element) any ,该方法表示在列表中删除列表元素e,返回被删除元素的值。
