Go语言基本容器

一、数组

在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([]类型,数量,容量)

切片的增删改查

  1. 切片直接通过下标访问元素

  2. 切片的插入使用内置函数: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
}
  1. 切片的删除

删除头部前i个元素

a = a[i+1:]

删除尾部后i个元素

a = a[:len(a)-i]

删除从索引i开始的N个元素

a = append(a[:i],a[i+N:]...)
  1. 切片的修改是直接对地址指向的值进行修改,如果切片是某个数组的切片会直接修改原数组的值

切片的复制

  1. 深拷贝,新的切片对象会创建一个新的数组,原来切片的内容拷贝进去,并且新的切片对象可以自定义长度和容量,是完全独立的。
copy( destSlice, srcSlice []T) int
  1. 浅复制就是仅仅是复制切片的值(原始指针,长度,容量),如果修改切片中元素会影响到原来的切片。
destSlice := srcSlice

切片的扩容

切片的长度达到容量后,在切片元素小于1024的情况下,直接将容量扩充一倍,否则按不小于25%的比例进行扩容。

切片的特点

  1. 切片如果发生扩容会导致原始指针地址发生变化,因此要及时写回原切片。
  2. 切片在头部添加元素一般都会导致内存的重新分配,append()函数可以实现链式操作,但是在链式调用过程中会创建临时切片。
  3. 切片a复制给切片b后,如果对切片b修改会直接修改到a中的元素,这是浅复制。
  4. 切片是个连续容器,因此对切片进行插入、删除(除了尾部操作)都会移动之后的元素,因此效率欠佳,如需这方面操作应该选择链表结构。

三、列表

Go语言通过container/list包实现了列表容器,list的底层实现原理是双向链表。

初始化

import "container/list"

// 声明 + 初始化
变量名 := list.New()

// 声明
var 变量名 list.List

增删改查

  1. 插入元素

(1) 双向列表可以支持两端插入元素,对应的方法分别是PushFront(插入元素)PushBack(插入元素),这两个方法都会返回一个句柄*list.Element结构,一般通过.Value获取其中的值。

(2) 还可以根据之前返回的*list.Element结构,在进行针对这个元素前后的插入,通过方法InsertBefore(插入元素,句柄)InsertAfter(插入元素,句柄),同样会返回一个句柄*list.Element结构。

  1. 删除元素

通过Remove(句柄)方法进行删除,返回值是删除的值。这个方法必须传递一个元素的句柄对象,一般用于在列表的迭代中进行元素的删除。

  1. 修改元素

直接通过句柄的.Value属性进行赋值即可修改元素的值

  1. 遍历列表
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的扩容条件:

  1. 达到了加载因子(6.5)的限制范围,计算方法:len(map) / 2 ^ B,其中B是2^B是桶的数量。
  2. 当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由一定的限定条件:

  1. 不能是切片:切片属于动态类型。但是数组可以作为key因为是固定类型
  2. 不能是指针:每个指针数值都不同,没有hash的意义。
  3. 不能是函数、闭包。

保存任意类型的字典

可以使用空接口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
	})
    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值