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

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

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"])
	}
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C# 同构字符串

205 同构字符串 给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符&#xff0c;同时不改变字符的顺序。不同字符不能映射到同一个字符…

详细介绍MATLAB中的图论算法

MATLAB是一种功能强大的编程语言和环境,提供了许多用于图论算法的工具和函数。图论是研究图及其属性和关系的数学分支,广泛应用于计算机科学、网络分析、社交网络分析等领域。在MATLAB中,我们可以使用图论算法来解决各种问题,如最短路径问题、最小生成树问题、最大流问题等…

国产MCU-CW32F030开发学习-BH1750模块

国产MCU-CW32F030开发学习-BH1750模块 硬件平台 CW32_48F大学计划板CW32_IOT_EVA物联网开发评估套件BH1750数字型光照强度传感器 BH1750 BH1750是一款数字型光照强度传感器&#xff0c;能够获取周围环境的光照强度。其测量范围在0~65535 lx。lx勒克斯&#xff0c;是光照强…

【电路效应】信号处理和通信系统模型中的模拟电路效应研究(SimulinkMatlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码、Simulink仿真实现 &#x1f4a5;1 概述 在信号处理和通信系统模型中&#xff0c;模拟电路效应研究是指考虑到实际电路的特性对信号进行建模和分析的过程。模拟电路效应…

Kubernetes—集群故障排查

一、用 Kubectl 调试 Kubernetes 节点 准备开始 你必须拥有一个 Kubernetes 的集群&#xff0c;同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 你的 Kubernetes 服务器版本必须不低于版本 1.2. 要获知版…

C#基础--委托

C#基础–委托 C#基础–委托 简单说它就是一个能把方法当参数传递的对象&#xff0c;而且还知道怎么调用这个方法&#xff0c;同时也是粒度更小的“接口”&#xff08;约束了指向方法的签名&#xff09; 一、什么是委托&#xff0c;委托的本质是什么&#xff1f; 跟方法有点类似…

云迁移第二波热潮来袭,你准备好了吗?

最近&#xff0c;云迁移再次被频繁提及&#xff0c;企业对云迁移的需求量有回升趋势&#xff0c;究其根本&#xff0c;主要有以下原因&#xff1a; 企业数字化进程加速&#xff0c;本地上云需求强劲 根据《2021中国企业上云指数洞察报告》&#xff0c;我国实体经济上云渗透率…

深入理解Java虚拟机(二)Java内存区域与内存溢出异常

一、前言 对于Java程序员来说&#xff0c;在虚拟机自动内存管理机制的帮助下&#xff0c;不再需要为每一个new操作去写配对的delete/free代码&#xff0c;不容易出现内存泄漏和内存溢出问题&#xff0c;看起来由虚拟机管理内存一切都很美好。不过&#xff0c;也正是因为Java程序…

kafka接收外部接口的数据,并实现转发

目录 一、什么是kafka 二、kafka接收外部接口数据 三、kafka收到数据后转发 四、kafka总结 一、什么是kafka Kafka是一种分布式流式处理平台&#xff0c;最初由LinkedIn开发。它设计用于高吞吐量、低延迟的数据处理&#xff0c;能够处理大规模的实时数据流。Kafka采用发布…

关系型数据库设计规则

目录 1.1 表、记录、字段 1.2 表的关联关系 1.2.1 一对一关联&#xff08;one-to-one&#xff09; 1.2.2 一对多关系&#xff08;one-to-many&#xff09; 1.2.3 多对多&#xff08;many-to-many&#xff09; 1.2.4 自我引用&#xff08;Self reference&#xff09; 关系…

用Python采用Modbus-Tcp的方式读取485电子水尺数据

README.TXT 2023/6/15 V1.0 实现了单个点位数据通信、数据解析、数据存储 2023/6/17 V2.0 实现了多个点位数据通信、数据解析、数据存储 2023/6/19 V2.1 完善log存储&#xff0c;仅保留近3天的log记录&#xff0c;避免不必要的存储&#xff1b;限制log大小&#xff0c;2MB。架…

基于Redisson的Redis结合布隆过滤器使用

一、场景 缓存穿透问题 一般情况下&#xff0c;先查询Redis缓存&#xff0c;如果Redis中没有&#xff0c;再查询MySQL。当某一时刻访问redis的大量key都在redis中不存在时&#xff0c;所有查询都要访问数据库&#xff0c;造成数据库压力顿时上升&#xff0c;这就是缓存穿透。…

成为一个年薪30W+的DFT工程师是一种什么体验?

一直以来&#xff0c;DFT都是数字IC设计行业中相对神秘的一个岗位。 你说他重要吧&#xff0c;并不是所有芯片设计公司都有这个岗位&#xff0c;你说他不重要吧&#xff0c;但凡芯片产品达到一定规模后&#xff0c;就必须设置DFT部门。 一、什么是DFT&#xff1f; DFT&#x…

【分布式应用】ceph分布式存储

目录 一、存储基础1.1单机存储设备1.2单机存储的问题1.3分布式存储的类型 二、Ceph简介2.1Ceph 优势2.2Ceph 架构2.3Ceph核心组件OSD&#xff08;Object Storage Daemon&#xff0c;守护进程 ceph-osd&#xff09;PG&#xff08;Placement Group 归置组&#xff09;PoolMonitor…

SpringBoot错误: 找不到或无法加载主类

1.一般出现这种情况都是配置文件application.properties出现的问题 2.可以尝试 maven clean install 以及rebuild project 3.删除项目里.idea文件 重新导入至IDEA编辑器 选择Maven项目 配置好maven.xml 后重新导入

解决GitHub下载速度太慢问题的方法汇总(持续更新,建议收藏)

文章目录 前言一、使用 git clone --depth1 来下载二、修改host文件解决三、谷歌浏览器插件加速四、油猴插件和脚本五、gitclone.com六、Github 加速下载链接七、Github 镜像访问八、使用码云下载参考资料&#xff0c;感谢以下文章 前言 Github上下载仓库或者克隆仓库&#xf…

Docker基础——Centos7安装Docker

0.安装Docker Docker 分为 CE 和 EE 两大版本。CE 即社区版&#xff08;免费&#xff0c;支持周期 7 个月&#xff09;&#xff0c;EE 即企业版&#xff0c;强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月。 Docker CE 分为 stable test 和 nightly 三个更新频道…

「深度学习之优化算法」(十三)蝙蝠算法

1. 蝙蝠算法简介 (以下描述,均不是学术用语,仅供大家快乐的阅读)   蝙蝠算法(Bat Algorithm)是受蝙蝠回声定位的特性启发而提出的新兴算法,提出时间是2010年,虽然距今(2020)有近10年,但与其它的经典算法相比仍算一个新算法。算法也已有一定规模的研究和应用,但仍…

数据结构 ~ 栈、队列

栈 一个后进先出的数据结构、JS中没有栈&#xff0c;可以使用 Array 模拟 const stack [] stack.push(1) // 入栈 stack.push(2) // 入栈 const item1 stack.pop() // 出栈 const item2 stack.pop() // 出栈以上代码可以使用 nodeJs 断点调试&#xff08;F5启动&#xff0…

【Envi风暴】Envi5.6安装图文教程(附Envi5.6完整版下载)

本文讲解Envi5.6与应用商店app store的安装与使用。 文章目录 一、ENVI5.6安装过程二、app store的安装三、ENVI5.6下载地址一、ENVI5.6安装过程 从文末网盘下载完整的ENVI5.6安装包,如下所示:双击主程序envi56-win.exe,开始安装。 点击Next。 点击Next。 选择安装路径,可…