Go——map

news/2024/6/17 4:49:54 标签: golang, 开发语言, 后端

一.map介绍和使用

        map是一种无序的基于key-value的数据结构,Go语言的map是引用类型,必须初始化才可以使用。

        1. 定义

        Go语言中,map类型语法如下:

map[KeyType]ValueType
  • KeyType表示键类型
  • ValueType表示值类型

        map类型的变量默认初始值为nil,需要使用make函数来分配内存。语法为:

make(map[KeyType]ValueType, [cap])

map[KeyType]ValueType{} //底层也是使用的make

map[KeyType]Value{ //底层也是使用的make
    key:value,
    key:value,
    ...
}

        其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量。 

        可以使用len()内置函数来获取map键值对的个数。

        注意:map保存的键值对中,键不能被修改,只能修改值。

        2.基本使用

package main

import "fmt"

func main() {
	scoreMap := make(map[string]int, 8)

	scoreMap["张三"] = 100
	scoreMap["小明"] = 90
	fmt.Println(scoreMap)
	fmt.Printf("key num is %d\n", len(scoreMap))
	fmt.Println(scoreMap["小明"])
	fmt.Printf("type(scoreMap)=%T\n", scoreMap)
}

map也支持在声明时填充元素: 

package main

import "fmt"

func main() {
	userInfo := map[string]string{
		"username": "zhansan",
		"password": "123456",
	}

	fmt.Println(userInfo)
}

        3. 判断某个键是否存在

        Go语言中有个判断map中的键是否存在的特殊写法:

value, ok := map[key]

        例子:

package main

import "fmt"

func main() {
	userInfo := map[string]string{
		"username": "zhansan",
		"passward": "123456",
	}

	value, ok := userInfo["passward"]
	if ok {
		fmt.Println(value)
	} else {
		fmt.Println("passward is not exit")
	}

	value, ok = userInfo["sex"]
	if ok {
		fmt.Println(value)
	} else {
		fmt.Println("sex is not exit")
	}
}

        4. map的遍历

        遍历key和value:

package main

import "fmt"

func main() {
	scoreMap := make(map[string]int, 8)
	scoreMap["小明"] = 100
	scoreMap["张三"] = 80
	scoreMap["李四"] = 60

	for key, value := range scoreMap {
		fmt.Printf("scoreMap[%s] = %d\n", key, value)
	}
}

         只遍历key:

注意:遍历map时的元素顺序与添加键值对的顺序无关。 

        5. 删除键值对

        使用delete()内置函数从map中删除一组键值对,格式如下:

delete(map, key)

//map:为需要删除键值对的map
//key:表示要删除键值对的键
package main

import "fmt"

func main() {
	scoreMap := make(map[string]int, 8)
	scoreMap["小明"] = 100
	scoreMap["张三"] = 80
	scoreMap["李四"] = 60

	value, ok := scoreMap["李四"]
	if ok {
		fmt.Println(value)
	} else {
		fmt.Println("李四 is not exit")
	}
	//删除键值对
	delete(scoreMap, "李四")

	value, ok = scoreMap["李四"]
	if ok {
		fmt.Println(value)
	} else {
		fmt.Println("李四 is not exit")
	}
}

        6. 按照指定顺序遍历map

        实际时先获取到所有的键,将键设置成指定顺序,再通过键来遍历map。

package main

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

func main() {
	rand.Seed(time.Now().UnixNano()) //初始化随机种子

	scoreMap := make(map[string]int, 200)
	for i := 0; i < 100; i++ {
		key := fmt.Sprintf("stu%02d", i)
		scoreMap[key] = rand.Intn(100) //获取0-100的随机数
		//fmt.Println(key, scoreMap[key])
	}

	keys := make([]string, 0, 200)//保存key
    //按照排序后的key遍历scoreMap
	for key := range scoreMap {
		keys = append(keys, key)
	}

	//fmt.Println(keys)
	sort.Strings(keys) //对keys进行排序
	for _, key := range keys {
		fmt.Printf("scoreMap[%s] = %d\n", key, scoreMap[key])
	}
}

        7. 元素为map类型的切片

