在字典中使用byte []作为键

时间:2009-09-17 17:58:57

标签: c#

我需要使用byte[]作为Dictionary中的关键字。由于byte[]未覆盖默认的GetHashCode方法,因此包含相同数据的两个单独的byte[]对象将在字典中使用两个单独的插槽。基本上我想要的是这个:

Dictionary<byte[], string> dict = new Dictionary<byte[], string>();
dict[new byte[] {1,2,3}] = "my string";
string str = dict[new byte[] {1,2,3}];
// I'd like str to be set to "my string" at this point

有一种简单的方法吗?我唯一能想到的就是构建一个包含byte[]的包装类,并根据GetHashCode的内容覆盖byte[],但这似乎容易出错。

7 个答案:

答案 0 :(得分:66)

默认情况下,byte[]将通过引用进行比较,在这种情况下,这不是您想要的。您需要做的是指定自定义IEqualityComparer<byte[]>并进行所需的比较。

例如

public class ByteArrayComparer : IEqualityComparer<byte[]> {
  public bool Equals(byte[] left, byte[] right) {
    if ( left == null || right == null ) {
      return left == right;
    }
    return left.SequenceEqual(right);
  }
  public int GetHashCode(byte[] key) {
    if (key == null)
      throw new ArgumentNullException("key");
    return key.Sum(b => b);
  }
}

然后你可以做

var dict = new Dictionary<byte[], string>(new ByteArrayComparer());

2.0的解决方案

public class ByteArrayComparer : IEqualityComparer<byte[]> {
  public bool Equals(byte[] left, byte[] right) {
    if ( left == null || right == null ) {
      return left == right;
    }
    if ( left.Length != right.Length ) {
      return false;
    }
    for ( int i= 0; i < left.Length; i++) {
      if ( left[i] != right[i] ) {
        return false;
      }
    }
    return true;
  }
  public int GetHashCode(byte[] key) {
    if (key == null)
      throw new ArgumentNullException("key");
    int sum = 0;
    foreach ( byte cur in key ) {
      sum += cur;
    }
    return sum;
  }
}

答案 1 :(得分:8)

所以,JaredPar的答案不是,但在某些方面可能会更好。首先,the IEqualityComparer page说“我们建议您从EqualityComparer类派生,而不是实现IEqualityComparer接口。”

其次,GetHashCode的实现应该是 fast 。它用于快速消除明显不同的对象,这显然是浪费时间来运行Equals。所以GetHashCode应该比实际运行Equals快得多。

第三,返回JaredPar所做的字节数组之和,很可能产生冲突 - 如果字节顺序不同,或者相对差异相互抵消等等。

所以我会推荐这样的解决方案:

public class ByteArrayComparer : EqualityComparer<byte[]>
{
    public override bool Equals(byte[] first, byte[] second)
    {
        if (first == null || second == null) {
            // null == null returns true.
            // non-null == null returns false.
            return first == second;
        }
        if (ReferenceEquals(first, second)) {
            return true;
        }
        if (first.Length != second.Length) {
            return false;
        }
        // Linq extension method is based on IEnumerable, must evaluate every item.
        return first.SequenceEqual(second);
    }
    public override int GetHashCode(byte[] obj)
    {
        if (obj == null) {
            throw new ArgumentNullException("obj");
        }
        // quick and dirty, instantly identifies obviously different
        // arrays as being different
        return obj.Length;
    }
}

上面,返回obj.Length,真的快速而肮脏,但也容易返回很多冲突。我想我们可以做得更好。

如果要检查所有字节,像JaredPar的答案中那样的事件比简单的字节总和更容易发生冲突。但同样,这将检查所有元素,因此它不会比实际运行Equals更好。你也可以无条件地返回0,并且总是强制使用Equals。

我强调:这比在JaredPar的答案中返回总和更好。并且总是返回0比这更好。返回obj.Length比返回0要好。

// This is not recommended. Performance is too horrible.
public override int GetHashCode(byte[] obj)
{
    // Inspired by fletcher checksum. Not fletcher.
    if (obj == null) {
        throw new ArgumentNullException("obj");
    }
    int sum = 0;
    int sumOfSum = 0;
    foreach (var val in obj) {
        sum += val; // by default, addition is unchecked. does not throw OverflowException.
        sumOfSum += sum;
    }
    return sum ^ sumOfSum;
}

