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 相关文章推荐
一文读懂go中semaphore(信号量)源码
Apr 03 Golang
golang判断key是否在map中的代码
Apr 24 Golang
golang 实现Location跳转方式
May 02 Golang
解决Golang中goroutine执行速度的问题
May 02 Golang
golang elasticsearch Client的使用详解
May 05 Golang
Golang 编译成DLL文件的操作
May 06 Golang
解决Goland 同一个package中函数互相调用的问题
May 06 Golang
victoriaMetrics库布隆过滤器初始化及使用详解
Apr 05 Golang
Golang 1.18 多模块Multi-Module工作区模式的新特性
Apr 11 Golang
golang操作redis的客户端包有多个比如redigo、go-redis
Apr 14 Golang
实现GO语言对数组切片去重
Apr 20 Golang
go goth封装第三方认证库示例详解
Aug 14 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
一个改进的UBB类
2006/10/09 PHP
PHP 地址栏信息的获取代码
2009/01/07 PHP
PHP 开发环境配置(测试开发环境)
2010/04/28 PHP
基于php无限分类的深入理解
2013/06/02 PHP
php将日期格式转换成xx天前的格式
2015/04/16 PHP
php实现zip文件解压操作
2015/11/03 PHP
Joomla简单判断用户是否登录的方法
2016/05/04 PHP
php自定义中文字符串截取函数substr_for_gb2312及substr_for_utf8示例
2016/05/28 PHP
ThinkPHP中create()方法自动验证表单信息
2017/04/28 PHP
laravel框架模型、视图与控制器简单操作示例
2019/10/10 PHP
javascript一些实用技巧小结
2011/03/18 Javascript
jquery的冒泡事件的阻止与允许(三种实现方法)
2013/02/01 Javascript
JavaScript 网页中实现一个计算当年还剩多少时间的倒数计时程序
2017/01/25 Javascript
详解Vue文档中几个易忽视部分的剖析
2018/03/24 Javascript
详解vue中移动端自适应方案
2019/05/05 Javascript
Python中asyncio模块的深入讲解
2019/06/10 Python
Django rest framework jwt的使用方法详解
2019/08/08 Python
使用Python为中秋节绘制一块美味的月饼
2019/09/11 Python
python编写softmax函数、交叉熵函数实例
2020/06/11 Python
Python读写csv文件流程及异常解决
2020/10/20 Python
linux面试题参考答案(2)
2015/12/06 面试题
Linux中如何用命令创建目录
2015/01/12 面试题
物流专业大学的自我评价
2014/01/11 职场文书
创业计划书如何编写
2014/02/06 职场文书
单位绩效考核方案
2014/05/11 职场文书
白血病募捐倡议书
2014/05/14 职场文书
幼师求职信
2014/06/23 职场文书
环境保护建议书
2014/08/26 职场文书
小学生清明节演讲稿
2014/09/05 职场文书
学习焦裕禄同志为人民服务思想汇报
2014/09/10 职场文书
售后客服个人自我评价
2014/09/14 职场文书
食品药品安全责任书
2015/05/11 职场文书
朋友聚会开场白
2015/06/01 职场文书
乡镇团代会开幕词
2016/03/04 职场文书
详解CocosCreator项目结构机制
2021/04/14 Javascript
Go 语言下基于Redis分布式锁的实现方式
2021/06/28 Golang