Golang 空map和未初始化map的注意事项说明


Posted in Golang onApril 29, 2021

可以对未初始化的map进行取值,但取出来的东西是空:

var m1 map[string]string
fmt.Println(m1["1"])

不能对未初始化的map进行赋值,这样将会抛出一个异常:

panic: assignment to entry in nil map

var m1 map[string]string
m1["1"] = "1"

通过fmt打印map时,空map和nil map结果是一样的,都为map[]。所以,这个时候别断定map是空还是nil,而应该通过map == nil来判断。

补充:Golang清空map的两种方式及性能比拼

一、Golang中删除map的方法

1、所有Go版本通用方法

a := make(map[string]int)
a["a"] = 1
a["b"] = 2
// clear all
a = make(map[string]int)

2. Go 1.11版本以上用法

通过Go的内部函数mapclear方法删除。这个函数并没有显示的调用方法,当你使用for循环遍历删除所有元素时,Go的编译器会优化成Go内部函数mapclear。

package main
func main() {
        m := make(map[byte]int)
        m[1] = 1
        m[2] = 2
        for k := range m {
	        delete(m, k)
        }
}

把上述源代码直接编译成汇编(默认编译是会优化的):

go tool compile -S map_clear.go

可以看到编译器把源码9行的for循环直接优化成了mapclear去删除所有元素。如下:

Golang 空map和未初始化map的注意事项说明

再来看看关闭优化后的结果:

go tool compile -l -N -S map_clear.go

关闭优化选项后,Go编译器直接通过循环遍历来删除map里面的元素。

Golang 空map和未初始化map的注意事项说明

具体的mapclear代码可以在go源码库中runtime/map.go文件中看到,代码如下:

// mapclear deletes all keys from a map.
func mapclear(t *maptype, h *hmap) {
	if raceenabled && h != nil {
		callerpc := getcallerpc()
		pc := funcPC(mapclear)
		racewritepc(unsafe.Pointer(h), callerpc, pc)
	}
	if h == nil || h.count == 0 {
		return
	}
	if h.flags&hashWriting != 0 {
		throw("concurrent map writes")
	}
	h.flags ^= hashWriting
	h.flags &^= sameSizeGrow
	h.oldbuckets = nil
	h.nevacuate = 0
	h.noverflow = 0
	h.count = 0
	// Keep the mapextra allocation but clear any extra information.
	if h.extra != nil {
		*h.extra = mapextra{}
	}
	// makeBucketArray clears the memory pointed to by h.buckets
	// and recovers any overflow buckets by generating them
	// as if h.buckets was newly alloced.
	_, nextOverflow := makeBucketArray(t, h.B, h.buckets)
	if nextOverflow != nil {
		// If overflow buckets are created then h.extra
		// will have been allocated during initial bucket creation.
		h.extra.nextOverflow = nextOverflow
	}
	if h.flags&hashWriting == 0 {
		throw("concurrent map writes")
	}
	h.flags &^= hashWriting
}

二、两种清空map方式性能比较

1、先用benchmark的方式测一下两种方式

benchmark代码如下:

func BenchmarkMakeNewMap(b *testing.B) {
	tmpMap := make(map[string]string, 10000)
	for i := 0; i < b.N; i++ {
		for j := 0; j < 10000; j++ {
			tmpMap["tmp"+strconv.Itoa(j)] = "tmp"
		}
		tmpMap = make(map[string]string, 10000)
	}
}
func BenchmarkDeleteMap(b *testing.B) {
	tmpMap := make(map[string]string, 10000)
	for i := 0; i < b.N; i++ {
		for j := 0; j < 10000; j++ {
			tmpMap["tmp"+strconv.Itoa(j)] = "tmp"
		}
		for k := range tmpMap {
			delete(tmpMap, k)
		}
	}
}

得到测试结果如下:

Golang 空map和未初始化map的注意事项说明

从测试结果上看,好像确实delete的方式效率更高,但是这个benchmark中总感觉没有测试到真正清空map的地方,中间穿插着put map的操作,我们用方法2再测一下。

2、单个UT测一下两种方式

UT代码如下:

测试过程中禁用了gc,避免gc对运行时间和内存产生干扰。

func TestMakeNewMap(t *testing.T) {
   debug.SetGCPercent(-1)
   var m runtime.MemStats
   tmpMap := make(map[string]string, 1000000)
   for j := 0; j < 1000000; j++ {
      tmpMap["tmp"+strconv.Itoa(j)] = "tmp"
   }
   start := time.Now()
   tmpMap = make(map[string]string, 1000000)
   fmt.Println(time.Since(start).Microseconds())
   runtime.ReadMemStats(&m)
   fmt.Printf("%d Kb\n", m.Alloc/1024)
}
func TestDeleteMap(t *testing.T) {
   debug.SetGCPercent(-1)
   var m runtime.MemStats
   tmpMap2 := make(map[string]string, 1000000)
   for j := 0; j < 1000000; j++ {
      tmpMap2["tmp"+strconv.Itoa(j)] = "tmp"
   }
   start := time.Now()
   for k := range tmpMap2 {
      delete(tmpMap2, k)
   }
   fmt.Println(time.Since(start).Microseconds())
   runtime.ReadMemStats(&m)
   fmt.Printf("%d Kb\n", m.Alloc/1024)
}