如果你碰巧知道你用作密钥的byte []数组本身就是加密哈希,那么你可以利用这个假设给你带来好处,只需返回转换为{{1}的前4个字节。 }。对于通用字节数组,它也可能正常工作:

int

答案 2 :(得分:4)

你能否将byte []转换为字符串并将其用作密钥?

类似的东西:

        ASCIIEncoding enc = new ASCIIEncoding();
        byte[] input;
        string demo = new string(enc.GetChars(input));
        byte[] decode = enc.GetBytes(demo.ToCharArray());

答案 3 :(得分:3)

你的想法也是我的第一个想法。我不认为这会容易出错。但是如果你不喜欢这个选项,你可以创建一个实现IEqualityComparer的类,并将它的一个实例传递给Dictionary的构造函数。

答案 4 :(得分:3)

using System;
using System.Collections;
using System.Collections.Generic;

[Serializable]
class StructuralEqualityComparer : IEqualityComparer, IEqualityComparer<object>
{
    public new bool Equals(object x, object y)
    {
        var s = x as IStructuralEquatable;
        return s == null ? object.Equals(x, y) : s.Equals(y, this);
    }

    public int GetHashCode(object obj)
    {
        var s = obj as IStructuralEquatable;
        return s == null ? EqualityComparer<object>.Default.GetHashCode(obj) : s.GetHashCode(this);
    }
}

答案 5 :(得分:1)

通过不在数组上但在IEnumerable<T>上工作,使EqualityComparer更加通用。

由于事实,我们现在有一个T,我们需要能够为元素指定一个可选的相等比较器。

最后但并非最不重要的一点是,GetHashCode()永远不应该抛出,有时你需要快速,有时你需要在第一次运行时更准确。因此,您可以选择从我们自己的哈希值中考虑哈希码的项目数(最大值)来定义准确度。

public class EnumerableEqualityComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    private static readonly Lazy<IEqualityComparer<IEnumerable<T>>> Lazy = new Lazy<IEqualityComparer<IEnumerable<T>>>(() => new EnumerableEqualityComparer<T>());
    private int accuracy;
    private IEqualityComparer<T> comparer;

    public EnumerableEqualityComparer()
        : this(-1)
    {
    }

    public EnumerableEqualityComparer(int accuracy)
        : this(accuracy, null)
    {
    }

    public EnumerableEqualityComparer(IEqualityComparer<T> elementEqualityComparer)
        : this(-1, elementEqualityComparer)
    {
    }

    public EnumerableEqualityComparer(int accuracy, IEqualityComparer<T> elementEqualityComparer)
    {
        if (accuracy < 0)
        {
            accuracy = 4;
        }

        this.accuracy = accuracy;
        comparer = elementEqualityComparer ?? EqualityComparer<T>.Default;
    }

    public static IEqualityComparer<IEnumerable<T>> Default { get; private set; } = Lazy.Value;

    public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
    {
        if (ReferenceEquals(x, y))
        {
            return true;
        }

        if (ReferenceEquals(x, null)
            || ReferenceEquals(y, null))
        {
            return false;
        }

        return x.SequenceEqual(y, comparer);
    }

    public int GetHashCode(IEnumerable<T> obj)
    {
        if (ReferenceEquals(obj, null))
        {
            return -1;
        }

        var count = (obj as ICollection<T>)?.Count ?? 1;
        var hashCode = count * 49297;

        foreach (var item in obj.Take(accuracy))
        {
            hashCode += comparer.GetHashCode(item) * 17123;
        }

        return hashCode;
    }
}

答案 6 :(得分:-3)

当您从Dictionary中检索项目时,您正在使用new运算符作为byte []。这将在字典中查找不存在的不同(新)byte []实例。

这是一个有效的解决方案:

 var dict = new Dictionary<byte[], string>();

            var b = new byte[] { 1,2,3};

            dict[b] = "my string";

            var value = dict[b]; 

            Console.WriteLine(value);