Go语言

6. Go语言内置容器

Go语言的内置容器主要有数组、切片和映射。

6.1 数组

数组是具有相同类型且长度固定的一组数据项序列,这组数据项序列对应存放在内存中的一块连续区域中。

数组中存放的元素类型可以是整型、字符串或其他自定义类型。数组在使用前需先声明,声明时必须指定数组的大小且数组大小之后不可再变。

下标从0开始。

6.1.1 声明数组
var 数组变量名 [数组长度]元素类型
package main

import "fmt"

func main() {
   var student [3]string
   fmt.Println(student)
}
6.1.2 初始化数组

在声明时赋值

var student = [3]string{"Tom","Ben","Peter"}

如果忽略中括号内的数字,不设置数组大小,Go语言编译器在编译时也可根据元素的个数来设置数组的大小,通过用“…”代替数组大小来实现

var student = [...]string{"Tom","Ben","Peter"}
package main

import "fmt"

func main() {
   var student = [...]string{"Tom", "Ben", "Peter"}
   fmt.Println(student)
}
6.1.3 range关键字

range是Go语言中非常常用的一个关键字,其主要作用就是配合for关键字对数组以及之后会介绍到的切片和映射等数据结构进行迭代。

package main

import "fmt"

func main() {
   var num = [...]int{1, 2, 3, 4}
   for k, v := range num {
      fmt.Println("变量k:", k, " ", "变量v:", v)
   }
}

range后接的表达式称为range表达式,在迭代时,关键字range会返回两个值,分别由变量k和v接收。其中k是当前循环迭代到的索引位置,v是该位置对应元素值的一份副本。

range表达式第一返回值第二返回值
数组元素下标元素值
切片元素下标元素值
映射
通道元素N/A
6.1.4 遍历数组

for循环遍历

package main

import "fmt"

func main() {
   var student = [...]string{"Tom", "Ben", "Peter"}
   for k, v := range student {
      fmt.Println("数组下标:", k, ",对应元素:", v)
   }
}
6.2 切片

相对于数组,切片(slice)是一种更方便和强大的数据结构,它同样表示多个同类型元素的连续集合,但是切片本身并不存储任何元素,而只是对现有数组的引用。

切片结构

  • 地址:切片的地址一般指切片中第一个元素所指向的内存地址,十六进制
  • 长度:切片中实际存在元素的个数
  • 容量:从切片的起始元素开始到其底层数组中最后一个元素的个数

三种切片生成方式

  • 从数组生成一个新的切片
  • 从切片生成一个新的切片
  • 直接生成一个新的切片
6.2.1 从数组生成一个新的切片
slice [开始位置:结束位置]

使用len()函数可获得当前切片长度,cap()函数可获得当前切片容量。

package main

import "fmt"

func main() {
   var student = [...]string{"Tom", "Ben", "Peter"}
   var student1 = student[1:2]
   fmt.Println("student数组:", student)
   fmt.Println("student1切片:", student1)
   fmt.Println("student数组地址为", &student[1])
   fmt.Println("student1切片地址为", &student1[0]) //元素的地址

   fmt.Println("student1切片长度为:", len(student1))
   fmt.Println("student1切片容量为:", cap(student1))
}

根据运行结果,从数组或切片生成新的切片有如下特性:

  • 新生成的切片长度:结束位置 - 开始位置
  • 新生成的切片取出的元素不包括结束位置对应的元素
  • 新生成的切片是对现有数组或切片的引用,其地址与截取的数组或切片开始位置对应的元素地址相同。
  • 新生成的切片容量指从切片的起始元素开始到其底层数组中的最后一个元素的个数
6.2.2 从切片生成一个新的切片

slice[:]来表示切片本身

package main

import "fmt"

func main() {
   var student = [...]string{"Tom", "Ben", "Peter"}
   var student1 = student[1:3]
   var student2 = student[0:1]

   fmt.Println("student数组:", student[:])
   fmt.Println("student1切片:", student1[:])
   fmt.Println("student2切片:", student2[:])

   fmt.Println("student数组地址为:", &student[1])
   fmt.Println("student1切片地址为:", &student1[0])
   fmt.Println("student2切片地址为:", &student2[0])

   fmt.Println("student1切片长度为:", len(student1))
   fmt.Println("student1切片容量为:", cap(student1))
   fmt.Println("student2切片长度为:", len(student2))
   fmt.Println("student2切片容量为:", cap(student2))
}
6.2.3 直接生成一个新的切片
var 切片变量名 []元素类型
package main

import "fmt"

func main() {
   var student []int
   fmt.Println("student切片", student)
   fmt.Println("student切片长度:", len(student))
   fmt.Println("student切片容量:", cap(student))
   fmt.Println("判定student切片是否为空:", student == nil)
}
package main

import "fmt"

func main() {
   var student = []string{"Tom", "Ben", "Peter"}
   fmt.Println("student切片:", student)
   fmt.Println("student切片长度:", len(student))
   fmt.Println("student切片容量:", cap(student))
   fmt.Println("判定student切片是否为空:", student == nil)
}
make([]元素类型,切片长度,切片容量)
package main

import "fmt"

func main() {
   var student []int
   student = make([]int, 2, 10)

   fmt.Println("student切片:", student)
   fmt.Println("student切片长度:", len(student))
   fmt.Println("student切片容量:", cap(student))

   fmt.Println("判定student切片是否为空:", student == nil)
}
6.2.4 为切片添加元素

使用append()函数来对切片进行元素的添加。

当切片不能再容纳其他元素时(即当前切片长度值等于容量值),下一次使用append()函数对切片进行元素添加,容量会按2倍数进行扩充。

package main

