go map and slice 2021-10-08

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go map and slice 2021-10-08相关的知识,希望对你有一定的参考价值。

参考技术A

golang是值传递,什么情况下都是值传递

那么,如果结构中不含指针,则直接赋值就是深度拷贝;

如果结构中含有指针(包括自定义指针,以及slice,map等使用了指针的内置类型),则数据源和拷贝之间对应指针会共同指向同一块内存,这时深度拷贝需要特别处理。因为值传递只是把指针拷贝了

map源码:

https://github.com/golang/go/blob/a7acf9af07bdc288129fa5756768b41f312d05f4/src/runtime/map.go

map最重要的两个结构体: hmap 和 bmap

其中 hmap 充当了哈希表中数组的角色, bmap充当了链表的角色。

其中,单个bucket是一个叫bmap的结构体.

Each bucket contains up to 8 key/elem pairs.

And the low-order bits of the hash are used to select a bucket. Each bucket contains a few high-order bits of each hash to distinguish the entries within a single bucket.

hash值的低位用来定位bucket,高位用来定位bucket内部的key

根据上面bmap的注释和 https://github.com/golang/go/blob/go1.13.8/src/cmd/compile/internal/gc/reflect.go ,

我们可以推出bmap的结构实际是

注意:在哈希桶中,键值之间并不是相邻排列的,而是键放在一起,值放在一起,来减少因为键值类型不同而产生的不必要的内存对齐

例如map[int64]int8,如果 key/elem/key/elem这样存放,那么int8类型的值就要padding 7个字节共56bits

更多可参考
https://zhuanlan.zhihu.com/p/406751292
https://studygolang.com/articles/32943

因此,slice、map作为参数传递给函数形参,在函数内部的改动会影响到原slice、map

go语言基础切片,map

目录

切片(Slice)

菜鸟教程切片
B站李文周go语言教学

切片使用

package main

import "fmt"

func main() 
	//基于数组得到切片
	a := [5]int11,22,33,44,55
	b := a[1:4]
	fmt.Println(b)
	fmt.Printf("%T\\n", b)

	//切片再次切片
	c := b[0:len(b)]
	fmt.Println(c)
	fmt.Printf("%T\\n", c)

	//make函数构造切片
	d := make([]int, 5, 10)
	fmt.Println(d)
	fmt.Printf("%T\\n", d)

	fmt.Println(len(d))//查看长度
	fmt.Println(cap(d))//查看容量


Operation results:
[22 33 44]
[]int
[22 33 44]
[]int
[0 0 0 0 0]
[]int
5
10

切片内存申请

package main

import "fmt"

func main() 
	var a []int //未初始化没内存
	var b = []int //初始化
	c := make([]int, 0) //构造

	if a == nil
		fmt.Println("a == nil")
	
	fmt.Println("a len =", len(a), "a cap =", cap(a))

	if b == nil
		fmt.Println("b == nil")
	
	fmt.Println("b len =", len(b), "b cap =", cap(b))

	if c == nil
		fmt.Println("c == nil")
	
	fmt.Println("c len =", len(c), "c cap =", cap(c))


Operation results:
a == nil
a len = 0 a cap = 0
b len = 0 b cap = 0
c len = 0 c cap = 0

切片赋值拷贝

package main

import "fmt"

func main() 
	a := make([]int ,3) //[0 0 0]
	b := a
	fmt.Println(a)
	fmt.Println(b)
	b[0] = 100
	fmt.Println(a)
	fmt.Println(b)


Operation results:
[0 0 0]
[0 0 0]
[100 0 0]
[100 0 0]

切片遍历

package main

import "fmt"

func main() 
	//切片的遍历
	a := []int1, 2, 3, 4, 5

	//第一列下标,第二列遍历出来的数据
	for i := 0;i < len(a);i++
		fmt.Println(i, a[i])
	
	fmt.Println("///")

	for index, value := range a
		fmt.Println(index, value)
	


Operation results:
0 1
1 2
2 3
3 4
4 5
///
0 1
1 2
2 3
3 4
4 5

切片扩容

定义切片的时候最好一次性定好,避免动态扩容最好

动态扩容

会发现容量存不下的时候,会重新申请更大的内存空间以及容量

package main

import "fmt"

func main() 
	var a []int //切片此时还未申请内存
	for i := 0 ;i < 10; i++
		a = append(a, i)
		fmt.Printf("%v len:%d cap:%d ptr:%p\\n", a, len(a), cap(a) ,a)
	

Operation results:
[0] len:1 cap:1 ptr:0xc000012088
[0 1] len:2 cap:2 ptr:0xc0000120d0
[0 1 2] len:3 cap:4 ptr:0xc0000101e0
[0 1 2 3] len:4 cap:4 ptr:0xc0000101e0
[0 1 2 3 4] len:5 cap:8 ptr:0xc00000e240
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc00000e240
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc00000e240
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc00000e240
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc00001a100
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc00001a100

切片扩切片

package main

import "fmt"

