数组切片与Map
七、数组
7.1、命名
var 数组名 [数组大小]数组类型7.2、初始化
第一种:
var arr1 [3]int = [3]int{1,2,3}
第二种:
var arr2 = [3]int{4,5,6}
第三种:
var arr3 = [...]int{12,34,45,65,67}
第四种:
var arr4 = [...]int{2:22, 1:11, 3:33, 0:55}7.3、类型
数组长度属于数组类型。
7.4、二维数组
遍历
方式一:for循环遍历
方式二:for-range遍历
func main() {
var arr [3][3]int = [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
fmt.Println(arr)
// 方式一:for循环遍历
for i := 0; i < len(arr); i++ {
for j := 0; j < len(arr[i]); j++ {
fmt.Printf("arr[%d][%d] = %d \t", i, j, arr[i][j])
}
fmt.Println()
}
fmt.Println("-----------------")
// 方式二:range循环遍历
for i, v := range arr {
for j, v2 := range v {
fmt.Printf("arr[%d][%d] = %d \t", i, j, v2)
}
fmt.Println()
}
}八、切片---左闭右开
切片是建立在数组之上
func main() {
var arr [6]int = [6]int{3, 5, 6, 7, 8, 9}
// 切片是构建在数组之上
var slice []int = arr[2:5]
fmt.Println(slice)
}切片(slice) 是一种动态数组。切片提供了便捷、灵活的数组操作方式,底层结构可以动态增长或缩小。
切片的底层结构
在 Go 中,切片的底层是基于数组实现的,其底层结构由一个 sliceHeader 管理,定义在 reflect 包中,包含以下三部分:
- 指针(Pointer):指向切片底层数组的起始位置。
- 长度(Length):表示切片当前的长度,即切片实际存储的元素个数。
- 容量(Capacity):表示从切片起始位置到底层数组末尾的最大可用空间。
因此,切片可以表示为以下结构:
type sliceHeader struct {
ptr *elementType // 指向底层数组的指针
length int // 当前切片长度
cap int // 当前切片的容量
}内存布局示意图
假设有以下代码:
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4]在这段代码中,arr 是一个长度为 5 的数组,而 s 是一个切片,表示 arr 中从索引 1 到 3 的部分。底层内存布局如下:
底层数组: [1, 2, 3, 4, 5]
↑ ↑
起始位置 长度为3
切片 s: 指向 arr[1](值为2),长度为3,容量为4(从arr[1]到arr[4])s.ptr指向arr[1]的位置。s.length为3,表示s包含的元素数量[2, 3, 4]。s.cap为4,因为从arr[1]开始到数组末尾一共有 4 个元素。
切片扩容
Go 的切片具有动态扩容能力,当切片使用 append 操作超出其容量时,Go 会分配一个新的、更大容量的底层数组,并将原切片的数据复制到新数组中。扩容机制一般遵循以下原则:
- 小切片(小于 1024 元素):每次扩容时,容量翻倍。
- 大切片(大于等于 1024 元素):每次扩容增加原来容量的 1/4 左右。
举例:
s := make([]int, 2, 2) // 创建一个长度为2,容量为2的切片
s = append(s, 1) // 超出容量,触发扩容在 append 操作中,由于超出容量,Go 会分配一个新的切片,长度为 3,容量为 4,并将数据 [0, 0, 1] 复制到新切片。
切片的共享与独立
在使用切片时要注意切片可能会共享同一个底层数组。例如:
arr := []int{1, 2, 3, 4, 5}
s1 := arr[1:4]
s2 := arr[2:5]
s1[1] = 10
fmt.Println(arr) // 输出:[1, 2, 10, 4, 5]在这里,s1 和 s2 都指向了 arr 的一部分,因此修改 s1 的值会影响 s2 和 arr。要独立地使用切片,可以使用 copy 函数创建切片的副本。
切片的拷贝
通过 copy 函数来实现对切片的拷贝,copy 会复制切片的内容,但不会共享底层数组。这意味着拷贝后的切片与原切片独立存储,修改其中一个切片不会影响另一个。
copy 函数的用法
copy 函数的语法为:
copy(dst, src []Type) intdst是目标切片(拷贝到的切片),src是源切片(被拷贝的切片)。copy返回复制的元素个数。它只会复制dst和src之间较小长度的部分。
示例代码
package main
import "fmt"
func main() {
src := []int{1, 2, 3, 4, 5}
dst := make([]int, len(src))
// 使用 copy 函数将 src 拷贝到 dst
count := copy(dst, src)
fmt.Println("拷贝的元素个数:", count) // 输出:拷贝的元素个数: 5
fmt.Println("源切片:", src) // 输出:源切片: [1 2 3 4 5]
fmt.Println("目标切片:", dst) // 输出:目标切片: [1 2 3 4 5]
// 修改目标切片
dst[0] = 10
fmt.Println("修改后的源切片:", src) // 输出:修改后的源切片: [1 2 3 4 5]
fmt.Println("修改后的目标切片:", dst) // 输出:修改后的目标切片: [10 2 3 4 5]
}结果说明
copy(dst, src)将src切片的内容逐一复制到dst切片中,两个切片的底层数据完全独立。- 修改
dst的元素不会影响到src,因为dst中的数据是新拷贝的副本。
九、映射map
Go 语言中,map 是一种内置的数据结构,用于实现键值对(key-value)存储。它可以高效地通过键快速查找、插入、和删除数据。map 是无序的,键的顺序在迭代时不保证固定。
基本语法与使用
声明和初始化
使用
make函数初始化一个map:gom := make(map[string]int) // 键是字符串类型,值是整数类型或者在声明的同时初始化:
gom := map[string]int{"apple": 1, "banana": 2, "cherry": 3}基本操作
添加或更新键值对:
gom["apple"] = 5 // 更新或添加键 "apple" 的值为 5访问元素:
govalue := m["apple"] // 获取键 "apple" 对应的值,若不存在则返回类型的零值删除键值对:
使用
delete函数从map中移除指定的键:godelete(m, "banana") // 删除键 "banana" 对应的键值对检测键是否存在:
在访问
map中的某个键时,可以使用双重赋值的形式来检查键是否存在:govalue, ok := m["apple"] if ok { fmt.Println("键存在,值为:", value) } else { fmt.Println("键不存在") }
map 的底层实现原理
Go 的 map 是基于哈希表实现的,通过哈希函数将键映射到特定的位置。每个位置可以存放一个或多个键值对,以应对哈希冲突。
哈希桶:
map将键值对分组存储在多个桶(bucket)中,每个桶中可以容纳若干个键值对。哈希冲突:
由于不同的键可能会被映射到相同的位置,Go 使用链表或开放寻址来解决冲突。在某些情况下,Go 会将冲突较多的桶再分成小桶进行分散存储。
动态扩容:
当
map中的键值对增加较多时,哈希表会自动扩容并重新分布桶,以保证查询和插入的性能。
特性与限制
无序性:
map中的元素是无序的,迭代时每次的顺序可能会不同,Go 不保证map中的键的顺序。并发安全性:
map不是并发安全的,在多线程环境下,多个 goroutine 同时读写map会导致竞态条件,通常需要使用sync.RWMutex进行加锁操作来保证安全。键的类型:
map的键可以是任何可比较的类型,比如int、string、struct(无切片、映射字段),但不能使用切片、映射、函数等不可比较的类型作为键。
示例代码
以下代码展示了 map 的基本使用:
package main
import "fmt"
func main() {
// 初始化一个 map
m := map[string]int{"apple": 1, "banana": 2, "cherry": 3}
// 添加或更新元素
m["date"] = 4
m["apple"] = 5
// 检查某个键是否存在
value, ok := m["banana"]
if ok {
fmt.Println("banana 存在,值为:", value)
} else {
fmt.Println("banana 不存在")
}
// 删除元素
delete(m, "cherry")
// 遍历 map
for key, value := range m {
fmt.Printf("%s -> %d\n", key, value)
}
}map 的零值和空 map
一个未初始化的
map的零值是nil,操作一个nilmap不会引发错误,但插入操作无效。govar m map[string]int fmt.Println(m == nil) // 输出: true
map 的使用场景
map 非常适合需要快速查找的数据结构,如:
- 统计字符或单词的出现频率
- 记录对象的映射关系(如用户 ID 到用户信息)
- 缓存查询结果以提高效率