package main

import "fmt"

func main() {
	mapSlice := make([]map[string]string, 3, 10) //并没有为map分配地址空间
	for index, val := range mapSlice {
		fmt.Printf("mapSlice[%d] = %v\n", index, val)
	}

	//分配地址空间
	for index, _ := range mapSlice {
		mapSlice[index] = make(map[string]string, 10)
	}
	fmt.Println("---------插入键值对后---------")
	//插入键值对
	mapSlice[0]["name"] = "张三"
	mapSlice[0]["passwd"] = "123123"
	mapSlice[1]["name"] = "李四"
	mapSlice[1]["passwd"] = "321321"

	mm := map[string]string{
		"name":   "小明",
		"passwd": "123465",
	}
	mapSlice = append(mapSlice, mm)
	for index, val := range mapSlice {
		fmt.Printf("mapSlice[%d] = %v\n", index, val)
	}
}

        8. 值为切片类型的map

package main

import "fmt"

func main() {
	sliceMap := make(map[string][]string, 10) //没有为slice分配空间

	sliceMap["中国"] = make([]string, 0, 10)
	sliceMap["中国"] = append(sliceMap["中国"], "北京", "上海", "长沙")

	key := "美国"
	value, ok := sliceMap[key]
	if !ok {
		value = make([]string, 0)
	}

	value = append(value, "芝加哥", "华盛顿")
	sliceMap[key] = value

	for key, val := range sliceMap {
		fmt.Printf("sliceMap[%s] = %v\n", key, val)
	}
}

二.map底层原理

         Go语言的map有两个重要的数据结构 hmap和bmap。

        2.1 map头部数据结构——hmap

        hmap中有几个重要的属性:

  • count:记录了map中实际元素的个数
  • B:控制哈希桶的个数为2^B个
  • buckets:是一个指向长度为2^B大小的类型为bmap的数组
  • oldbuckets:与buckets一样也是指向一个多桶的数组,不同的是oldbuckets指向的是旧桶的地址,当oldbuckets不为空时,表示map正处于扩容阶段。
type hmap struct {
	// map中元素的个数,使用len返回就是该值
	count     int
    // 状态标记
    // 1: 迭代器正在操作buckets
    // 2: 迭代器正在操作oldbuckets 
    // 4: go协程正在像map中写操作
    // 8: 当前的map正在增长,并且增长的大小和原来一样
	flags     uint8
    // buckets桶的个数为2^B
	B         uint8 
    // 溢出桶的个数
	noverflow uint16 
    // key计算hash时的hash种子
	hash0     uint32
    // 指向的是桶的地址
	buckets    unsafe.Pointer
    // 旧桶的地址,当map处于扩容时旧桶才不为nil
	oldbuckets unsafe.Pointer 
    //扩容之后数据迁移的计数器,记录下次迁移的位置,当nevacuate>旧桶元素个数,数据迁移完
	nevacuate  uintptr 
    // 额外的map字段,存储溢出桶信息
	extra *mapextra
}

        创建一个map实际是创建一个指针,指向hmap结构。

        2.2 bmap

        bmap是每一个桶的数据结构,每一个bmap包含8个key和value。

type bmap struct {
    tophash [bucketCnt]uint8        
    // len为8的数组,用来快速定位key是否在这个bmap中
    // 一个桶最多8个槽位,如果key所在的tophash值在tophash中,则代表该key在这个桶中
}

         上面是bmap的静态结构,在编译过程中runtime.bmap会扩展成以下结构:

  • topbits :用来快速定位桶中键值对的位置。
  • keys:键值对的键
  • values:键值对的值
  • overflow:当8个key满的时候,需要新创建一个桶,overflow保存下一个桶的地址。

