最近我看到Golang社区有一个非常有意思的提议,想要给Golang增加一个内置的clear()函数,来清除map、无内容的slice、指向array的指针等:proposal: spec: add clear(x) builtin, to clear map, zero content of slice, ptr-to-array #56351。在golang中清除一个map的所有元素,目前只有通过for循环+delete的方式,这确实不太优雅。
for k := range m {
delete(m, k)
}
对于map来说,for循环+delete的方式还有一个缺点,即map中的 空bucket 不会被删除,仍然会占用部分空间。具体原因可以自行搜索,本文不做详细介绍。
但其实,delete函数还存在一个小问题,而这个小问题让 delete函数的正确性 受到影响。我们看看下面这段代码:
package main
import (
"fmt"
"math"
)
func main() {
m := make(map[float64]string)
m[math.NaN()] = "Yes"
for k := range m {
delete(m, k)
}
fmt.Printf("Map has length %d, contents: %#v\n", len(m), m)
// 输出:Map has length 1, contents: map[float64]string{NaN:"Yes"}
}
感兴趣的读者可以自己跑一次试试。很明显,这里的代码运行结果与我们的预期不一致!如果map中存在以浮点数NaN为key的元素,你无法删除它。 造成这一问题的原因在于,Golang对浮点数的比较操作遵从IEEE754标准 Floating-point values are comparable and ordered, as defined by the IEEE-754 standard. 在这一标准下,NaN与任何浮点数都不相等,并且两个NaN之间也不相等。
而delete(m, k)函数需要通过对比值,删除与k相等的元素,如果m中没有与k相等的元素,delete函数什么也不做。所以,delete函数无法删除map中将NaN作为key的元素(即使你可以用range来迭代出它)
我在读到这一提案之前,并不知道NaN带来的问题,对此我也十分惊讶。增加一个内置的clear函数,固然能够提升代码效率,但更重要的是修正一个遗留的代码正确性问题,希望能在go 1.21版本中看到这一改进吧!