Golang basic_leaming2 语言容器

Posted 知其黑、受其白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang basic_leaming2 语言容器相关的知识,希望对你有一定的参考价值。

阅读目录

Go 语言数组_定义_初始化_遍历

定义数组

定义数组的格式如下:

var 数组变量名 [元素数量]T

说明:

  • 数组变量名: 定义一个数组的变量 名;
  • 元素数量: 定义数组的大小;
  • T 可以是任意基本类型,甚至可以是数组本身,若为数组,则可以实现多维数组。

下面这段代码中,定义了一个变量为 arr, 成员类型为字符串 , 大小为 3 的数组:

package main

import (
	"fmt"
)

func main() 
	// 定义一个变量为 arr, 成员类型为 string, 大小为 3 的数组
	var arr [3]string

	// 赋值操作
	arr[0] = "wgchen."
	arr[1] = "blog.csdn"
	arr[2] = ".net"

	fmt.Println(arr)

PS E:\\golang\\src> go run .\\main.go
[wgchen. blog.csdn .net]
PS E:\\golang\\src>

Go 语言初始化数组

var arr = [3]string"www.", "wgchen", ".blog.csdn"

这种写法需要保证大括号内的元素数量与数组的大小一致。

但是,还可以将定义数组大小的操作交给编译器,让编译器在编译时,根据元素的个数来确定大小:

var arr = [...]string"www.", "wgchen", ".blog.csdn"

... 表示让编译器来确定数组大小。
编译器会自动将这个数组的大小设置为 3。

Go 语言遍历数组

可以通过 for range 来遍历数组,代码如下:

package main

import (
	"fmt"
)

func main() 

	// var arr = [3]string"www.", "wgchen", ".blog.csdn"
	var arr = [...]string"www.", "wgchen", ".blog.csdn"

	for index, v := range arr 
		fmt.Printf("index: %d, value: %s\\n", index, v)
	

PS E:\\golang\\src> go run .\\main.go
index: 0, value: www.
index: 1, value: wgchen
index: 2, value: .blog.csdn
PS E:\\golang\\src>

上面代码中,index 表示数组当前的下标, v 表示当前元素的值。

参考文献

Golang 【basic_leaming】数组

Go 语言切片(Slice)初始化_删除元素_遍历

什么是切片

切片和数组类似,都是 数据集合。和数组不同的是,切片是一块动态分配大小的连续空间。它和 Java 语言中的 AarryList 集合类似。

声明切片

切片的声明格式如下:

var name []T
  • name 表示切片变量 名;
  • T 表示切片类型。

下面是示例代码:

package main

import "fmt"

func main() 
	// 声明整型切片
	var numList []int

	// 声明字符串切片
	var strList []string

	// 声明一个空切片,  表示已经分配内存,但是切片里面的元素是空的
	var numListEmpty = []int

	// 输出3个切片
	fmt.Println(numList, strList, numListEmpty)

	// 输出3个切片大小
	fmt.Println(len(numList), len(strList), len(numListEmpty))

	// 切片判定是否为空结果
	fmt.Println(numList == nil)
	fmt.Println(strList == nil)
	fmt.Println(numListEmpty == nil)

PS E:\\golang\\src> go run .\\main.go
[] [] []
0 0 0
true
true
false
PS E:\\golang\\src>

使用 make() 函数构造切片

可以通过 make() 函数动态的创建一个切片,格式如下:

make( []T, size, cap )
  • T : 切片中元素的类型;
  • size : 表示为这个类型分配多少个元素;
  • cap : 预分配的元素数量,该值设定后不影响 size, 表示提前分配的空间,设置它主要用于降低动态扩容时,造成的性能问题。

示例代码如下:

package main

import "fmt"

func main() 
	a := make([]int, 4)
	b := make([]int, 4, 10)

	fmt.Println(a, b)
	fmt.Println(len(a), len(b))

PS E:\\golang\\src> go run .\\main.go
[0 0 0 0] [0 0 0 0]
4 4
PS E:\\golang\\src>