细节:

        这里将键和键保存到了一起,值和值保存在了一起,为什么不讲键和值保存在一起?

        因为键和值的类型可能不同,结构体内存对齐会浪费空间。

type bmap struct{
    topbits  [8]uint8
    keys     [8]keytype
    values   [8]valuetype
    pad      uintptr        // 内存对齐使用,可能不需要
    overflow uintptr        // 当bucket 的8个key 存满了之后
    // overflow 指向下一个溢出桶 bmap,
    // overflow是uintptr而不是*bmap类型,保证bmap完全不含指针,是为了减少gc,溢出桶存储到extra字段中
}


http://www.niftyadmin.cn/n/5444100.html

相关文章

SinoDB客户端工具dbaccess

类似Oracle的客户端工具sqlplus&#xff0c;Mysql的客户端工具mysql&#xff0c;SinoDB数据库也有自带的命令行客户端工具dbaccess。 dbaccess 识别用户输入&#xff0c;将用户输入的 SQL 语句打包发送给 SinoDB 数据库服务器执行&#xff0c;然后接收服务器的执行结果&#xf…

C语言中关于补码的问题

最近在项目中遇到一个关于补码的问题&#xff0c; K2 2s complement(e) dec(-2) 这是一个二进制数的补码表示法&#xff0c; 这是什么意思呢&#xff0c;就是说 e 表示的是 -2&#xff0c;就是16进制0xe表示10进制的-2&#xff1b; 那么问题来了&#xff0c;0xa表示多少&…

python实现 linux 执行命令./test启动进程,进程运行中,输入参数s, 再输入参数1, 再输入参数exit, 获取进程运行结果重定向写入到文件

要在 Python 中实现执行 ./test 启动进程&#xff0c;并在进程运行中依次输入参数 s、1&#xff0c;最后输入参数 exit&#xff0c;并将进程的输出结果重定向写入到文件&#xff0c;你可以使用 subprocess 模块。以下是一个示例代码&#xff1a; import subprocess# 启动 test…

Elasticsearch8.x版本Java客户端Elasticsearch Java API 如何并发修改

前言 并发控制&#xff0c;一般有两种方案&#xff0c;悲观锁和乐观锁&#xff0c;其中悲观锁是默认每次更新操作肯定会冲突&#xff0c;所以每次操作都要先获取锁&#xff0c;操作完毕再释放锁&#xff0c;适用于写比较多的场景。而乐观锁是默认每次更新操作都不会冲突&#…

五、大模型-Prompt

一、prompt是什么 在大型语言模型集成中&#xff0c;"prompt" 是指您向模型提供的输入文本或指令&#xff0c;以引导模型生成特定类型的响应。这个 prompt 可以是一个问题、一段描述、一个任务说明&#xff0c;甚至是一部分对话历史记录等。通过设计和优化 prompt&a…

【GPT概念04】仅解码器(only decode)模型的解码策略

一、说明 在我之前的博客中&#xff0c;我们研究了关于生成式预训练转换器的整个概述&#xff0c;以及一篇关于生成式预训练转换器&#xff08;GPT&#xff09;的博客——预训练、微调和不同的用例应用。现在让我们看看所有仅解码器模型的解码策略是什么。 二、解码策略 在之前…

软件工程(双语)

教材《软件工程 实践者的研究方法》 双语教学&#xff0c;但目前感觉都是在讲没用的 ”过程决定质量&#xff0c;复用决定效率” 介绍 软工的本质 程序数据结构算法 软件程序文档&#xff08;需求、模型、说明书&#xff09; 软件应用&#xff1a; 系统软件 应用 工程/科学…

5.78 BCC工具之sslsniff.py解读

一,工具简介 sslsniff工具可以用来追踪OpenSSL、GnuTLS和NSS的写入/发送和读取/接收函数。传递给这些函数的数据会以纯文本的形式打印出来。也就是用于捕获和分析 SSL/TLS 加密的网络流量。 二,代码示例 #!/usr/bin/env pythonfrom __future__ import print_function from…