【发布时间】:2018-07-02 13:30:26
【问题描述】:
我编写了这个函数来为我的测试用例生成随机的唯一 ID:
func uuid(t *testing.T) string {
uidCounterLock.Lock()
defer uidCounterLock.Unlock()
uidCounter++
//return "[" + t.Name() + "|" + strconv.FormatInt(uidCounter, 10) + "]"
return "[" + t.Name() + "|" + string(uidCounter) + "]"
}
var uidCounter int64 = 1
var uidCounterLock sync.Mutex
为了测试它,我在不同的 goroutine 中生成了一堆值,将它们发送到主线程,主线程通过执行 map[v] = map[v] + 1 将结果放入 map[string]int。此地图没有并发访问,它是主线程私有的。
var seen = make(map[string]int)
for v := range ch {
seen[v] = seen[v] + 1
if count := seen[v]; count > 1 {
fmt.Printf("Generated the same uuid %d times: %#v\n", count, v)
}
}
当我将uidCounter 转换为字符串时,我会在单个键上遇到大量冲突。当我使用strconv.FormatInt 时,我根本不会遇到任何冲突。
当我说很多时,我的意思是我刚刚从 2227980 生成的值中得到 1115919 值的碰撞,即 50% 的值在同一个键上发生碰撞。值不相等。对于相同的源代码,我总是得到相同数量的冲突,所以至少它在某种程度上是确定性的,即可能与竞争条件无关。
rune 中的整数溢出会是一个问题,我并不感到惊讶,但我离 2^31 还很远,这并不能解释为什么地图认为 50% 的值具有相同的键。另外,我不希望哈希冲突会影响正确性,只会影响性能,因为我可以遍历映射中的键,因此值存储在某处。
在输出中,所有打印的符文都是0xEFBFBD。它与最高有效 unicode 代码点的位数相同,但这也不真正匹配。
Generated the same uuid 2 times: "[TestUuidIsUnique|�]"
Generated the same uuid 3 times: "[TestUuidIsUnique|�]"
Generated the same uuid 4 times: "[TestUuidIsUnique|�]"
Generated the same uuid 5 times: "[TestUuidIsUnique|�]"
...
Generated the same uuid 2047 times: "[TestUuidIsUnique|�]"
Generated the same uuid 2048 times: "[TestUuidIsUnique|�]"
Generated the same uuid 2049 times: "[TestUuidIsUnique|�]"
...
这里发生了什么? go 作者是否假设hash(a) == hash(b) 暗示a == b 用于字符串?还是我只是错过了一些愚蠢的事情? go test -race 也没有抱怨。
我使用的是 macOS 10.13.2 和 go version go1.9.2 darwin/amd64。
【问题讨论】:
-
哈希表直接比较桶内的值,问题是你不断添加相同的字符串:
"[TestUuidIsUnique|�]" -
@JimB 当然,但为什么我得到相同的字符串?为什么它会停在那个确切的符文上?它甚至可以在操场上重现:play.golang.org/p/85KHbc4X9jV
-
无效符文的字符串转换返回一个包含unicode替换字符的字符串:“�”
-
@CeriseLimón 啊,就是这样!你想把它写成答案吗?
-
@FilipHaglund:将无效符文转换为字符串总是返回
"\ufffd"或"\xef\xbf\xbd"
标签: dictionary go hash concurrency goroutine