Go语言之重要数组类型map(映射)类型

news2024/11/27 10:20:09

通过切片,我们可以动态灵活存储管理学生姓名、年龄等信息,比如

names := []string{"张三","李四","王五"}
ages := []int{23,24,25}
fmt.Println(names)
fmt.Println(ages)

但是如果我想获取张三的年龄,这是一个再简单不过的需求,但是却非常麻烦,我们需要先获取张三的切片索引,再去ages切片中对应索引取出,前提还得是姓名年龄按索引对应存储。

所以在编程语言中大都会存在一种映射(key-value)类型,在JS中叫json对象类型,在python中叫字典(dict)类型,而在Go语言中则叫Map类型。

Map是一种通过key来获取value的一个数据结构,其底层存储方式为数组,在存储时key不能重复,当key重复时,value进行覆盖,我们通过key进行hash运算(可以简单理解为把key转化为一个整形数字)然后对数组的长度取余,得到key存储在数组的哪个下标位置,最后将key和value组装为一个结构体,放入数组下标处
slice查询是遍历方式,时间复杂度是O(n), map查询是hash映射 ;当数据量小的时候切片查询比map快,但是数据量大的时候map的优势就体现出来了

map的声明和初始化

不同于切片根据索引查找值,map类型是根据key查找值。

map 是引用类型,声明语法:

var map_name map[key_type]value_type

map_name 为 map 的变量名。
key_type为键类型。
value_type是键对应的值类型。

var info map[string]string
fmt.Println(info)   // map[]

(1) 先声明再赋值

// var info map[string]string   // 没有默认空间
info := make(map[string]string)
info["name"] = "yuan"
info["age"] = "23"
fmt.Println(info)  // map[age:23 name:yuan]  

map的键是无序的
map的键不能重复

(2) 直接声明赋值

info := map[string]string{"name": "yuan", "age": "23","gender":"male"}
fmt.Println(info) // map[age:18 gender:male name:yuan]

map的增删改查

(1) 查

通过key访问值

info := map[string]string{"name": "yuan", "age": "18","gender":"male"}
val:= info["name"]

val,is_exist:= info["name"]  // 判断某个键是否存在map数据中
if is_exist{
    fmt.Println(val)
    fmt.Println(is_exist)
}else {
    fmt.Println("键不存在!")
}

循环访问所有键值

for k,v :=range info{
    fmt.Println(k,v)  
}
noSortMap := map[int]int{
        1: 1,
        2: 2,
        3: 3,
        4: 4,
        5: 5,
        6: 6,
    }

    for k, v := range noSortMap {    // for range顺序随机
        fmt.Println(k, v)
    }

(2)添加和更新

info := map[string]string{"name": "yuan", "age": "18","gender":"male"}
info["height"] = "180cm"  // 键不存在,则是添加键值对
info["age"] = "22"  // 键存在,则是更新键的值
fmt.Println(info)  // map[age:22 gender:male height:180cm name:yuan]

(3)删除键值对

一个内置函数 delete(),用于删除容器内的元素

info := map[string]string{"name": "yuan", "age": "18","gender":"male"}
delete(info,"gender")
fmt.Println(info)

如果想清空一个map,最优方式即创建一个新的map!

map 容量

和数组不同,map 可以根据新增的 key-value 动态的伸缩,因此它不存在固定长度或者最大限制,但是也可以选择标明 map 的初始容量 capacity,格式如下:

make(map[keytype]valuetype, cap)
m := make(map[string]float, 100)

当 map 增长到容量上限的时候,如果再增加新的 key-value,map 的大小会自动加 1,所以出于性能的考虑,对于大的 map 或者会快速扩张的 map,即使只是大概知道容量,也最好先标明。

map的灵活运用

