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语言带缓冲的通道实现
Apr 26 Golang
golang中实现给gif、png、jpeg图片添加文字水印
Apr 26 Golang
go语言中json数据的读取和写出操作
Apr 28 Golang
解决golang在import自己的包报错的问题
Apr 29 Golang
golang中的并发和并行
May 08 Golang
Golang 语言控制并发 Goroutine的方法
Jun 30 Golang
修改并编译golang源码的操作步骤
Jul 25 Golang
Go语言特点及基本数据类型使用详解
Mar 21 Golang
Go归并排序算法的实现方法
Apr 06 Golang
Go语言测试库testify使用学习
Jul 23 Golang
Go中使用gjson来操作JSON数据的实现
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
深入探讨:Nginx 502 Bad Gateway错误的解决方法
2013/06/03 PHP
PHP中unset,array_splice删除数组中元素的区别
2014/07/28 PHP
基于PHP给大家讲解防刷票的一些技巧
2015/11/18 PHP
thinkPHP3.x常量整理(预定义常量/路径常量/系统常量)
2016/05/20 PHP
起点页面传值js,有空研究学习下
2010/01/25 Javascript
js function使用心得
2010/05/10 Javascript
使用js+jquery实现无限极联动
2013/05/23 Javascript
js 触发select onchange事件代码
2014/03/20 Javascript
javascript动态修改Li节点值的方法
2015/01/20 Javascript
jQuery AJAX timeout 超时问题详解
2016/06/21 Javascript
KnockoutJS 3.X API 第四章之表单textInput、hasFocus、checked绑定
2016/10/11 Javascript
vue插件tab选项卡使用小结
2016/10/27 Javascript
浅析jQuery操作select控件的取值和设值
2016/12/07 Javascript
JS获取当前时间的实例代码(昨天、今天、明天)
2018/11/13 Javascript
利用原生JS实现data方法示例代码
2019/05/28 Javascript
中高级前端必须了解的JS中的内存管理(推荐)
2019/07/04 Javascript
JavaScript构造函数原理及实现流程解析
2020/11/19 Javascript
python逐行读取文件内容的三种方法
2014/01/20 Python
Python基于scrapy采集数据时使用代理服务器的方法
2015/04/16 Python
python3实现windows下同名进程监控
2018/06/21 Python
Python 十六进制整数与ASCii编码字符串相互转换方法
2018/07/09 Python
Pytorch 实现自定义参数层的例子
2019/08/17 Python
使用Python将字符串转换为格式化的日期时间字符串
2019/09/01 Python
在django中自定义字段Field详解
2019/12/03 Python
matplotlib grid()设置网格线外观的实现
2021/02/22 Python
英国赛车、汽车改装和摩托车零件购物网站:Demon Tweeks
2018/10/29 全球购物
新闻网站实习自我鉴定
2013/09/25 职场文书
新入职员工的自我介绍演讲稿
2014/01/02 职场文书
长辈证婚人证婚词
2014/01/09 职场文书
分公司总经理岗位职责
2014/08/03 职场文书
法定代表人授权委托书范文
2014/09/22 职场文书
演讲开头怎么书写?
2019/08/06 职场文书
30岁前绝不能错过的10本书
2019/08/08 职场文书
MySQL Shell import_table数据导入的实现
2021/08/07 MySQL
Python使用华为API为图像设置多个锚点标签
2022/04/12 Python
Elasticsearch 数据类型及管理
2022/04/19 Python