import "fmt"

func main() {
   student := make([] int , 1, 1)
   for i := 0 ; i < 8 ; i ++{
      student = append(student,i)
      fmt.Println("当前切片长度:",len(student),"当前切片容量:",cap(student))
   }
}
package main

import "fmt"

func main() {
   var student = [...]string{"Tom", "Ben", "Peter"}
   var student1 = student[0:1] //从student数组生成切片student1

   fmt.Println("student数组:", student)
   fmt.Println("student切片:", student1)
   student1 = append(student1, "Danny") // 对student1切片的元素添加,会覆盖引用数组对应的元素
   
   fmt.Println("扩充Danny后的student1切片:", student1, ",切片长度为:", len(student1), "切片容量为:", cap(student1))
   fmt.Println("扩充Danny后的student数组:", student)
}

由于student1切片是从student数组生成(即对student数组的引用),为student1添加元素会覆盖student数组中对应的元素。

So → 如果切片是从其他数组或切片生成,新切片的元素添加需要考虑对原有数组或切片中数据的影响。

6.2.5 从切片删除元素

Go语言没有为删除切片元素提供方法,所以需要手动将删除点前后的元素连接起来,从而实现对切片中元素的删除。

package main

import "fmt"

func main() {
   var student = []string{"Tom", "Ben", "Peter", "Danny"}
   student = append(student[0:1], student[2:]...) //这一句等价于 student = append(student[0:1],student[2],student[3])

   fmt.Println("student切片:", student)
   fmt.Println("student切片长度:", len(student))
   fmt.Println("student切片容量:", cap(student))
}

清空切片,可以把切片的开始下标和结束下标都设为0实现

package main

import "fmt"

func main() {
   var student = []string{"Tom", "Ben", "Peter", "Danny"}
   student = student[0:0]
   fmt.Println("student切片:", student)
   fmt.Println("student切片长度:", len(student))
   fmt.Println("student切片容量:", cap(student))
}
6.2.6 遍历切片

切片的遍历和数组类似,可以通过切片下标来进行遍历。切片下标同样从0开始。

6.3 映射

映射(map)是一种无序的键值对的集合,map的键类似于索引,指向数据的值。当程序中需要存放有关联关系的数据时,往往就会用到map。

country := map[string]string{
    "中国":"China",
    "美国":"America",
    "日本":"Japan",
}
6.3.1 声明映射
var map [键类型]值类型
package main

import "fmt"

func main() {
   var studentScoreMap map[string]int
   fmt.Println(studentScoreMap)
}
6.3.2 初始化映射
package main

import "fmt"

func main() {
   var studentScoreMap = map[string]int{
      "Tom":   80,
      "Ben":   85,
      "Peter": 90,
   }
   fmt.Println(studentScoreMap)
}
make(map[键类型]值类型,map容量)
package main

import "fmt"

func main() {
   var studentScoreMap map[string]int
   studentScoreMap = make(map[string]int)
   studentScoreMap["Tom"] = 80
   studentScoreMap["Ben"] = 85
   studentScoreMap["Peter"] = 90
   fmt.Println("map长度为:", len(studentScoreMap))
   fmt.Println(studentScoreMap)
}
6.3.3 遍历映射

map的遍历主要通过for循环来完成,遍历时可同时获得map的键和值。

package main

import "fmt"

func main() {
   var studentScoreMap map[string]int
   studentScoreMap = make(map[string]int)
   studentScoreMap["Tom"] = 80
   studentScoreMap["Ben"] = 85
   studentScoreMap["Peter"] = 90

   for k, v := range studentScoreMap {
      fmt.Println(k, v)
   }
}

只遍历键

for k := range studentScoreMap{
    fmt.Println(k)
}

只遍历值

for _,v := range studentScoreMap{
    fmt.Println(v)
}
6.3.4 从映射中删除键值对

Go语言通过delete()函数来对map中的指定键值对进行删除操作。

delete(map,键)

delete()函数会直接删除指定的键值对,而不是仅仅删除键或值。

Go语言没有为map提供清空所有元素的方法,想要清空map的唯一方法就是重新定义一个新的map。

6.5 知识拓展
package main

func main() {
   Gomap := make(map[int]int)
   for i := 0; i < 10000; i++ {
      go writeMap(Gomap, i, i)
      go readMap(Gomap, i)
   }
}

func readMap(Gomap map[int]int, key int) int {
   return Gomap[key]
}

func writeMap(Gomap map[int]int, key int, value int) {
   Gomap[key] = value
}
package main

import (
   "fmt"
   "sync"
)

var lock sync.RWMutex

func main() {
   GoMap := make(map[int]int)
   for i := 0; i < 100000; i++ {
      go writeMap(GoMap, i, i)
      go readMap(GoMap, i)
   }
   fmt.Println("Done")
}

func readMap(Gomap map[int]int, key int) int {
   lock.Lock() //加锁
   m := Gomap[key]
   lock.Unlock() //解锁
   return m
}

func writeMap(Gomap map[int]int, key int, value int) {
   lock.Lock()
   Gomap[key] = value
   lock.Unlock()
}
package main

import (
   "fmt"
   "sync"
)

func main() {
   var GoMap sync.Map
   for i := 0; i < 100000; i++ {
      go writeMap(GoMap, i, i)
      go readMap(GoMap, i)
   }
   fmt.Println("Done")
}

func readMap(Gomap sync.Map, key int) int {
   res, ok := Gomap.Load(key) //线程安全读取
   if ok == true {
      return res.(int)
   } else {
      return 0
   }
}

func writeMap(Gomap sync.Map, key int, value int) {
   Gomap.Store(key, value) //线程安全设置
}