// 案例1
data := map[string][]string{"hebei": []string{"廊坊市", "石家庄", "邯郸"}, "beijing": []string{"朝阳", "丰台", "海淀"}}
// 打印河北的第二个城市
// 循环打印每个省份的名字和城市数量
// 添加一个新的省份和城市的key-value
// 删除北京的key-value

// 案例2
info := map[int]map[string]string{1001: {"name": "yuan", "age": "23"}, 1002: {"name": "alvin", "age": "33"}}
// 打印学号为1002的学生的年龄
// 循环打印每个学员的学号,姓名,年龄
// 添加一个新的学员
// 删除1001的学生

// 案例3
stus := []map[string]string{{"name": "yuan", "age": "23"}, {"name": "rain", "age": "22"}, {"age": "32", "name": "eric"}}
// 打印第二个学生的姓名
// 循环打印每一个学生的姓名和年龄
// 添加一个新的学生map
// 删除一个学生map
// 将姓名为rain的学生的年龄自加一岁
// 根据age的大小重新排序
stus := []map[string]int{map[string]int{"age": 23}, map[string]int{"age": 33}, map[string]int{"age": 18}}
fmt.Println(stus)

map的底层原理

(1)摘要算法

“消息摘要”(Message Digest)是一种能产生特殊输出格式的算法,这种加密算法的特点是无论用户输入什么长度的原始数据,经过计算后输出的密文都是固定长度的,这种算法的原理是根据一定的运算规则对原数据进行某种形式的提取,这种提取就是“摘要”,被“摘要”的数据内容与原数据有密切联系,只要原数据稍有改变,输出的“摘要”便完全不同,因此基于这种原理的算法便能对数据完整性提供较为健全的保障。但是,由于输出的密文是提取原数据经过处理的定长值,所以它已经不能还原为原数据,即消息摘要算法是**“不可逆”**的,理论上无法通过反向运算取得原数据内容,因此它通常只能被用来做数据完整性验证,而不能作为原数据内容的加密方案使用,否则谁也无法还原。

package main

import (
    "crypto/md5"
    "crypto/sha1"
    "crypto/sha256"
    "fmt"
    "os"
)

func main() {
    //输⼊字符串测试开始.
    input := "k4"
    //MD5算法.
    hash := md5.New()
    _, err := hash.Write([]byte(input))
    if err != nil {
        fmt.Println(err)
        os.Exit(-1)
    }
    result := hash.Sum(nil)
    //或者result := hash.Sum([]byte(""))
    fmt.Printf("md5 hash算法长度为%d,结果:%x\n", len(result), result)
    //SHA1算法.
    hash = sha1.New()
    _, err = hash.Write([]byte(input))
    if err != nil {
        fmt.Println(err)
        os.Exit(-1)
    }
    result = hash.Sum(nil)
    //或者result = hash.Sum([]byte(""))
    fmt.Printf("sha1 hash算法长度为%d,结果:%x\n", len(result), result)
    //SHA256算法.
    hash = sha256.New()
    _, err = hash.Write([]byte(input))
    if err != nil {
        fmt.Println(err)
        os.Exit(-1)
    }
    result = hash.Sum(nil)
    //或者result = hash.Sum([]byte(""))
    fmt.Printf("sha256 hash算法长度为%d,结果:%x\n", len(result), result)

}

(2)map底层存储

哈希表属于编程中比较常见的数据结构之一,基本上所有的语言都会实现数组和哈希表这两种结构。

slice查询是遍历⽅式,时间复杂度是O(n)
map查询是hash映射,时间复杂度是O(1)

在go的map实现中,它的底层结构体是hmap,hmap⾥维护着若⼲个bucket数组 (即桶数组)。

Bucket数组中每个元素都是bmap结构,也即每个bucket(桶)都是bmap结构,【ps:后⽂为了语义⼀致,和⽅便理解,就不再提bmap 了,统⼀叫作桶】 每个桶中保存了8个kv对,如果8个满了,⼜来了⼀个key落在了这个桶⾥,会使⽤overflow连接下⼀个桶(溢出桶)。

