一、数组
在go语言中,数组是一个固定长度的特定类型元素组成的集合。有别于C/C++中的数组,Go语言不能对数组进行动态扩容,因此数组是一个固定类型。而且Go语言无法使用指针偏移来遍历数组。
var 数组名 [元素数量]类型
注意:
- 类型:可以是go中的四类数据类型中任意一种
- 元素数量:必须是编译时能确定,不允许出现运行时确认大小的情况
初始化
var arr [5]int = [5]int{1,2,3} // 索引3-4会被默认初始化为0
var arr2 [...]int = [5]{1,2,3,4,5} // 可以让编译器自动推断数组初始化大小
// 默认初始化指定索引 arr[1] = 2
var arr [5]int = [5]int{1:2}
比较大小
go语言对于数组变量,如果两个数组长度相等并且类型相同,可以使用==
或!=
来进行比较。当数组内元素全部相等或者数组长度为0时两数组相等。
遍历方式
for i:=0;i<len(arr);i++ {
fmt.Printf("%d ",arr[i])
}
for i,v := range arr {
fmt.Printf("arr[%d] = %d",i,v)
}
数组间赋值
go语言数组两数组长度一致的话可以直接按值复制到另一个数组内的
var arr1 [5]int = [5]int{1,2,3}
var arr2 [5]int
arr2 = arr1
arr2[1] = 3
fmt.Println(arr1,arr2)
// [1 2 3 0 0] [1 3 3 0 0]
二、切片
切片由三个部分组成:指向一个序列起始元素的指针,元素数量,容量。
切片的创建
slice1 := make([]类型,数量,容量)
切片的增删改查
-
切片直接通过下标访问元素
-
切片的插入使用内置函数:
append(切片,元素(任意数量)/切片) [] T
尾部插入
a := []int{7,8,9}
a = append(a,1,2,3) // [7,8,9,1,2,3]
头部插入
// 切片作为参数可以和原来切片共享一个内存段,发生重叠也没有关系
// 切片需要手动解包
a := []int{7,8,9}
a = append(a,a[1:3]...) // [7 8 9 8 9]
中间插入
// 在索引i处插入元素x
func insert(slice []int, i int, x int) []int {
// 需要手动对切片解包
slice = append(slice[:i], append([]int{x}, slice[i:]...)...)
return slice
}
- 切片的删除
删除头部前i个元素
a = a[i+1:]
删除尾部后i个元素
a = a[:len(a)-i]
删除从索引i开始的N个元素
a = append(a[:i],a[i+N:]...)
- 切片的修改是直接对地址指向的值进行修改,如果切片是某个数组的切片会直接修改原数组的值
切片的复制
- 深拷贝,新的切片对象会创建一个新的数组,原来切片的内容拷贝进去,并且新的切片对象可以自定义长度和容量,是完全独立的。
copy( destSlice, srcSlice []T) int
- 浅复制就是仅仅是复制切片的值(原始指针,长度,容量),如果修改切片中元素会影响到原来的切片。
destSlice := srcSlice
切片的扩容
切片的长度达到容量后,在切片元素小于1024的情况下,直接将容量扩充一倍,否则按不小于25%的比例进行扩容。
切片的特点
- 切片如果发生扩容会导致原始指针地址发生变化,因此要及时写回原切片。
- 切片在头部添加元素一般都会导致内存的重新分配,append()函数可以实现链式操作,但是在链式调用过程中会创建临时切片。
- 切片a复制给切片b后,如果对切片b修改会直接修改到a中的元素,这是浅复制。
- 切片是个连续容器,因此对切片进行插入、删除(除了尾部操作)都会移动之后的元素,因此效率欠佳,如需这方面操作应该选择链表结构。
三、列表
Go语言通过container/list
包实现了列表容器,list的底层实现原理是双向链表。
初始化
import "container/list"
// 声明 + 初始化
变量名 := list.New()
// 声明
var 变量名 list.List
增删改查
- 插入元素
(1) 双向列表可以支持两端插入元素,对应的方法分别是PushFront(插入元素)
和PushBack(插入元素)
,这两个方法都会返回一个句柄*list.Element
结构,一般通过.Value
获取其中的值。
(2) 还可以根据之前返回的*list.Element
结构,在进行针对这个元素前后的插入,通过方法InsertBefore(插入元素,句柄)
和InsertAfter(插入元素,句柄)
,同样会返回一个句柄*list.Element
结构。
- 删除元素
通过Remove(句柄)
方法进行删除,返回值是删除的值。这个方法必须传递一个元素的句柄对象,一般用于在列表的迭代中进行元素的删除。
- 修改元素
直接通过句柄的.Value
属性进行赋值即可修改元素的值
- 遍历列表
import "container/list"
l := list.New()
l.PushBack("CPU")
l.PushBack("GPU")
l.PushBack("DMA")
for ele := l.Front();ele != nil;ele = l.Next() {
fmt.Println(ele.Value)
}
四、字典
map底层通过hash表实现,因此是一个无序容器,但可以实现O(1)的查找效率。
map读线程安全,但同时读写是线程不安全,所以不是一个线程安全的容器。当对map进行并发读/写的时候会报出错误fatal error: concurrent map read and map write
。因此在并发场景下需要使用sync.Map
。
声明方式
var 字典名称 map[键类型]值类型
使用len()函数可以获取map中pair的数量。
切片可以作为值类型,由此可以实现一个key映射到多个value上
初始化
// 声明字典,但未进行初始化
var mapCreated map[string]int
// 对字典进行初始化,分配内存空间
mapCreated = make(map[string]int, 1) // 参数:类型,容量
// 新增k-v
mapCreated["k1"] = 1
扩容机制
map的扩容条件:
- 达到了加载因子(6.5)的限制范围,计算方法:
len(map) / 2 ^ B
,其中B是2^B
是桶的数量。 - 当overflow的bucket过多的时候。一种情况是map的中Bucket的数量小于
2^15
时,如果bucket中key的数量大于2^B
时就发生扩容。
遍历
直接使用for k,v := range map1 { }
即可。
每次遍历返回的key-value顺序都不相同,因为底层是随机选择bucket开始遍历。并且,因为map的增量扩容机制,也导致了遍历顺序每次不一定相同。
删除元素
直接使用go语言的内置函数delete(map变量名,键)
如果要实现清空,则可以直接通过make函数新建一个map来实现
ps:go语言的并行垃圾回收效率极高,因此清空字典通过新建map比循环删除元素快得多
聚合类型做key
需要求解hashCode,并处理hash碰撞冲突,对于hash碰撞可以通过切片存储冲突的key来解决
type Profile struct {
Name string // 名字
Age int // 年龄
Married bool // 已婚
}
var mapper = make(map[interface{}]int)
a := Profile{Name: "张三", Age: 30, Married: true}
mapper[a] = 1
for k, v := range mapper {
fmt.Printf("%v -- %v\n", k, v)
}
// {张三 30 true} -- 1
map的key由一定的限定条件:
- 不能是切片:切片属于动态类型。但是数组可以作为key因为是固定类型
- 不能是指针:每个指针数值都不同,没有hash的意义。
- 不能是函数、闭包。
保存任意类型的字典
可以使用空接口interface{}
来表示map
类型,这样就可以实现一个存储任意类型的map,Go语言底层会帮我们做hash运算
// 创建一个Dictinary结构
type Dictionary struct {
data map[interface{}]interface{}
}
// 根据key获取value
func (d *Dictionary) Get(key interface{}) interface{} {
return d.data[key]
}
// 根据key、value设置键值对
func (d *Dictionary) Put(key interface{}, value interface{}) {
d.data[key] = value
}
// 根据key删除
func (d *Dictionary) Delete(key interface{}) {
delete(d.data,key)
}
// 清空
func (d *Dictionary) Clear() {
d.data = make(map[interface{}]interface{})
}
// 遍历集合,可以自定义机制来随时终止遍历
// 把回调函数callback作为方法参数,返回值设为bool类型,有利于流程控制
func (d * Dictionary) Visit(callback func(key,value interface{}) bool) {
// 如果回调结果为空
if callback == nil {
return
}
for k,v := range d.data {
// 如果callback返回false终止循环
if !callback(k,v) {
return
}
}
}
// 初始化Dictionary
func InitDictionary() *Dictionary {
dict := &Dictionary{}
d.Clear()
return d
}
// 客户端调用
func main() {
dict := InitDictionary()
// 添加游戏数据
dict.Set("My Factory", 60)
dict.Set("Terra Craft", 36)
dict.Set("Don't Hungry", 24)
// 自定义遍历逻辑
dict.Visit(func(key,value interface{}) bool {
// 将接口类型转换为int类型
if value.(int) > 40 {
fmt.Println(key,"很贵")
return true
}
fmt.Println(key,"很便宜")
return true
})
}