【发布时间】:2011-06-30 21:28:06
【问题描述】:
我有两个字符串,我想将它们用作字典键,但我有点懒于创建另一个对象,计算字符串的哈希码等。
那么,我可以得到两个字符串的哈希码,添加它们并将结果用作字典的键吗?
有可能造成冲突吗?对吧?
有什么想法吗?
【问题讨论】:
-
您使用的是哪个 .NET 版本?
标签: c# string data-structures dictionary hashcode
我有两个字符串,我想将它们用作字典键,但我有点懒于创建另一个对象,计算字符串的哈希码等。
那么,我可以得到两个字符串的哈希码,添加它们并将结果用作字典的键吗?
有可能造成冲突吗?对吧?
有什么想法吗?
【问题讨论】:
标签: c# string data-structures dictionary hashcode
我有两个字符串,我想 将它们用作字典键,但我 m 有点懒得再创建一个对象了
在 .NET 4.0 中,您可以使用 Tuple<T1, T2> 类作为键,其中 T1 和 T2 = 字符串。
我可以得到两个字符串的哈希码吗 , 添加它们并将结果用作 字典的键?
Tuple<T1, T2> 用于组合哈希码的公式类似于(未记录或保证不会更改):((h1 << 5) + h1) ^ h2,对于您的目的应该足够体面。顺便说一句,天真的添加通常不是组合哈希码的最佳方式。
有可能造成冲突吗? 对吧?
这总是可能的,即使是 single 字符串。字符串多于 32 位整数。
【讨论】:
使用元组:
var dict = new Dictionary<Tuple<string,string>,SomeType>();
dict.Add(Tuple.Create("Hello","World"), new SomeType());
【讨论】:
如果您使用的是 .NET 4,则可以使用 Tuple 类:
Dictionary<Tuple<string, string>, TValue> dict = new ...
如果您不在 .NET 4 上,您应该创建自己的类型来保存它。
您可以使用KeyValuePair 结构,但它从基值类型继承了相关方法,因此严重依赖反射。这会影响性能(请参阅答案底部。)
对于键值对:
Dictionary<KeyValuePair<string, string>, TValue> dict = new ...
如果您不想自己做饭,可以使用以下通用类型:
public struct SimpleTuple<TValue1, TValue2>
{
private readonly TValue1 _Value1;
private readonly TValue2 _Value2;
public SimpleTuple(TValue1 value1, TValue2 value2)
{
_Value1 = value1;
_Value2 = value2;
}
public TValue1 Value1 { get { return _Value1; } }
public TValue2 Value2 { get { return _Value2; } }
public int GetHashCode()
{
unchecked
{
int result = 37;
result *= 23;
if Value1 != null)
result += Value1.GetHashCode();
result *= 23;
if (Value2 != null)
result += Value2.GetHashCode();
return result;
}
}
public override bool Equals(object obj)
{
if (obj == null) return false;
if (obj.GetType() != typeof(SimpleTuple<TValue1, TValue2>))
return false;
var other = (SimpleTuple<TValue1, TValue2>)obj;
return Equals(other.Value1, Value1) && Equals(other.Value2, Value2);
}
}
当然,KeyValuePair 也适用于 .NET 4.0,就像 good bad。
至于碰撞,这取决于你的意思。哈希表(字典在内部使用哈希表结构)总是有可能发生键冲突,但这就是比较起作用的地方。如果两个不同的键生成相同的哈希码,字典类会将键与键进行比较,以查看它们是否真的是相同的值,或者只是产生相同的哈希码。
为什么哈希表总是有可能发生冲突的原因最好用pidgeonhole principle (Wikipedia)来描述。
这意味着,如果两个不同的键会导致冲突,这不会有问题,它们都会以正确的值存储在字典中。
当然,如果您创建了两次相同的键,字典会将其视为相同的键,并且要么无法添加新值,要么覆盖现有的值(取决于您要求它添加值的方式。)
这将在重复键上引发异常:
dict.Add(key, value);
这将添加或覆盖现有的:
dict[key] = value;
针对 Ani 的评论,我为LINQPad 编写了以下简单的测试脚本。输出是:
键值对:975 毫秒 MyKeyValuePair:52ms脚本:
void Main()
{
const int iterations = 10 * 1000 * 1000;
// JIT preheat
Test1(1);
Test2(1);
Stopwatch sw = Stopwatch.StartNew();
Test1(iterations);
sw.Stop();
Debug.WriteLine("KeyValuePair: " + sw.ElapsedMilliseconds + "ms");
sw = Stopwatch.StartNew();
Test2(iterations);
sw.Stop();
Debug.WriteLine("MyKeyValuePair: " + sw.ElapsedMilliseconds + "ms");
}
public static void Test1(int iterations)
{
for (int index = 0; index < iterations; index++)
{
var kvp = new KeyValuePair<int, int>(index, index);
kvp.GetHashCode();
}
}
public static void Test2(int iterations)
{
for (int index = 0; index < iterations; index++)
{
var kvp = new MyKeyValuePair<int, int>(index, index);
kvp.GetHashCode();
}
}
public struct MyKeyValuePair<TKey, TValue>
{
private readonly TKey _Key;
private readonly TValue _Value;
public MyKeyValuePair(TKey key, TValue value)
{
_Key = key;
_Value = value;
}
public TKey Key { get { return _Key; } }
public TValue Value { get { return _Value; } }
public int GetHashCode()
{
unchecked
{
int result = 37;
result *= 23;
if (Key != null)
result += Key.GetHashCode();
result *= 23;
if (Value != null)
result += Value.GetHashCode();
return result;
}
}
public override bool Equals(object obj)
{
if (obj == null) return false;
if (obj.GetType() != typeof(MyKeyValuePair<TKey, TValue>))
return false;
var other = (MyKeyValuePair<TKey, TValue>)obj;
return Equals(other.Key, Key) && Equals(other.Value, Value);
}
}
【讨论】:
System.ValueType,因为它似乎没有覆盖它们。
简单的解决方案,适用于所有版本的 .net。只需将字符串连接在一起。
var dictionary = new Dictionary<string, int>();
dictionary.Add("The meaning" + " of life, the universe, and everything", 42);
当然,这仅适用于 2 个字符串(尽管您可以在许多其他类型上使用 .ToString())并且如果您不需要仅通过两个字符串之一查找字典,但如果您同时拥有这两个字符串很简单。
【讨论】: