关于c#:使用字节数组作为字典键

Using byte array as dictionary key

本问题已经有最佳答案,请猛点这里访问。

我想在concurentDictionary中使用字节数组作为查找键。目前,我通过使用自定义EqualityComparer来解决这个问题。

这很好,但是我确实意识到我的哈希代码生成器会产生很多重叠,在这些重叠中,事情最终会出现在同一个哈希桶中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ByteArrayEqualityComparer : EqualityComparer<byte[]>
{
    public override bool Equals(byte[] x, byte[] y)
    {
        //fast buffer compare
        return UnsafeCompare(x, y);
    }

    public override int GetHashCode(byte[] obj)
    {
        int hash = 0;
        for (int i = 0; i < obj.Length; i += 2)
        {
            hash += obj[i]; //xor? shift? black magic?
        }
        return hash;
    }
}

从字节数组中创建相对快速的哈希值的好公式是什么?

我的想法是,我可以通过跳过每x字节的速度来计算哈希代码。由于最后的比较仍然是在完整的数据集上完成的,所以多次比较所有字节似乎是毫无意义的。

我认为一些XOR魔法和转移hash var可以使事情变得更好。

这是非常关键的性能,所以也欢迎使用任何快捷方式。

[编辑]我最终使用了这个解决方案。我使用一个结构来包装字节数组,这样我就可以为它使用缓存的哈希代码,而不是为每个比较计算它。这导致了非常好的性能提升。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public struct ByteArrayKey
{
    public readonly byte[] Bytes;
    private readonly int _hashCode;

    public override bool Equals(object obj)
    {
        var other = (ByteArrayKey) obj;
        return Compare(Bytes, other.Bytes);
    }

    public override int GetHashCode()
    {
        return _hashCode;
    }

    private static int GetHashCode([NotNull] byte[] bytes)
    {
        unchecked
        {
            var hash = 17;
            for (var i = 0; i < bytes.Length; i++)
            {
                hash = hash*23 + bytes[i];
            }
            return hash;
        }
    }

    public ByteArrayKey(byte[] bytes)
    {
        Bytes = bytes;
        _hashCode = GetHashCode(bytes);
    }

    public static ByteArrayKey Create(byte[] bytes)
    {
        return new ByteArrayKey(bytes);
    }

    public static unsafe bool Compare(byte[] a1, byte[] a2)
    {
        if (a1 == null || a2 == null || a1.Length != a2.Length)
            return false;
        fixed (byte* p1 = a1, p2 = a2)
        {
            byte* x1 = p1, x2 = p2;
            var l = a1.Length;
            for (var i = 0; i < l/8; i++, x1 += 8, x2 += 8)
                if (*(long*) x1 != *(long*) x2) return false;
            if ((l & 4) != 0)
            {
                if (*(int*) x1 != *(int*) x2) return false;
                x1 += 4;
                x2 += 4;
            }
            if ((l & 2) != 0)
            {
                if (*(short*) x1 != *(short*) x2) return false;
                x1 += 2;
                x2 += 2;
            }
            if ((l & 1) != 0) if (*x1 != *x2) return false;
            return true;
        }
    }
}


哈希的更好选择可能是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public override int GetHashCode(byte[] obj)
{
    int hash = 0;
    for (int i = 0; i < obj.Length; i++)
    {
        exponents = [0, 8, 16, 24];
        exponent = exponents[i % 4];

        unchecked
        {
            hash += obj[i] * (1 << i);
        }
    }
    return hash;
}

从概念上讲,这会将4个字节的每个块转换为int,因为这两个字节都是32位,然后将它们与标准整数溢出相加。因此,长度小于等于4的所有唯一字节数组将映射到不同的哈希代码,并且(给定随机数据)较大的数组应在哈希空间中分布良好。如果您期望许多非常相似的数组,或者每4个或更多重复一次的数组,那么这可能不是最佳策略。


杂音杂音很快也很简单。有许多基于.NET的实现,但我不知道它们的性能如何。