测试结果如下:

Golang 空map和未初始化map的注意事项说明

从测试结果上看,好像确实是make方式的效率更低,而且内存占用更多,但结果真的是这样吗?

我们把make方式的make map的大小改为0再试一下:

tmpMap = make(map[string]string)

得到如下结果,What?时间为0了,内存消耗也跟delete的方式一样:

Golang 空map和未初始化map的注意事项说明

我们把make方式的make map的大小改为10000再试一下:

tmpMap = make(map[string]string, 10000)

结果如下:

Golang 空map和未初始化map的注意事项说明

三、总结

通过上面的测试,可以得出结论:

1、在map的数量级在10w以内的话,make方式会比delete方式速度更快,但是内存会消耗更多一点。

2、如果map数量级大于10w的话,delete的速度会更快,且内存消耗更少。

3、对于不再使用的map,直接使用make方式,长度为0清空更快。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
golang 实现对Map进行键值自定义排序
Apr 28 Golang
解决Go gorm踩过的坑
Apr 30 Golang
go语言中fallthrough的用法说明
May 06 Golang
golang 实现时间戳和时间的转化
May 07 Golang
Golang全局变量加锁的问题解决
May 08 Golang
Go 中的空白标识符下划线
Mar 25 Golang
Go归并排序算法的实现方法
Apr 06 Golang
Go并发4种方法简明讲解
Apr 06 Golang
golang使用map实现去除重复数组
Apr 14 Golang
GO语言字符串处理函数之处理Strings包
Apr 14 Golang
Golang 链表的学习和使用
Apr 19 Golang
Golang获取List列表元素的四种方式
Apr 20 Golang
彻底理解golang中什么是nil
基于Go Int转string几种方式性能测试
Apr 28 #Golang
Go语言中break label与goto label的区别
golang 如何用反射reflect操作结构体
Apr 28 #Golang
golang 生成对应的数据表struct定义操作
Apr 28 #Golang
golang 如何通过反射创建新对象
Apr 28 #Golang
golang 实现两个结构体复制字段
Apr 28 #Golang
You might like
用PHP与XML联手进行网站编程代码实例
2008/07/10 PHP
php记录日志的实现代码
2011/08/08 PHP
PHP通过正则表达式下载图片到本地的实现代码
2011/09/19 PHP
php的$_FILES的临时储存文件与回收机制实测过程
2013/07/12 PHP
yii2中使用Active Record模式的方法
2016/01/09 PHP
PHP实现深度优先搜索算法(DFS,Depth First Search)详解
2017/09/16 PHP
?牟┛途W扣了一??效果出?? target=
2007/05/27 Javascript
jQuery 1.5 源码解读 面向中高阶JSER
2011/04/05 Javascript
JQuery中$之选择器用法介绍
2011/04/05 Javascript
分享XmlHttpRequest调用Webservice的一点心得
2012/07/20 Javascript
AngularJS控制器controller正确的通信的方法
2016/01/25 Javascript
浅析JavaScriptSerializer类的序列化与反序列化
2016/11/22 Javascript
详解AngularJS验证、过滤器、指令
2017/01/04 Javascript
js实现多行文本框统计剩余字数功能
2017/03/28 Javascript
一篇文章让你彻底弄懂JS的事件冒泡和事件捕获
2017/08/14 Javascript
解决vue.js在编写过程中出现空格不规范报错的问题
2017/09/20 Javascript
JavaScript数组排序reverse()和sort()方法详解
2017/12/24 Javascript
详解vue-cli3开发Chrome插件实践
2019/05/29 Javascript
[01:07]2015国际邀请赛 中国区预选赛精彩回顾
2015/06/15 DOTA
[02:09]2018DOTA2亚洲邀请赛TNC赛前采访
2018/04/04 DOTA
Python3字符串学习教程
2015/08/20 Python
python使用wmi模块获取windows下的系统信息 监控系统
2015/10/27 Python
Python基于list的append和pop方法实现堆栈与队列功能示例
2017/07/24 Python
Python装饰器原理与简单用法实例分析
2018/04/29 Python
Python利用Faiss库实现ANN近邻搜索的方法详解
2020/08/03 Python
html5使用canvas压缩图片的示例代码
2018/09/11 HTML / CSS
北美个性化礼品商店:Things Remembered
2018/06/12 全球购物
英国专业美容产品在线:Mylee(从指甲到脱毛)
2020/07/06 全球购物
新加坡第一的杂货零售商:NTUC FairPrice
2020/12/05 全球购物
iostream与iostream.h的区别
2015/01/16 面试题
我们在web应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,如何输出一个某种编码的字符串?
2014/03/30 面试题
新闻学专业职业生涯规划范文:我的人生我做主
2014/09/12 职场文书
甜品蛋糕店创业计划书
2014/09/21 职场文书
2015幼儿园庆元旦活动方案
2014/12/09 职场文书
观看《杨善洲》宣传教育片心得体会
2016/01/23 职场文书
提取视频中的音频 Python只需要三行代码!
2021/05/10 Python