技术标签: Golang golang 编程语言 go 后端
哈希表是一种巧妙并且实用的数据结构。它是一个无序的 key/value对 的集合,其中所有的 key 都是不同的,然后通过给定的 key 可以在常数时间复杂度内检索、更新或删除对应的 value。
在 Go 语言中,一个 map 就是一个哈希表的引用,map 类型可以写为 map[K]V,其中 K 和 V 分别对应 key 和 value。map 中所有的 key 都有相同的类型,所有的 value 也有着相同的类型,但是 key 和 value 之间可以是不同的数据类型。其中 K 对应的 key 必须是支持 == 比较运算符的数据类型(切片、函数等不支持),所以 map 可以通过测试 key 是否相等来判断是否已经存在。虽然浮点数类型也是支持相等运算符比较的,但是将浮点数用做 key 类型则是一个坏的想法。对于 V 对应的 value 数据类型则没有任何的限制。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。由于 map 是无序的,我们无法决定它的返回顺序。
可以使用内建函数 make 也可以使用 map 关键字来定义 map:
// 使用 make 函数
m := make(map[keyType]valueType)
// 长度为 0 的 map
m := make(map[keyType]valueType, 0)
// 声明变量,默认 map 是 nil
var m map[keyType]valueType
// 长度为 0 的 map
var m map[keyType]valueType{
}
其中:
在声明的时候不需要知道 map 的长度,因为 map 是可以动态增长的。但是如果我们提前知道 map 需要的长度,最好指定一下。
我们可以用 len(m)
来查看 map 的长度。注意,使用 cap(m)
会报错(cap 支持 数组、指向数组的指针、切片、channel):
invalid argument m (type map[string]int) for cap
如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对。如果向一个 nil 值的 map 存入元素将导致一个 panic 异常:
下面我们用 make 函数创建一个 map:
ages := make(map[string]int)
当然,我们也可以直接创建一个 map 并且指定一些最初的值:
ages := map[string]int{
"Conan": 18,
"Kidd": 23,
}
这种就相当于:
ages := make(map[string]int)
ages["Conan"] = 18
ages["Kidd"] = 23
所以,另一种创建空(不是 nil)的 map 方法是:
ages := map[string]int{
}
map 在定义时,key 是唯一的,不允许重复(value 可以重复)。下面的程序会报错:
ages := map[string]int{
"Conan": 18,
"Conan": 23,
}
但是之后在对 map 赋值时,则会覆盖原来的 value
ages["Conan"] = 18
ages["Conan"] = 23
fmt.Println(ages["Conan"]) // 23
map 类型的零值是 nil,也就是没有引用任何哈希表,其长度也为 0.
var ages map[string]int
fmt.Println(ages == nil) // true
fmt.Println(len(ages)) // 0
增加 map 的值很简单,只需要 m[key] = value
即可,比如:
ages := make(map[string]int)
ages["Conan"] = 18
ages["Kidd"] = 23
使用内置的 delete 函数可以删除元素,参数为 map 和其对应的 key,没有返回值:
delete(ages, "Conan")
注意:即使这些 key 不在 map 中也不会报错。
修改 map 的内容和 增 的写法类似,只不过 key 是已存在的,如果不存在,则为增加,例如:
ages := map[string]int{
"Conan": 18,
"Kidd": 23,
}
ages["Conan"] = 21
map 中的元素通过 key 对应的下标语法访问:
ages["Conan"] = 18
fmt.Println(ages["Conan"]) // 18
要想遍历 map 中全部的键值对的话,可以使用 range 风格的 for 循环实现,和之前的 slice 遍历语法类似。例如:
for key, value := range ages {
fmt.Println(key, value)
}
如果用不到 value,无需使用匿名变量 _
,直接不写即可:
for key := range ages {
fmt.Println(key)
}
如果查找失败也没有关系,程序也不会报错,而是返回 value 类型对应的零值。例如:
ages := map[string]int{
"Conan": 18,
"Kidd": 23,
}
fmt.Println(ages["Lan"]) // 0
通过 key 作为索引下标来访问 map 将产生一个 value。如果 key 在 map 中是存在的,那么将得到与 key 对应的 value;如果 key 不存在,那么将得到 value 对应类型的零值。
但是有时候我们需要知道对应的元素是否真的是在 map 之中。比如,如果元素类型是一个数字,你需要区分一个已经存在的 0,和不存在而返回零值的 0。例如:
ages := map[string]int{
"Conan": 18,
"Kidd": 23,
}
// 如果 key 存在,则 ok = true;不存在,ok = false
if value, ok := ages["Conan"]; ok {
fmt.Println(value)
} else {
fmt.Println("key 不存在")
}
在这种场景下,map 的下标语法将产生两个值;第二个是一个布尔值,用于报告元素是否真的存在。布尔变量一般命名为 ok,特别适合马上用于 if 条件判断部分。
map 的迭代顺序是不确定的。有没有什么办法可以顺序的打印出 map 呢?我们可以借助切片来完成。先将 key(或者 value)添加到一个切片中,再对切片排序,然后使用 for-range 方法打印出所有的 key 和 value。如下所示:
package main
import (
"fmt"
"sort"
)
func main() {
// 创建一个 ages map,并给三个值
ages := make(map[string]int)
ages["Conan"] = 18
ages["Kidd"] = 23
ages["Lan"] = 19
// 创建一个切片用于给 key 进行排序
var names []string
for name := range ages {
names = append(names, name)
}
sort.Strings(names)
// 循环打印出 map 中的值
for _, name := range names {
fmt.Printf("%s\t%d\n", name, ages[name])
}
}
因为我们一开始就知道 names 的最终大小,因此给切片分配一个合适的容量大小将会更有效。下面的代码创建了一个空的切片,但是切片的容量刚好可以放下 map 中全部的 key:
names := make([]string, 0, len(ages))
当然,如果使用结构体切片,这样就会更有效:
type name struct {
key string
value int
}
map 之间不能进行相等比较;唯一的例外是和 nil 进行比较。要判断两个 map 是否包含相同的 key 和 value,我们必须通过一个循环实现:
func equalMap(x, y map[string]int) bool {
// 长度不一样,肯定不相等
if len(x) != len(y) {
return false
}
for k, xv := range x {
if yv, ok := y[k]; !ok || xv != yv {
return false
}
}
return true
}
map 作为函数参数是地址传递(引用传递),作返回值时也一样。
在函数内部对 map 进行操作,会影响主调函数中实参的值。例如:
func foo(m map[string]int) {
m["Conan"] = 22
m["Lan"] = 21
}
func main() {
m := make(map[string]int, 2)
m["Conan"] = 18
fmt.Println(m) // map[Conan:18]
foo(m)
fmt.Println(m) // map[Conan:22 Lan:21]
}
Go 语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。
下面我们来看一下在并发情况下读写 map 时会出现的问题,代码如下:
// 创建一个 map
m := make(map[int]int)
// 开启一个 go 程
go func () {
// 不停地对 map 进行写入
for true {
m[1] = 1
}
}()
// 开启一个 go 程
go func() {
// 不停的对 map 进行读取
for true {
_ = m[1]
}
}()
// 运行 10 秒停止
time.Sleep(time.Second * 10)
运行代码会报错,错误如下:
fatal error: concurrent map read and map write
当两个并发函数不断地对 map 进行读和写时,map 内部会对这种并发操作进行检查并提前发现。
当我们需要并发读写时,一般的做法是加锁,但是这样性能不高。
Go 语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map。
sync.Map 有以下特性:
并发安全的 sync.Map 示例代码如下:
package main
import (
"fmt"
"sync"
)
func main() {
var ages sync.Map
// 将键值对保存到 sync.Map
ages.Store("Conan", 18)
ages.Store("Kidd", 23)
ages.Store("Lan", 18)
// 从 sync.Map 中根据键取值
age, ok := ages.Load("Conan")
fmt.Println(age, ok)
// 根据键删除对应的键值对
ages.Delete("Kidd")
fmt.Println("删除后的 sync.Map: ", ages)
// 遍历所有 sync.Map 中的键值对
ages.Range(func(key, value interface{
}) bool {
fmt.Println(key, value)
return true
})
}
sync.Map 没有提供获取 map 数量的方法,替代方法是在获取 sync.Map 时遍历自行计算数量,sync.Map 为了保证并发安全有一些性能损失,因此在非并发情况下,使用 map 相比使用 sync.Map 会有更好的性能。
所以,我们用 sync.Map 时进行同时读写是没问题的,示例代码如下:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var m sync.Map
// 开启一个 go 程
go func() {
// 不停地对 map 进行写入
for true {
m.Store(1, 1)
}
}()
// 开启一个 go 程
go func() {
// 不停的对 map 进行读取并打印读取结果
for true {
value, _ := m.Load(1)
fmt.Println(value)
}
}()
time.Sleep(time.Second * 10)
}
这时的结果就会一直输出 1。
1、封装 wordCountFunc() 函数。接收一段英文字符串 str。返回一个 map,记录 str 中每个“单词”出现的次数。
示例:
输入:"I love my work and I love my family too"
输出:
family:1
too:1
I:2
love:2
my:2
work:1
and:1
提示:使用 strings.Fields() 函数可提高效率
实现:
package main
import (
"fmt"
"strings"
)
func wordCountFunc(str string) map[string]int {
// 使用 strings.Fields 进行拆分, 自动按照空格对字符串进行拆分成切片
wordSlice := strings.Fields(str)
// 创建一个用于存储 word 次数的 map
m := make(map[string]int)
// 遍历拆分后的字符串切片
for _, value := range wordSlice {
if _, ok := m[value]; !ok {
// key 不存在
m[value] = 1
} else {
// key 值已存在
m[value]++
}
}
return m
}
func main() {
str := "I love my work and I love my family too"
res := wordCountFunc(str)
// 遍历 map, 展示每个 word 出现的次数
for key, value := range res {
fmt.Println(key, ": ", value)
}
}
如需更深入的了解 map 的原理,推荐阅读这篇文章:深度解密Go语言之map
欢迎访问我的个人网站:
李培冠博客:lpgit.com
黑马程序员-String---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------描述: java中的字符串类型描述类,常见的数据类型,很多种语言都有对字符串类型的描述和功能的定义。特点:一旦被初始化,就固定不变。对字符串常见的操作如下: 1.获取
基于python的豆瓣电影评论爬取声明:环境 百度 AI Studio感谢百度开放大脑的教程支持! pip install bs4 知名爬虫框架bs4! pip install openpyxl 写excel用 有一定的兼容性问题,python3.5以上可以凑活import sysimport timefrom bs4 import BeautifulSoupimport reimport urllibimport openpyxl #加载必要的库def askURL(url):
2013年7月,Adobe公司推出新版本photoshop——Photoshop CC(Creative Cloud)。在Photoshop CS6功能的基础上,Photoshop CC新增相机防抖动功能、CameraRAW功能改进、图像提升采样、属性面板改进、Behance集成等功能,以及Creative Cloud,即云功能。继2012年Adobe推出Photos...
什么是XSS攻击XSS(Cross Site Scripting)中文名称是:跨站脚本攻击。XSS重点不在跨站,而在于执行的脚本。XSS的原理是指攻击者利用网站的安全漏洞,向web页面中插入一段恶意的script代码,当用户浏览网页时,执行脚本,攻击者就可以非法获取用户的敏感信息(如cookie、账号密码等),从而达到攻击的目的。XSS的类型从攻击代码的工作方式可以分为三个类型:1、反射型XSS。...
Class文件是一组以8位字节为基础单位的二进制流,包含多个数据项目(数据项目的顺序,占用的字节数均由规范定义),各个数据项目严格按照顺序紧凑的排列在Class文件中,不包含任何分隔符,使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙。当遇到需要占用超过8位字节以上空间的数据项目时,会按照高位在前的方式分割为多个8位字节进行存储数据项目分...
文章目录Java三层架构1.什么是系统架构2.什么是三层架构3.三层结构的优点4.三层结构的缺点5.与MVC的区别Java三层架构1.什么是系统架构 所谓系统架构是指,整合应用系统程序大的结构,经常提到的系统结构有两种 : 三层架构 与 MVC,这两种结构既有区别也有联系,但是这两种结构的使用,均是为了降低系统之间模块间的耦合度2.什么是三层架构三层架构(3-tier architecture) 通常意义上的三层架构就是将整个业务应用划分为:界面层(User Interface layer)、业
截屏程序的源码网上到处都有,但是基本都不支持多显示器。这让我一度以为支持多显示器是一件很困难的事情。(demo http://download.csdn.net/detail/problc/3841959 包含多显示器支持,窗口高亮,十字放大等)其实多显示的截屏跟主显示器的截屏区别并不大,甚至根本不需要EnumDisplayMonitors之类的调用。只是因为网上有了源
在tomcat/conf/server.xml里的<host>标签下加上<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt"pattern="common" reso
今天部署了一个Authorization项目,由于改了auth服务器客户端id和密码,而前端请求header没有修改,登录时一直弹框要求输入用户名和密码,输入后却无效,只好改前端代码。改完只好就可以了。以下是参考文章。POST /goform/ser2netconfigAT HTTP/1.1Host: 192.168.16.254Connection: keep-aliveAuthorizatio...
作者:洪斌爱可生南区负责人兼技术服务总监,MySQL ACE,擅长数据库架构规划、故障诊断、性能优化分析,实践经验丰富,帮助各行业客户解决 MySQL 技术问题,为金融、运营商、互联网等行业客户提供 MySQL 整体解决方案。本文来源:转载自公众号-玩转MySQL*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。MGR 具备了 RPO=0 的高可用容灾能力,但并不适合...
redis.windows.conf配置详解转自:https://www.cnblogs.com/kreo/p/4423362.html# redis 配置文件示例 # 当你需要为某个配置项指定内存大小的时候,必须要带上单位,# 通常的格式就是 1k 5gb 4m 等酱紫:## 1k => 1000 bytes# 1kb => 1024 bytes...
CMake 生成 NMake 的配置文件:cmake -G "NMake Makefiles" ..进入 64 位命令行,运行 NMake 即构建 Windows 64 位程序。参考:如何编写makefile编译win_64程序? http://www.tc5u.com/cpp/2405952.htm安装完visual stidio 2015后的一些工具如何使用 https://social.m