阅读目录
- Go 语言数组_定义_初始化_遍历
- 定义数组
- Go 语言初始化数组
- Go 语言遍历数组
- 参考文献
- Go 语言切片(Slice)初始化_删除元素_遍历
- 什么是切片
- 声明切片
- 使用 make() 函数构造切片
- 使用 append() 函数为切片添加元素
- 从数组或切片生成新的切片
- 从指定范围中生成切片
- 重置切片
- 复制切片元素到另一个切片
- 从切片中删除元素
- 参考文献
- Go 语言字典 (Map)
- 初始化字典 map
- 字段 map 的另外一种初始化方式
- 遍历字段 map
- 删除字典 map 中键值对
- 能够在并发环境下使用的字典 sync.Map
- 参考文献
- Go 语言 list (列表)_初始化_遍历_删除
- 初始化 list (列表)
- 1.1 通过 container/list 包中的 New 方法来初始化 list
- 1.2 通过声明初始化 list
- 向 list (列表) 中添加元素
- 从 list (列表) 中删除元素
- 遍历 list (列表)
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]int{1, 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)
// 在 1 之后添加字符串 2
l.InsertAfter("2", element)
// 在 1 之前添加字符串 0
l.InsertBefore("0", element)
// 删除 element 对应的元素
l.Remove(element)
}
遍历 list (列表)
遍历 list (列表) 需要搭配 Front() 函数获取头元素,遍历过程中,只要元素不为空则可继续调用 Next 函数往下遍历:
package main
import (
"container/list"
"fmt"
)
func main() {
l := list.New()
// 头部添加字符串
l.PushFront("知其黑,受其白")
// 尾部添加字符串
l.PushBack("wgchen.blog.csdn.net")
// 遍历
for i := l.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
}
PS E:\golang\src> go run .\main.go
知其黑,受其白
wgchen.blog.csdn.net
PS E:\golang\src>
注意,在 for 语句遍历中:
- 其中 i := l.Front() 表示初始赋值,用来获取列表的头部下标;
- 然后每次会循环会判断
i != nil
,若等于空,则会退出循环,否则执行i.Next()
继续循环下一个元素;