a 和 b 切片均为大小为 2, 不同的是 b 内存空间预分配了 10 个,但是实际只使用了 2 个元素。

len() 函数计算的是元素的个数,与切片容量无关。

使用 append() 函数为切片添加元素

Go 语言 中的内置函数 append() 可以为切片动态添加元素, 示例代码如下:

package main

import "fmt"

func main() 
	// 声明一个字符串类型的切片
	var strList []string

	// 循环动态向 strList 切片中添加 20 个元素,并打印相关参数
	for i := 0; i < 10; i++ 
		line := fmt.Sprintf("wgchen.blog.csdn.net %d", i)
		strList = append(strList, line)

		fmt.Printf("len: %d, cap: %d, pointer: %p, content: %s\\n", len(strList), cap(strList), strList, strList[i])
	

PS E:\\golang\\src> go run .\\main.go
len: 1, cap: 1, pointer: 0xc000088220, content: wgchen.blog.csdn.net 0
len: 2, cap: 2, pointer: 0xc0000a03c0, content: wgchen.blog.csdn.net 1
len: 3, cap: 4, pointer: 0xc0000d4000, content: wgchen.blog.csdn.net 2
len: 4, cap: 4, pointer: 0xc0000d4000, content: wgchen.blog.csdn.net 3
len: 5, cap: 8, pointer: 0xc0000d6000, content: wgchen.blog.csdn.net 4
len: 6, cap: 8, pointer: 0xc0000d6000, content: wgchen.blog.csdn.net 5
len: 7, cap: 8, pointer: 0xc0000d6000, content: wgchen.blog.csdn.net 6
len: 8, cap: 8, pointer: 0xc0000d6000, content: wgchen.blog.csdn.net 7
len: 9, cap: 16, pointer: 0xc0000d8000, content: wgchen.blog.csdn.net 8
len: 10, cap: 16, pointer: 0xc0000d8000, content: wgchen.blog.csdn.net 9
PS E:\\golang\\src>

通过上面的代码输出,会发现 len() 并不等于 cap。

这是因为当切片空间不足以容纳足够多的元素时,切片会自动进行扩容操作, 扩容规律按切片容量的 2 倍进行扩容,如 1、2、4、8、16 ....

PS: 扩容一般发生在 append() 函数调用时。

另外,append() 函数除了添加一个元素外,还能一次性添加多个元素:

package main

import "fmt"

func main() 
	var strList []string

	// 添加一个元素
	strList = append(strList, "wgchen")

	// 添加多个元素
	strList = append(strList, "www", "wgchen", "com")

	// 添加切片
	list := []string"知其黑", "受其白"
	// list 后面的 ... 表示将 list 整个添加到 strList 切片中
	strList = append(strList, list...)

	fmt.Println(strList)

PS E:\\golang\\src> go run .\\main.go
[wgchen www wgchen com 知其黑 受其白]
PS E:\\golang\\src>

从数组或切片生成新的切片

从数组或切片生成新的切片是很常见的操作,格式如下:

slice [开始位置:结束位置]
  • slice 表示切片目标;
  • 开始位置和结束位置对应目标切片的下标。

从数组中生成切片:

package main

import "fmt"

func main() 
	var arr = [3]int1, 2, 3

	fmt.Println(arr, arr[1:2])

PS E:\\golang\\src> go run .\\main.go
[1 2 3] [2]
PS E:\\golang\\src>

[2] 是 arr[1:2] 切片操作的结果。
注意取出的元素不包括结束位置的元素。

从指定范围中生成切片

package main

import "fmt"

func main() 
	var arr = [20]int

	// 向数组中添加元素
	for i := 0; i < 20; i++ 
		arr[i] = i + 1
	

	// 指定区间
	fmt.Println(arr[8:15])

	// 中间到尾部所有元素
	fmt.Println(arr[10:])

	// 开头到中间所有元素
	fmt.Println(arr[:10])

	// 切片本身
	fmt.Println(arr[:])