map 的源码位于 src/runtime/map.go 文件中,结构如下:

type hmap struct { 
                  count     int // 当前 map 中元素数量 
                  flags     uint8 
                  B         uint8  // 当前 buckets 数量,2^B 等于 buckets 个数 
                  noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details 
                  hash0     uint32 // 哈希种子 
  
                  buckets    unsafe.Pointer // buckets 数组指针 
                  oldbuckets unsafe.Pointer // 扩容时保存之前 buckets 数据。 
                  nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated) 

                  extra *mapextra // optional fields 
} 

在这里插入图片描述

// 每一个 bucket 的结构,即 hmap 中 buckets 指向的数据。 
type bmap struct { 
    tophash [bucketCnt]uint8 
} 
// 编译期间重构此结构 
type bmap struct { 
    topbits  [8]uint8 
    keys     [8]keytype 
    values   [8]valuetype 
    pad      uintptr 
    overflow uintptr 
} 

在这里插入图片描述
在这里插入图片描述

(1)插入key-value

map的赋值流程可总结位如下几步:
map的赋值流程可总结位如下⼏步:

<1> 通过key的hash值后“B”位确定是哪⼀个桶,图中⽰例为5号桶。
<2> 遍历当前桶,通过key的tophash和hash值,防⽌key重复。如果key已存在则直接更新值。如果没找到将key,将key插入到第⼀个可以插⼊的位置,即空位置处存储数据。
<3> 如果当前桶元素已满,会通过overflow链接创建⼀个新的桶,来存储数据。

(2)查询key-value

参考上图,k4的get流程可以归纳为如下⼏步:

<1> 计算k4的hash值。[由于当前主流机都是64位操作系统,所以计算结果有64个⽐特位]
<2> 通过最后的“B”位来确定在哪号桶,此时B为4,所以取k4对应哈希值的后4位,也就是0101,0101⽤⼗进制表⽰为5,所以在5号桶)
<3> 根据k4对应的hash值前8位快速确定是在这个桶的哪个位置(额外说明⼀下,在bmap中存放了每个key对应的tophash,是key的哈希值前8位),⼀旦发现前8位⼀致,则会执⾏下⼀步
<4> 对⽐key完整的hash是否匹配,如果匹配则获取对应value
<5> 如果都没有找到,就去连接的下⼀个溢出桶中找
有很多同学会问这⾥为什么要多维护⼀个tophash,即hash前8位?
这是因为tophash可以快速确定key是否正确,也可以把它理解成⼀种缓存措施,如果前8位都不对了,后⾯就没有必要⽐较了。

练习