func main() 
	//切片的扩容
	var a []int // 还未申请内存
	
	a = append(a, 1,2,3,4,5)
	fmt.Println(a)
	b := []int12, 13, 14, 15
	a = append(a, b...)//三个点代表会把切片b的元素一个个拿出来追加到a后面
	fmt.Println(a)


Operation results:
[1 2 3 4 5]
[1 2 3 4 5 12 13 14 15]

切片copy

package main

import "fmt"

func main() 
	//copy 是传值, := 是传引用(相当于传地址,两个变量操作同一个地址中的东西)
	a := []int1,2,3,4,5
	b := make([]int, 5, 5)
	c := b
	copy(b, a)
	b[0] = 100
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)


Operation results:
[1 2 3 4 5]
[100 2 3 4 5]
[100 2 3 4 5]

删除切片

package main

import "fmt"

func main() 
	//切片删除元素
	//使用copy方法  append(a[:index], a[index+1:]...)
	a := []int11, 22, 33, 44, 55
	a = append(a[:2], a[3:]...)//三个点代表会把切片b的元素一个个拿出来追加到a后面
	fmt.Println(a)

Operation results:
[11 22 44 55]

map

Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现。
map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。

语法定义:
map[KeyType]ValueType
KeyType:键的类型
ValueType:键对应的值的类型

map类型的变量默认初始值为nil,需要使用make()函数来分配内存。语法为:
make(map[KeyType]ValueType, [cap])
其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容器。

不初始化分配内存就不能直接操作

简单使用

package main

import "fmt"

// map(映射)
func main() 
	//光声明map类型 但是没有初始化 a就是初始化值nil
	var a map[string]int
	fmt.Println(a == nil)

	//map的初始化
	a = make(map[string]int, 8)
	fmt.Println(a == nil)

	//map中添加键值对
	a["山东"] = 100
	a["北京"] = 200
	fmt.Println(a)
	fmt.Printf("a:%#v\\n", a)
	fmt.Printf("type:%T\\n", a)

	//声明map的同时完成初始化
	//如果按照第一种初始化,末尾不加, 会导致报错
	b := map[int]bool
		1:true,
		2:false,
	
	fmt.Println(b)

	c := map[string]bool
		"y1":true,
		"y2":false
	fmt.Printf("b:%#v\\n", c)
	fmt.Printf("type:%T\\n", c)



Operation results:
true
false
map[北京:200 山东:100]
a:map[string]int“北京”:200, “山东”:100
type:map[string]int
map[1:true 2:false]
b:map[string]bool“y1”:true, “y2”:false
type:map[string]bool

判断键是否在map中

package main

import "fmt"

// map(映射)
func main() 
	//判断某个键存不存在
	var scoreMap = make(map[string]int, 8)
	scoreMap["Zhangsan"] = 29
	scoreMap["Lisi"] = 76

	//判断 wangwu 在不在scoreMap中
	value, ok := scoreMap["Wangwu"]
	fmt.Println(value, ok)
	if ok
		fmt.Println("Find Wangwu ok!")
	else
		fmt.Println("No find!")
	



Operation results:
0 false
No find!

遍历map

package main

import "fmt"

// map(映射)
func main() 
	//遍历map
	var scoreMap = make(map[string]int, 8)
	scoreMap["Zhangsan"] = 29
	scoreMap["Lisi"] = 76

	//for range
	//map 是无序的,键值对和添加的顺序无关
	for Key, Value := range scoreMap
		fmt.Println(Key, Value)
	

	//只遍历Key
	for Key := range scoreMap
		fmt.Println(Key)
	

	//只遍历Value
	for _, Value := range scoreMap
		fmt.Println(Value)
	


Operation results:
Zhangsan 29
Lisi 76
Zhangsan
Lisi
76
29

删除键值对

使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:
delete(map, key)
map:表示要删除键值对的map
key:表示要删除的键值对的键

package main

import "fmt"

// map(映射)
func main() 
	//删除键值对
	var scoreMap = make(map[string]int, 8)
	scoreMap["Zhangsan"] = 29
	scoreMap["Lisi"] = 76

	delete(scoreMap, "Zhangsan")
	fmt.Println(scoreMap)


Operation results:
map[Lisi:76]

按照某个固定顺序遍历map

会发现map是无序的
Go语言fmt.Sprintf(格式化输出)

package main

import (
	"fmt"
	"math/rand"
	"sort"
	"time"
)

// map(映射)
func main() 
	rand.Seed(time.Now().UnixNano()) //初始化随机数种子

	var scoreMap = make(map[string]int, 100)

	//添加20个键值对
	for i := 0; i < 20; i++ 
		key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串,格式化字符串并打印输出
		value := rand.Intn(100)          //0~99的随机整数
		scoreMap[key] = value
	

	//按照某个固定顺序遍历map
	// for k, v := range scoreMap 
	// 	fmt.Println(k, v)
	// 

	//按照key从小到大的顺序去遍历scoreMap
	//1.先取出所有的key存放到切片中
	keys := make([]string, 0, 100)
	for k := range scoreMap 
		keys = append(keys, k)
	

	//2.对key做排序
	sort.Strings(keys) //keys目前是一个有序的切片

	//3.按照排序后的key对scoreMap排序
	for _, key := range keys 
		fmt.Println(key, scoreMap[key])
	