PS E:\\golang\\src> go run .\\main.go
[9 10 11 12 13 14 15]
[11 12 13 14 15 16 17 18 19 20]
[1 2 3 4 5 6 7 8 9 10]
[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20]
PS E:\\golang\\src>
  • 若不填写结束位置,如 arr[10:], 则表示从下标 10 置到数组的结束位置。
  • 若不填写开始位置,如 arr[:10],则表示从 0 到下标 10 的位置。
  • 若开始位置和结束位置都不填写,如 arr[:], 则会生成一个和原有切片一样的切片。

重置切片

若把切片的开始位置和结束位置都设置为 0, 则会生成一个空的切片:

package main

import "fmt"

func main() 
	var arr = [20]int

	// 向数组中添加元素
	for i := 0; i < 20; i++ 
		arr[i] = i + 1
	

	fmt.Println(arr[0:0])

PS E:\\golang\\src> go run .\\main.go
[]
PS E:\\golang\\src>

复制切片元素到另一个切片

Go 语言内置函数 copy() 可以将一个切片中的数据复制到另一个切片中,使用格式如下:

copy( destSlice, srcSlice []T) int
  • srcSlice 代表源切片;
  • destSlice 代表目标切片。
    注意,目标切片必须有足够的空间来装载源切片的元素个数。返回值为整型,表示实际发生复制的元素个数。

演示代码如下:

package main

import "fmt"

func main() 
	// 设置元素数量为 10
	const count = 10

	// 源分片
	srcSlice := make([]int, count)

	// 给源分片赋值
	for i := 0; i < count; i++ 
		srcSlice[i] = i
	

	// 目标分片
	destSlice := make([]int, count)

	// 将 srcSlice 分片的数据复制到 destSlice 中
	copy(destSlice, srcSlice)

	fmt.Println(srcSlice)
	fmt.Println(destSlice)

PS E:\\golang\\src> go run .\\main.go
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
PS E:\\golang\\src>

另外,我们还可以复制指定范围的数据:

// 将 srcSlice 分片中指定范围的数据复制到 destSlice 中
copy(destSlice, srcSlice[4:8])

fmt.Println(srcSlice)
fmt.Println(destSlice)
[0 1 2 3 4 5 6 7 8 9]
[4 5 6 7 0 0 0 0 0 0]

从切片中删除元素

Go 语言中并没有提供特定的函数来删除切片中元素,但是可以利用切片的特性来达到目的:

package main

import "fmt"

func main() 
	// 声明一个字符串类型的切片
	arr := []string"q", "u", "a", "n", "x", "i", "a", "o", "h", "a"

	// 指定删除位置,也就是 u 元素
	index := 1

	// 打印删除位置之前和之后的元素, arr[:index] 表示的是被删除元素的前面部分数据,arr[index+1:] 表示的是被删除元素后面的数据
	fmt.Println(arr[:index], arr[index+1:])

	// 将删除点前后的元素拼接起来
	arr = append(arr[:index], arr[index+1:]...)

	fmt.Println(arr)

[q] [a n x i a o h a]
[q a n x i a o h a]

总结:Go 语言中切片删除元素的本质即 以被删除元素为分界点, 将前后两个部分的内存重新连接起来。

参考文献

Golang 【basic_leaming】切片

Go 语言字典 (Map)

初始化字典 map

Go 语言中定义字典 map 格式如下:

map [keyType]valueType
  • keyType 表示键类型;
  • valueType 表示键对应的值类型;

注意:键和键对应的值总是以一对一的形式存在。

下面是一段 map 的示例代码:

package main

import "fmt"

func main() 
	// 定义一个键类型为字符串,值类型为整型的 map
	m := make(map[string]int)

	// 向 map 中添加一个键为 “知其黑,受其白”,值为 1 的映射关系
	key := "知其黑,受其白"
	m[key] = 1

	// 输出 map 中键为 “知其黑,受其白” 对应的值
	fmt.Println(m[key])

	// 尝试输出一个不能存在的键,会输出该值类型的默认值
	n := m["知其黑,受其白2"]

	fmt.Println(n)