func1个_map函数1() {
	var stu = map[string]string{"name": "yuan", "age": "23"}
	fmt.Println(stu["name"])
	fmt.Println(len(stu))
	//新增一个key-value
	stu["gender"] = "male"
	stu["height"] = "190cm"
	//删除一个key-value
	delete(stu, "height")
	fmt.Println(stu)
}
func2个_map函数make1() {
	s := make([]int, 3)
	s[0] = 100

	//基于make函数声明初始化
	var stu02 map[string]string //这样写会报错的,原因:map是引用类型,还没有开辟空间
	stu02["name"] = "rain"
	fmt.Println(stu02)
}
func3个_map函数make2() {
	var stu = make(map[string]string)
	stu["name"] = "rain"
	fmt.Println(stu)
	//new和make的区别?
	//new函数直接返回一个指针地址,make函数返回的是引用类型本身,切片返回切片,map返回map
}
func4个_map函数make3() {
	var stu = make(map[string]interface{}) //表示任意类型
	stu["name"] = "rain"
	stu["age"] = 30
	fmt.Println(stu)
}
func5_遍历map1() {
	var s = []int{10, 11, 12, 13, 14, 15}
	for i, v := range s {
		fmt.Println(i, v)
	}
}
func6_遍历map2() {
	//map打印是无序的
	var stu = make(map[string]interface{}) //表示任意类型
	stu["name"] = "rain"
	stu["age"] = 30
	stu["name"] = "jack"
	stu["age"] = 23
	for i, v := range stu {
		fmt.Println(i, v)
	}
}
func7_使用map函数1() {
	//map和切片
	//var map_name map[key_type]value_type
	var data = make(map[string][]string)
	data["北京"] = []string{"朝阳", "海淀"}
	data["山东"] = []string{"济南", "青岛"}
	data["河北"] = []string{"石家庄", "保定", "衡水"}

	// 查询河北的第二个城市
	//fmt.Println(data["hebei"][1])

	// 添加一个新的省份和城市的key-value
	data["海南"] = []string{"海南岛"}

	// 删除北京的key-value
	delete(data, "北京")

	// 遍历每一个省份以及对应的城市名称,嵌套循环
	for proStr, citysSlice := range data {
		fmt.Println(proStr, len(citysSlice)) //打印城市的数量
		for i, v := range citysSlice {
			fmt.Printf("%d.%s", i, v)
		}
		fmt.Println()
	}
}
func8_案例2() {
	//map嵌套map
	info := map[int]map[string]string{1001: {"name": "yuan", "age": "23"},
		1002: {"name": "alvin", "age": "33"}}
	// 打印学号为1002的学生的年龄
	fmt.Println(info[1002]["age"])

	// 添加一个新的学员
	info[1003] = map[string]string{"name": "jack", "age": "25"}
	// 删除1001的学生
	delete(info, 1001)

	// 循环打印每个学员的学号,姓名,年龄
	for no, stu := range info {
		fmt.Printf("学号:%d,姓名:%s,年龄:%s\n", no, stu["name"], stu["age"])
	}
}
func9个_map嵌套map() {
	stu01 := map[string]string{"name": "rain", "age": "12"}
	stu02 := map[string]string{"name": "jack", "age": "45"}
	var stu = make(map[int]map[string]string)
	stu[1001] = stu01
	stu[1002] = stu02
	fmt.Println(stu, len(stu))
}
func10_案例3切片嵌套map1() {
	stu01 := map[string]string{"name": "yuan1", "age": "231"}
	stu02 := map[string]string{"name": "yuan2", "age": "232"}
	stu03 := map[string]string{"name": "yuan3", "age": "233"}
	//stu := make([]map[string]string, 3)
	stu := []map[string]string{stu01, stu02, stu03}
	fmt.Println(stu)
}
func11_案例3切片嵌套map() {
	// 案例3,切片嵌套map
	stus := []map[string]string{{"name": "yuan", "age": "23"}, {"name": "rain", "age": "22"}, {"age": "32", "name": "eric"}}
	fmt.Println(stus)

	// 打印第二个学生的姓名
	//fmt.Println(stus[1]["name"])
	// 添加一个新的学生map
	stus = append(stus, map[string]string{"name": "jack", "age": "28"})

	// 删除一个学生map
	// 基于索引删除用append
	//stus = append(stus[:1], stus[3:]...)

	// 删除一个学生jack的map
	var delIndex = 0
	for index, stuMap := range stus {
		if stuMap["name"] == "eric" {
			delIndex = index
		}
	}
	stus = append(stus[:delIndex], stus[1+delIndex:]...)

	// 将姓名为rain的学生的年龄自加一岁
	for _, stuMap := range stus {
		if stuMap["name"] == "rain" {
			parseInt, _ := strconv.Atoi(stuMap["age"])
			parseInt++
			stuMap["age"] = strconv.Itoa(parseInt)
		}
	}

	// 循环打印每一个学生的姓名和年龄
	for _, item := range stus {
		//-8左对齐,打印对齐
		fmt.Printf("姓名:%-8s,年龄:%-8s\n", item["name"], item["age"])
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/759835.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

概率论的学习和整理17:EXCEL的各种期望,方差的公式

目录 1 总结 1.1 本文目标总结方法 1.2 总结一些中间关键函数 2 均值和期望 2.1 求均值的公式 2.2 求随机变量期望的公式 2.3 求随机变量期望的朴素公式 3 方差 3.1 确定数的方差 3.2 统计数的方差公式 3.3 随机变量的方差公式 3.4 EXCEL提供的直接计算方差的公式 …

Ant Design Vue组件,a-select标签

a-select标签是组件里的选择框&#xff0c;具体使用可以查看官网&#xff0c;这里记录一下在使用中遇到的问题。 最近在做项目的时候有一个需求在 a-modal 标签中加入 a-select 标签&#xff0c;a-modal 是模态对话框&#xff0c;意思就是在模态对话框里面添加选择框&#xff0…

Cglib 动态代理实现

每天看看新东西,心情也要好上许多 问题 cglib是如何实现动态代理的cglib如何支持类的代理cglib和jdk的动态代理有什么区别 使用方式 cglib不属于jdk的一部分,因此要使用需要先引入相应的包,maven依赖如下 <dependency><groupId>cglib</groupId><artif…

TortoiseGit 入门指南09:合并

前面章节讲了 分支&#xff0c;提到一种常用的工作流&#xff1a;将默认分支&#xff08;master&#xff09;设置为主分支&#xff0c;保存最新的、随时可以发布的版本&#xff0c;所有的新特性、BUG都在另一个称为特性分支上增加或修改&#xff0c;然后在一个合适点&#xff0…

Ubuntu下搭建Redis主从集群

目录 准备实例和配置 开启主从关系 测试配置 搭建的主从集群结构&#xff0c;只有主服务器与客户端进行写操作&#xff0c;通过主从同步数据&#xff0c;从服务器向客户端提供读操作 共包含三个节点&#xff0c;一个主节点&#xff0c;两个从节点。 这里我们会在同一台虚拟机…

【C++】多线程编程二(std::thread详解)

目录 std::thread详解 &#xff08;1&#xff09;启动线程 ①无参无返回的函数作为入参 ②函数对象&#xff08;仿函数&#xff09;作为入参 &#xff08;2&#xff09;不等待线程detch() &#xff08;3&#xff09;等待线程完成join() &#xff08;4&#xff09;向线程…

CANoe如何配置Master/Slave模式

系列文章目录 文章目录 系列文章目录前言一、CANoe配置端口二、CANoe配置Master模式三、CANoe配置Slave模式前言 随着智能电动汽车的行业的发展,车载以太网的应用越来越广泛,最近很多朋友在问CANoe Master/Slave模式如何设置,车载以太网物理层也有一项是测试Master/Slave模式…

springcloud整合nacos实现注册发现中心

文章目录 微服务为什么需要服务注册发现中心怎么使用注册发现中心1.本示例环境2.nacos 安装3.pom.xml4.application.yml5.NacosDiscoveryDemoController6.ServerConfig7.NacosNacosDiscoveryServiceImpl8.启动用http工具测试结果 如果需要完整源码请关注公众号"架构殿堂&q…

2023.7.16-偶数(奇数)的枚举

功能&#xff1a;输入一个整数&#xff0c;结果打印出所有不大于这个整数的偶数。 程序&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() {int a, b;printf("请输入一个整数&#xff1a;");scanf("%d",&a);print…

Android之Intent

意图介绍 一个意图(Intent)对象包含了目标组件、动作、数据、类别、附加数据、标志六个部分。 目标组件 目标组件可以帮助应用发送显式意图调用请求。在创建Intent时&#xff0c;可以通过setComponent方法来设置一个组件&#xff0c;如&#xff1a; //设置组件 intent.setC…

19.基于XML的自动装配

基于XML的自动装配 自动装配&#xff1a; 根据指定的策略&#xff0c;在IOC容器中匹配某一个bean&#xff0c;自动为指定的bean中所依赖的类类型或接口类型属性赋值 使用bean标签的autowire属性设置自动装配效果(默认为no和defalse不进行装配——bean中的属性不会自动匹配某个b…

Nodejs的字节操作(Buffer)

Hi I’m Shendi Nodejs的字节操作&#xff08;Buffer&#xff09; 字节操作是一个编程语言中必不可少的&#xff0c;而在NodeJs中也可以很方便的进行字节操作。 Buffer类 在 js 中没有二进制数据类型&#xff0c;但在一些情况下必须使用到二进制数据类型&#xff0c;比如网络通…

ModaHub魔搭社区:AI原生云向量数据库Zilliz Cloud与 OpenAI 集成搭建相似性搜索系统

目录 准备工作 检索图书 本文将讨论如何使用 OpenAI 的 Embedding API 与 Zilliz Cloud 搭建相似性搜索系统。 在本篇中你将看到如何使用 OpenAI 的 Embedding API 和 Zilliz Cloud 完成图书检索。当前,很多的图书检索方案,包括公共图书馆里使用的那些方案,都是使用关键词…

简单认识MySQL数据库事务

文章目录 一、MySQL事务的概念1、简介2、事务的ACID特点1.原子性&#xff08;Atomicity&#xff09;2.一致性&#xff08;Consistency&#xff09;3.隔离性&#xff08;lsolation&#xff09;4.持久性&#xff08;Durability) 3、并发访问表的一致性问题和事务的隔离级别1.并发…

C# Modbus通信从入门到精通(6)——Modbus RTU(0x04功能码)

1、04(0x04)读输入寄存器 使用该功能码能从远程地址中读取1到125个输入寄存器的值,每个输入寄存器都占两个字节,读取的输入寄存器数量由主站读取时指定。 2、发送报文格式 更详细的格式如下: 从站地址+功能码+起始地址高位+起始地址低位+寄存器数量高位+寄存器数量低位+…

备战求战 | 笔试强训6

目录 一、选择题 二、编程题 三、选择题题解 四、编程题题解 一、选择题 1、十进制变量i的值为100&#xff0c;那么八进制的变量i的值为&#xff08;&#xff09; A. 146 B. 148 C. 144 D. 142 2、执行下面语句后的输出为 int I1; if(I<0)printf("****\n") …

全志F1C200S嵌入式驱动开发(串口驱动)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 对于uboot、kernel和rootfs来说,他们的串口输出一般都是uart0。一般这么做,是没有问题的。只不过我们自己买的这块f1c200s电路板,设计者把uart转ttl的接口,改接到了uart1上面。…

自定义注解+AOP完成公共字段填充

在开发中&#xff0c;我们的实体类经常会有几个公共的字段&#xff0c;如下图的创建时间&#xff0c;修改时间就为各个类共有的字段&#xff1a; 目前项目中处理这些字段都是在每一个业务方法中进行赋值操作,如下: 如果都按照上述的操作方式来处理这些公共字段, 需要在每一个业…

中级课程——信息收集(完结)

文章目录 概要whois备案号警告CDN历史解析查询子域名查询后台目录指纹C段&#xff0c;旁站真实IP与CDN端口证书其他资产社工漏洞情报实操案例 概要 whois 备案号 警告 CDN历史解析查询 子域名查询 工具推荐 或者找在线工具 后台目录 指纹 C段&#xff0c;旁站 真实IP与CDN 端…

无符号数和数据类型转换

无符号数 字符类型的无符号值&#xff1a; 所有的数据底层都是采用二进制来保存&#xff0c;而第一位用于保存符号位&#xff0c;当不考虑符号位时&#xff0c;所有的数都按照数值进行保存 #include <stdio.h>int main() {unsigned char a -65;printf("%u"…