Operation results:
stu00 82
stu01 8
stu02 85
stu03 4
stu04 16
stu05 17
stu06 68
stu07 91
stu08 48
stu09 42
stu10 35
stu11 88
stu12 4
stu13 6
stu14 1
stu15 75
stu16 47
stu17 40
stu18 61
stu19 52

元素为map类型的切片

package main

import (
	"fmt"
)

// map(映射)
func main() 
	var mapSlide = make([]map[string]int, 8, 8) //这里只对了切片初始化,还不能直接操作,还需要对map初始化
	fmt.Println(mapSlide == nil)                //查看切片是否初始化
	fmt.Println(mapSlide[0] == nil)             //查看map是否初始化
	fmt.Println(mapSlide)

	//切片中的map初始化
	mapSlide[0] = make(map[string]int, 8)
	mapSlide[1] = make(map[string]int, 8)

	//初始化后即可对map进行操作
	mapSlide[0]["鸡汤MAN"] = 100
	mapSlide[1]["尼冲Q币嘛?"] = 12
	fmt.Println(mapSlide)


Operation results:
false
true
[map[] map[] map[] map[] map[] map[] map[] map[]]
[map[鸡汤MAN:100] map[尼冲Q币嘛?:12] map[] map[] map[] map[] map[] map[]]

元素为map类型的切片循环初始化

我发现上面初始化其实是有问题的,要一个个初始化,还有后面的长度容量似乎可以不用

package main

import (
	"fmt"
)

// map(映射)
func main() 
	var mapSlide = make([]map[string]int, 8, 8) //这里只对了切片初始化,还不能直接操作,还需要对map初始化
	fmt.Println(mapSlide == nil)                //查看切片是否初始化
	fmt.Println(mapSlide[0] == nil)             //查看map是否初始化
	fmt.Println(mapSlide)

	//切片中的map初始化
	for index, _ := range mapSlide 
		mapSlide[index] = make(map[string]int)
	

	//初始化后即可对map进行操作
	mapSlide[0]["鸡汤MAN"] = 100
	mapSlide[1]["尼冲Q币嘛?"] = 12
	mapSlide[5]["西巴"] = 23
	fmt.Println(mapSlide)


Operation results:
false
true
[map[] map[] map[] map[] map[] map[] map[] map[]]
[map[鸡汤MAN:100] map[尼冲Q币嘛?:12] map[] map[] map[] map[西巴:23] map[] map[]]

值为切片的map

package main

import "fmt"

// map(映射)
func main() 
	//map值为切片
	var slideMap = make(map[string][]int, 8)
	_, ok := slideMap["中国"]

	if ok 
		fmt.Println(slideMap["中国"])
	 else 
		slideMap["中国"] = make([]int, 8, 8)
		slideMap["中国"][0] = 10
		slideMap["中国"][2] = 49
	
	fmt.Println(slideMap)


Operation results:
map[中国:[10 0 49 0 0 0 0 0]]

map计算字符串每个单词出现的次数

Go strings.Split函数

package main

import (
	"fmt"
	"strings"
)

// map(映射)
func main() 
	//定义字符出
	var TestStr = "How do you do"

	//定义map
	var WordCount = make(map[string]int, 5)

	//字符是用空格来分割的
	Words := strings.Split(TestStr, " ") //返回值是个切片
	fmt.Printf("%T\\n", Words)
	fmt.Println(Words)

	for index, word := range Words 
		fmt.Println("切片下标:", index) //打印切片下标
		// fmt.Println(word)  //打印切片数据(也就是每个单词)

		//map的key就是单词,值就是出现次数,下面的v就是值也就是出现次数,ok代表是否出现过
		v, ok := WordCount[word]
		if ok  //如果出现过,次数加一
			WordCount[word] = v + 1
		 else  //第一次出现
			WordCount[word] = 1
		
	

	for k, v := range WordCount 
		fmt.Println(k, v)
	



Operation results:
[]string
[How do you do]
切片下标: 0
切片下标: 1
切片下标: 2
切片下标: 3
How 1
do 2
you 1

后面我发现,这个ok判断好像有点多余

package main

import (
	"fmt"
	"strings"
)

func main() 
	var TestStr = "How do you do you do"

	var WordCount = make(map[string]int, 5)

	Words := strings.Split(TestStr, " ")

	for _, word := range Words 
		v, _ := WordCount[word]
		WordCount[word] = v + 1
	

	for k, v := range WordCount 
		fmt.Println(k, v)
	



Operation results:
How 1
do 3
you 2

以上是关于go map and slice 2021-10-08的主要内容,如果未能解决你的问题,请参考以下文章

Go map 转 slice

Go map 转 slice

Arrays and Slices in Go

17 Go Slices: usage and internals

Go 语言中的 Array,Slice,Map 和 Set

golang在多个go routine中进行map或者slice操作应该注意的对象。