PS E:\\golang\\src> go run .\\main.go
1
0
PS E:\\golang\\src>

上面代码最后,我们尝试从 map 中获取一个并不存在的键(key), 此时会输出值类型的默认值,整型的默认值为 0 。

当我们需要明确知道 map 中是否存在某个键(key)时,可以使用下面这种写法:

// 声明一个 ok 变量,用来接收对应键是否存在于 map 中
value, ok := m["知其黑,受其白2"]

// 如果值不存在,则输出值
if !ok 
  fmt.Println(value)

字段 map 的另外一种初始化方式

字典 map 还存在另外一种初始化方式, 代码如下:

import "fmt"

func main() 
	m := map[int](string)
		1: "wgchen",
		2: ".blog.csdn.",
		3: "net",
	
	fmt.Println(m)

PS E:\\golang\\src> go run .\\main.go
map[1:wgchen 2:.blog.csdn. 3:net]
PS E:\\golang\\src>

上面的这段代码并没有使用 make(), 而是通过大括号的方式来初始化字典 map, 有点像 JSON 格式一样,冒号左边的是键(key) , 右边的是值(value) ,键值对之间使用逗号分隔。

遍历字段 map

Go 语言中字典 map 的遍历需要使用 for range 循环,代码如下:

package main

import "fmt"

func main() 
	m := map[int](string)
		1: "wgchen",
		2: ".blog.csdn.",
		3: "net",
	
	// 通过 for range 遍历, 获取 key, value 值并打印
	for key, value := range m 
		fmt.Printf("key: %d, value: %s\\n", key, value)
	

PS E:\\golang\\src> go run .\\main.go
key: 2, value: .blog.csdn.
key: 3, value: net
key: 1, value: wgchen
PS E:\\golang\\src>

如果只需要遍历值,也可以通过 Go 语言变量 小节中说到的匿名变量来实现:

for _, value := range m 
	fmt.Printf("value: %s\\n", value)

只遍历键时,通过下面这种方式:

for key := range m 
	fmt.Printf("key: %d\\n", key)

注意: 字典 map 是一种无序的数据结构,不要期望输出时按照一定顺序输出。如果需要按顺序输出,请使用切片 来完成。

删除字典 map 中键值对

通过内置函数 delete() 来删除键值对,格式如下:

delete(map,)
  • map 表示要删除的目标 map 对象;
  • 键表示要删除的 map 中 key 键。

示例代码如下:

package main

import "fmt"

func main() 
	m := map[int](string)
		1: "wgchen.",
		2: "blog.csdn",
		3: ".net",
	

	// 删除 map 中键为 1 的键值对
	delete(m, 1)

	// 通过 for range 遍历, 获取 key, value 值并打印
	for key, value := range m 
		fmt.Println(key, value)
	

PS E:\\golang\\src> go run .\\main.go
2 blog.csdn
3 .net
PS E:\\golang\\src>

能够在并发环境下使用的字典 sync.Map

Go 语言中的 map 在并发环境下,只读是线程安全的,同时读写是线程不安全的。

下面这段代码演示了并发环境下读写 map 会出现的问题,代码如下:

package main

func main() 
	// 初始化一个键为整型,值也为整型的 map
	m := make(map[int]int)

	// 开启一段并发代码
	go func() 
		// 无限循环往 map 里写值
		for 
			m[1] = 1
		
	()

	// 开启一段并发代码
	go func() 
		// 无限循环读取 map 数据
		for 
			_ = m[1]
		
	()

	// 死循环,让上面的并发代码在后台执行
	for 
	

运行上面的代码,会报错如下:

fatal error: concurrent map read and map write

错误提示:因为并发的对 map 进行读写。

两个并发函数不断的对 map 进行读写发生了竞态问题。

map 内部会对这种并发操作进行检查并提前发现。

正常情况下,针对并发读写的场景,是需要加锁处理的。但是加锁就意味了性能不高。Go 语言在 1.9 版本中提供了一种高效率的并发安全的 sync.Map。

