Golang的map类型
删除Map的key 内存是否会自动释放
- 如果删除的元素是值类型,如int,float,bool,string以及数组和struct,map的内存不会自动释放
- 如果删除的元素是引用类型,如指针,slice,map,chan等,map的内存会自动释放,但释放的内存是子元素应用类型的内存占用
- 将map设置为nil后,内存被回收
值类型Map
-
package main
-
-
import (
-
"log"
-
"runtime"
-
)
-
-
var lastTotalFreed uint64
-
var intMap map[int]int
-
var cnt = 8192
-
-
func main() {
-
printMemStats()
-
-
initMap()
-
runtime.GC()
-
printMemStats()
-
-
log.Println(len(intMap))
-
for i := 0; i < cnt; i {
-
delete(intMap, i)
-
}
-
log.Println(len(intMap))
-
-
runtime.GC()
-
printMemStats()
-
-
intMap = nil
-
runtime.GC()
-
printMemStats()
-
}
-
-
func initMap() {
-
intMap = make(map[int]int, cnt)
-
-
for i := 0; i < cnt; i {
-
intMap[i] = i
-
}
-
}
-
-
func printMemStats() {
-
var m runtime.MemStats
-
runtime.ReadMemStats(&m)
-
log.Printf("Alloc = %v TotalAlloc = %v Just Freed = %v Sys = %v NumGC = %v\n",
-
m.Alloc/1024, m.TotalAlloc/1024, ((m.TotalAlloc-m.Alloc)-lastTotalFreed)/1024, m.Sys/1024, m.NumGC)
-
-
lastTotalFreed = m.TotalAlloc - m.Alloc
-
}
看结果前,解释下几个字段:
- Alloc:当前堆上对象占用的内存大小。
- TotalAlloc:堆上总共分配出的内存大小。
- Sys:程序从操作系统总共申请的内存大小。
- NumGC:垃圾回收运行的次数。
结果如下:
-
2019/12/19 11:48:03 Alloc = 89 TotalAlloc = 89 Just Freed = 0 Sys = 1700 NumGC = 0
-
2019/12/19 11:48:03 Alloc = 403 TotalAlloc = 437 Just Freed = 33 Sys = 3234 NumGC = 1
-
2019/12/19 11:48:03 8192
-
2019/12/19 11:48:03 0
-
2019/12/19 11:48:03 Alloc = 404 TotalAlloc = 438 Just Freed = 1 Sys = 3234 NumGC = 2
-
2019/12/19 11:48:03 Alloc = 91 TotalAlloc = 439 Just Freed = 313 Sys = 3234 NumGC = 3
Alloc代表了map占用的内存大小,这个结果表明,执行完delete后,map占用的内存并没有变小,Alloc依然是403,代表map的key和value占用的空间仍在map里.执行完map设置为nil,Alloc变为91,与刚创建的map大小基本是约等于
引用类型Map
-
package main
-
-
import (
-
"log"
-
"runtime"
-
)
-
-
var intMapMap map[int]map[int]int
-
-
var cnt = 1024
-
var lastTotalFreed uint64 // size of last memory has been freed
-
-
func main() {
-
// 1
-
printMemStats()
-
-
// 2
-
initMapMap()
-
runtime.GC()
-
printMemStats()
-
-
// 3
-
fillMapMap()
-
runtime.GC()
-
printMemStats()
-
-
// 4
-
log.Println(len(intMapMap))
-
for i := 0; i < cnt; i {
-
delete(intMapMap, i)
-
}
-
log.Println(len(intMapMap))
-
runtime.GC()
-
printMemStats()
-
-
// 5
-
intMapMap = nil
-
runtime.GC()
-
printMemStats()
-
}
-
-
func initMapMap() {
-
intMapMap = make(map[int]map[int]int, cnt)
-
for i := 0; i < cnt; i {
-
intMapMap[i] = make(map[int]int, cnt)
-
}
-
}
-
-
func fillMapMap() {
-
for i := 0; i < cnt; i {
-
for j := 0; j < cnt; j {
-
intMapMap[i][j] = j
-
}
-
}
-
}
-
-
func printMemStats() {
-
var m runtime.MemStats
-
runtime.ReadMemStats(&m)
-
log.Printf("Alloc = %v TotalAlloc = %v Just Freed = %v Sys = %v NumGC = %v\n",
-
m.Alloc/1024, m.TotalAlloc/1024, ((m.TotalAlloc-m.Alloc)-lastTotalFreed)/1024, m.Sys/1024, m.NumGC)
-
-
lastTotalFreed = m.TotalAlloc - m.Alloc
-
}
结果如下:
-
2019/12/19 11:49:59 Alloc = 89 TotalAlloc = 89 Just Freed = 0 Sys = 1700 NumGC = 0
-
2019/12/19 11:50:00 Alloc = 41171 TotalAlloc = 41204 Just Freed = 32 Sys = 46026 NumGC = 5
-
2019/12/19 11:50:00 Alloc = 41259 TotalAlloc = 41342 Just Freed = 49 Sys = 46026 NumGC = 6
-
2019/12/19 11:50:00 1024
-
2019/12/19 11:50:00 0
-
2019/12/19 11:50:00 Alloc = 132 TotalAlloc = 41343 Just Freed = 41129 Sys = 46026 NumGC = 7
-
2019/12/19 11:50:00 Alloc = 91 TotalAlloc = 41344 Just Freed = 41 Sys = 46026 NumGC = 8
这个结果表明,在执行完delete后,顶层map占用的内存从41259降到了132,子层map占用的空间肯定是被GC回收了,不然占用内存不会下降这么显著。但依然比初始化的顶层map占用的内存89多出不少,那是因为delete操作,顶层map的key占用的空间依然在map里,当把顶层map设置为nil时,大小变为91吗,顶层map占用那些空间被释放了.
不能作为Map key 的类型
- slices
- maps
- functions
Map实现两种 get 操作
Go 语言中读取 map 有两种语法:带 comma 和 不带 comma。当要查询的 key 不在 map 里,带 comma 的用法会返回一个 bool 型变量提示 key 是否在 map 中;而不带 comma 的语句则会返回一个 key 对应 value 类型的零值。如果 value 是 int 型就会返回 0,如果 value 是 string 类型,就会返回空字符串
-
func main() {
-
ageMap := make(map[string]int)
-
ageMap["qcrao"] = 18
-
-
// 不带 comma 用法
-
age1 := ageMap["stefno"]
-
fmt.Println(age1)
-
-
// 带 comma 用法
-
age2, ok := ageMap["stefno"]
-
fmt.Println(age2, ok)
-
}
Map为什么无序
- 底层数据 哈希表,本就是无序的,
- 正常写入(非哈希冲突写入):是hash到某一个bucket上,而不是按buckets顺序写入
- 哈希冲突写入:如果存在hash冲突,会写到同一个bucket上。
- range遍历的时候随机一个位置开始
Map扩容机制
- 成倍扩容: (元素数量/bucket数量) > 6.5时触发成倍扩容,元素顺序变化
- 等量扩容:溢出桶的数量大于等于 2*B时 触发等量扩容,不会改变元素顺序
Map顺序输出
Golang中map的遍历输出的时候是无序的,不同的遍历会有不同的输出结果,如果想要顺序输出的话,需要额外保存顺序,例如使用slice,将slice中排序,再通过slice的顺序去读取。
-
package main
-
-
import (
-
"fmt"
-
"sort"
-
)
-
-
func sortMap(testMap map[string]string) {
-
var testSlice []string
-
for key, value := range testMap {
-
testSlice = append(testSlice, key)
-
fmt.Println(key, ":", value)
-
}
-
-
/* 对slice数组进行排序,然后就可以根据key值顺序读取map */
-
sort.Strings(testSlice)
-
fmt.Println("排序输出:")
-
for _, Key := range testSlice {
-
/* 按顺序从MAP中取值输出 */
-
fmt.Println(Key, ":", testMap[Key])
-
}
-
}
-
-
func main() {
-
/* 声明索引类型为字符串的map */
-
var testMap = make(map[string]string)
-
testMap["Bda"] = "B"
-
testMap["Ada"] = "A"
-
testMap["Dda"] = "D"
-
testMap["Cda"] = "C"
-
testMap["Eda"] = "E"
-
sortMap(testMap)
-
}
for range陷阱
关键字range
可用于循环,类似迭代器操作,它可以遍历slice
,array
,string
,map
和channel
,然后返回索引或值。
- 1. 只有一个返回值时,则第一个参数是index;
- 2. 遍历 map 为随机序输出,slice 为索引序输出;
- 3. range v 是值拷贝,且只会声明初始化一次;
-
func main() {
-
mySlice := []string{"I", "am", "peachesTao"}
-
fmt.Printf("遍历前首元素内存地址:%p\n", &mySlice[0])
-
for _, ele := range mySlice {
-
ele = ele "-new"
-
fmt.Printf("遍历中元素内存地址:%p\n", &ele)
-
}
-
fmt.Println(mySlice)
-
}
-
//遍历前首元素内存地址:0xc000070480
-
//遍历中元素内存地址:0xc00003a230
-
//遍历中元素内存地址:0xc00003a230
-
//遍历中元素内存地址:0xc00003a230
-
//[I am peachesTao]
-
-
func main() {
-
slice := []int{0, 1, 2, 3}
-
m := make(map[int]*int)
-
for key, val := range slice {
-
m[key] = &val
-
}
-
for k, v := range m {
-
fmt.Println(k, "->", *v)
-
}
-
}
答案:
-
0 -> 3
-
1 -> 3
-
2 -> 3
-
3 -> 3
参考解析:这是新手常会犯的错误写法,for range 循环的时候会创建每个元素的副本,而不是元素的引用,所以 m[key] = &val 取的都是变量 val 的地址,所以最后 map 中的所有元素的值都是变量 val 的地址,因为最后 val 被赋值为3,所有输出都是3.
-
func main() {
-
var m = [...]int{1, 2, 3}
-
-
for i, v := range m {
-
go func() {
-
fmt.Println(i, v)
-
}()
-
}
-
-
time.Sleep(time.Second * 1)
-
}
答案及解析:
-
2 3
-
2 3
-
2 3
for range 使用短变量声明(:=)的形式迭代变量,需要注意的是,变量 i、v 在每次循环体中都会被重用,而不是重新声明。各个 goroutine 中输出的 i、v 值都是 for range 循环结束后的 i、v 最终值,而不是各个goroutine启动时的i, v值。可以理解为闭包引用,使用的是上下文环境的值。
闭包换成函数传递
-
for i, v := range m {
-
go func(i,v int) {
-
fmt.Println(i, v)
-
}(i,v)
-
}
下面代码输出什么
-
func main() {
-
var a = []int{1, 2, 3, 4, 5}
-
var r = make([]int, 0)
-
-
for i, v := range a {
-
if i == 0 {
-
a = append(a, 6, 7)
-
fmt.Println(a)
-
}
-
-
r = append(r, v)
-
}
-
-
fmt.Println(r)
-
}
参考答案及解析:[1 2 3 4 5]。a 在 for range 过程中增加了两个元素,len 由 5 增加到 7,但 for range 时会使用 a 的副本 a’ 参与循环,副本的 len 依旧是 5,因此 for range 只会循环 5 次,也就只获取 a 对应的底层数组的前 5 个元素。
并发缺陷
Go语言原生的Map非并发安全的, 在多并发的情况下,如果有写的操作,会出现Panic,提示concurrent map writes的错误
-
func main() {
-
mm := map[int]int{}
-
for i := 0; i < 21; i {
-
go func() { mm[1] = 1 }()
-
}
-
}
另外如果多线程同时 read 和 write ,或者删除 key,还会出现 fatal error: concurrent map read and map write,这都是 map 存在的并发问题。
sync.RWMutex包实现
-
type Demo struct {
-
Data map[string]string
-
Lock sync.RWMutex
-
}
-
-
func (d Demo) Get(k string) string{
-
d.Lock.RLock()
-
defer d.Lock.RUnlock()
-
return d.Data[k]
-
}
-
-
func (d Demo) Set(k,v string) {
-
d.Lock.Lock()
-
defer d.Lock.Unlock()
-
d.Data[k]=v
-
}
-
-
func main() {
-
mapInfo := make(map[int]string)
-
mutex := sync.RWMutex{}
-
-
// 使用for循环模拟多个请求对map进行写操作。
-
for i := 0; i < 10000; i {
-
mutex.Lock()
-
go func(index int, mapInfo map[int]string) {
-
mapInfo[index] = "demo"
-
mutex.Unlock()
-
}(i, mapInfo)
-
}
-
-
fmt.Println(len(mapInfo))
-
-
// 正常写法
-
//mapInfo := make(map[int]string)
-
//mutex := sync.RWMutex{}
-
//mutex.Lock()
-
//mapInfo[0] = "demo"
-
//mutex.Unlock()
-
}
sync.map包实现
官方在新版本中推荐使用sync.Map
来实现并发写入操作。go1.9之后诞生了sync.Map。sync.Map思路来自java的ConcurrentHashMap。sync.map就是1.9版本带的线程安全map,主要有如下几种方法
-
Load(key interface{}) (value interface{}, ok bool)
-
//通过提供一个键key,查找对应的值value,如果不存在,则返回nil。ok的结果表示是否在map中找到值
-
-
Store(key, value interface{})
-
//这个相当于是写map(更新或新增),第一个参数是key,第二个参数是value
-
-
LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
-
//通过提供一个键key,查找对应的值value,如果存在返回键的现有值,否则存储并返回给定的值,如果是读取则返回true,如果是存储返回false
-
-
Delete(key interface{})
-
//通过提供一个键key,删除键对应的值
-
-
Range(f func(key, value interface{}) bool)
-
//循环读取map中的值。
-
//因为for ... range map是内置的语言特性,所以没有办法使用for range遍历sync.Map, 但是可以使用它的Range方法,通过回调的方式遍
-
var sy sync.Map
-
-
func main() {
-
sy.Store("name", "tom")
-
-
sy.Range(func(key, value interface{}) bool {
-
fmt.Println(key, value)
-
return false
-
})
-
}
sync.Map核心思想是减少锁,使用空间换取时间
。该包实现如下几个优化点:
-
空间换时间。通过冗余的两个数据结构(read、dirty),实现加锁对性能的影响。
-
使用只读数据(read),避免读写冲突。
-
动态调整,miss次数多了之后,将dirty数据提升为read。
-
double-checking。
-
延迟删除。删除一个键值只是打标记,只有在提升dirty的时候才清理删除的数据。
-
优先从read读取、更新、删除,因为对read的读取不需要锁。
无法对 map 的 key 或 value 进行取址
-
package main
-
-
import "fmt"
-
-
func main() {
-
m := make(map[string]int)
-
-
fmt.Println(&m["qcrao"])
-
}
cannot assign to struct field list["student"].Name in map
-
package main
-
-
import "fmt"
-
-
type Student struct {
-
Name string
-
}
-
-
var list map[string]Student
-
-
func main() {
-
-
list = make(map[string]Student)
-
-
student := Student{"Aceld"}
-
-
list["student"] = student
-
list["student"].Name = "LDB"
-
-
fmt.Println(list["student"])
-
}
分析
map[string]Student
的 value 是一个 Student 结构值,所以当list["student"] = student
,是一个值拷贝过程。而list["student"]
则是一个值引用。那么值引用的特点是只读
。所以对list["student"].Name = "LDB"
的修改是不允许的。
-
package main
-
-
import "fmt"
-
-
type Student struct {
-
Name string
-
}
-
-
var list map[string]*Student
-
-
func main() {
-
-
list = make(map[string]*Student)
-
-
student := Student{"Aceld"}
-
-
list["student"] = &student
-
list["student"].Name = "LDB"
-
-
fmt.Println(list["student"])
-
}
指向的 Student 是可以随便修改的
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgcbjhf
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01