sync.Map 有以下特性:

  • 无需初始化,直接声明即可;
  • sync.Map 不能使用 map 的方式进行取值、设置等操作,而是使用 sync.Map 提供的方法进行调用,如:Store 表示存储,Load 表示获取,Delete 表示删除。
  • 针对遍历操作,需要使用 Range 配合一个回调函数,回调函数会返回内部遍历出来的值。Range 参数中的回调函数返回值功能是: 需要继续遍历时,返回 true;终止遍历时,返回 false。

示例代码如下:

package main

import (
	"fmt"
	"sync"
)

func main() 
	var m sync.Map

	// 添加一些键值对到 map 中
	m.Store(1, "wgchen")
	m.Store(2, ".blog.csdn.")
	m.Store(3, "net")

	// 从 sync.Map 中获取键为 2 的值
	fmt.Println(m.Load(2))

	// 删除键值对
	m.Delete(1)

	// 遍历 sync.Map 中的键值对
	m.Range(func(key, value interface) bool 
		fmt.Printf("key: %d, value: %s\\n", key, value)
		return true
	)

PS E:\\golang\\src> go run .\\main.go
.blog.csdn. true
key: 2, value: .blog.csdn.
key: 3, value: net
PS E:\\golang\\src>

注意: sync.Map 没有提供获取 map 元素数量的方法,你需要自行遍历计数。

参考文献

Golang 【basic_leaming】map 详解

Go 语言 list (列表)_初始化_遍历_删除

列表 (list) 是一种非连续存储的容器,又多个节点组成,节点通过一些变量将彼此串联起来。

列表(list)底层常见的数据结构有: 单链表、双链表等。

在 Go 语言 中,列表的实现都在 container/list 包中,内部实现原理是双链表。

列表(list)能够方便高效地进行元素的删除、插入操作。

初始化 list (列表)

list 的初始化方法有两种:New声明
两者的效果是一样的。

1.1 通过 container/list 包中的 New 方法来初始化 list

格式如下:

变量名 := list.New()

1.2 通过声明初始化 list

格式如下:

var 变量名 = list.List

PS: 列表和 map (字典) 有什么区别?

相比较 map (字典),列表没有具体元素类型的限制,也就是说,你可以添加任意类型到 list 容器中,如 字符串 、整型 等。

这带来了一些便利,但是也存在一些问题:给一个列表添加了非期望类型的值后,在取值时,将 interface 转换为期望类型时会发生宕机。

向 list (列表) 中添加元素

双链表支持往队列前面或后面添加元素,对应的方法分别是:

  • PushFront
  • PushBack

示例代码如下:

l := list.New()
l.PushFront("知其黑,受其白")
l.PushBack("wgchen.blog.csdn.net")

关于 list (列表) 插入元素的方法,如下表所示:

方法功能
InsertAfter(v interface, mark *Element) *Element在 mark 点后面插入元素
InsertBefore(v interface, mark *Element) *Element在 mark 点前面插入元素
PushFrontList(other *List)添加 other 列表中的元素到头部
PushBackList(other *List)添加 other 列表中的元素到尾部

从 list (列表) 中删除元素

list (列表) 的插入函数的返回值是一个 *list.Element 结构,通过它来完成对列表元素的删除:

package main

import (
	"container/list"
)

func main() 
	l := list.New()

	// 头部添加字符串
	l.PushFront("知其黑,受其白")

	// 尾部添加字符串
	l.PushBack("wgchen.blog.csdn.net")

	// 尾部添加一个整型,并保持元素句柄
	element := l.PushBack(1)

	// 在 

以上是关于Golang basic_leaming2 语言容器的主要内容,如果未能解决你的问题,请参考以下文章

Golang basic_leaming1 基本语法

Golang basic_leaming1 基本语法

Golang basic_leaming3 流程控制

Golang basic_leaming3 流程控制

Golang basic_leaming变量常量变量命名规则

Golang basic_leaming变量常量变量命名规则