【问题标题】:Selecting a good dictionary key选择一个好的字典键
【发布时间】:2010-10-14 14:02:15
【问题描述】:

我有一个对象,我想用它来查找其他对象。我将使用Dictionary<TKey, TValue>()

密钥对象有两个唯一标识它的字符串,例如KeyObj.Str1KeyObj.Str2

您建议我使用什么作为字典的键?

1:字符串的串联。

Dictionary<String, TValue>();
Key = KeyObj.Str1:KeyObj.Str2; ("somestring:anotherstring")

2:每个对象都有一个唯一的整数来识别它?

Dictionary<int, TValue>();
KeyObj.ID = _nextID++;
Key = KeyObj.ID;

3:对对象的引用。

Dictionary<KeyObj, TValue>();
Key = KeyObj;

选项 3 将是最简单的,但根据参考值索引字典似乎效率低下。

如果 key 对象包含一个唯一的字符串,那么显而易见的选择是使用它,但是只有两个唯一的字符串组合起来会变得更加困难。

【问题讨论】:

    标签: c# .net dictionary indexing key


    【解决方案1】:

    串联的字符串应该效果最好。

    如果您知道它们的组合是唯一的,那么这就是您应该选择的——记住哈希码通常是唯一的,但并非总是如此。

    【讨论】:

      【解决方案2】:

      如果您可以适当地覆盖 GetHashCode() 和 Equals(),则可以使用选项 3,即类似这样的内容:

          public override int GetHashCode()
          {
              return str1.GetHashCode() ^ str2.GetHashCode();
          }
      
          public override bool Equals(object obj)
          {
              if (!obj is KeyObj)
              {
                  return false;
              }
      
              KeyObj key = (KeyObj)obj;
              return this.str1.Equals(key.str1) && this.str2.Equals(key.str2);
          }
      

      【讨论】:

      • str1.GetHashCode() ^ str2.GetHashCode() 很容易引起溢出。请务必使用unchecked 包装操作。请记住,它并不能 100% 保证密钥是唯一的。
      【解决方案3】:

      如何使用 KeyObj.GetHashCode()?

      【讨论】:

      • 根据MSDN:GetHashCode方法的默认实现不保证不同对象的返回值唯一。
      • (因此这里的实际问题是如何实现它)
      【解决方案4】:

      它们中的任何一个都是有效的,但我假设您希望能够根据两个字符串之一快速找到这些对象,因此使用 int 作为键意味着您仍然需要扫描找到您想要的对象的值。

      字符串是否都是唯一的,还是仅在组合时才唯一?如果它们都是独一无二的,并且您愿意交换一点空间,您可以这样做:

      dict.Add(KeyObj.Str1, KeyObj);
      dict.Add(KeyObj.Str2, KeyObj);
      

      并且有两个对字典中对象的引用,使用每个唯一的字符串作为键。或者,如果它们只是唯一的字符串,您总是可以将它们组合在一起,它会在内部使用哈希码来查找它们。

      【讨论】:

        【解决方案5】:

        连接它们可能是最好的主意。您可以在执行连接的 KeyObj 对象中公开一个属性,这样您就不必在每次访问字典值时都执行它。

        编辑:

        我显然误读了这个问题。我认为您真正想要做的是 1 和 3 的混合,您可以覆盖 Equals()GetHashCode() 以使用唯一标识对象的 strings(只要确保它们是不可变的!)

        public override Equals(object obj) 
        {
           if (obj == null || !(obj is KeyObj))
              return false;
           KeyObj other = (KeyObj)obj;
           if (this.Key1 == other.Key1 && this.Key2 == other.Key2)
             return true;
           return false;
        }
        
        public override GetHashCode()
        {
            return (this.Key1 + this.Key2).GetHashCode();
        }
        

        那么你可以使用你建议的第三个选项:

        Dictionary<KeyObj, ValueObj>...
        

        【讨论】:

          【解决方案6】:

          您不需要使用新类作为字典键。改用新的结构,因为它会更轻量级......并且显然由这两个字符串值组成。

          【讨论】:

          • 我使用的类比我在示例中描述的更复杂......我简化了它以保持清晰。我不想让它成为一个结构。
          【解决方案7】:

          如果性能是主要考虑因素,您可以考虑使用两个字符串的哈希值。但是你的“值”字段必须同时包含键和值。

          我有另一个 SO 问题的参考,我只需要找到它。

          Is it faster to search for a large string in a DB by its hashcode?

          但这个问题更面向 DB。并且性能被考虑了数千次迭代。

          【讨论】:

            【解决方案8】:

            请记住,字典是一个美化的哈希表,因此键(不是双关语)是使用一个键,该键将导致与另一个键的冲突极少(如果有的话)。我倾向于#3,但这是假设 KeyObj 类型具有良好的哈希值生成器。

            【讨论】:

            • 我不会这么说,因为所有键在字典中都必须是唯一的。
            • Dictionary 类是否隐式使用 KeyObj.GetHashCode() 来比较引用对象?
            • 它实际上使用了 EqualityComparer 的默认实现(如果您没有指定)。它使用 GetHashCode 结果来加快搜索速度(通过创建多个存储桶),但最后它使用 Equals 方法来确保它们是相同的。
            【解决方案9】:

            字符串作为键是最好的,看我的测试代码:

            var tupleKeyDict = new Dictionary, string>();

                    for (int i = 0; i < 1000000; i++)
                    {
                        tupleKeyDict.Add(new Tuple<int, int>(i,0),i.ToString() );
                    }
            
                    System.Diagnostics.Stopwatch stopWatch = new Stopwatch();
                    stopWatch.Start();
                    string e1 = tupleKeyDict[new Tuple<int, int>(0, 0)];
                    string e2 = tupleKeyDict[new Tuple<int, int>(500000, 0)];
                    string e3 = tupleKeyDict[new Tuple<int, int>(999999, 0)];
                    stopWatch.Stop();
                    Console.WriteLine("Tuplekey cost(tick): " + stopWatch.ElapsedTicks.ToString());
                    Console.WriteLine("Tuplekey cost(ms): " + stopWatch.ElapsedMilliseconds.ToString());
            
            
            
            
            
                    var strKeyDict = new Dictionary<string, string>();
            
                    for (int i = 0; i < 1000000; i++)
                    {
                        strKeyDict.Add(i.ToString() + ":0", i.ToString());
                    }
            
                    System.Diagnostics.Stopwatch stopWatch2 = new Stopwatch();
                    stopWatch2.Start();
                    string se1 = strKeyDict["0:0"];
                    string se2 = strKeyDict["500000:0"];
                    string se3 = strKeyDict["999999:0"];
                    stopWatch2.Stop();
                    Console.WriteLine("strkey cost(tick): " + stopWatch2.ElapsedTicks.ToString());
                    Console.WriteLine("strkey cost(ms): " + stopWatch2.ElapsedMilliseconds.ToString());
            
            
            
            
                    var intKeyDict = new Dictionary<int, string>();
            
                    for (int i = 0; i < 1000000; i++)
                    {
                        intKeyDict.Add(i, i.ToString());
                    }
            
                    System.Diagnostics.Stopwatch stopWatch3 = new Stopwatch();
                    stopWatch3.Start();
                    string ie1 = intKeyDict[0];
                    string ie2 = intKeyDict[500000];
                    string ie3 = intKeyDict[999999];
                    stopWatch3.Stop();
                    Console.WriteLine("intkey cost(tick): " + stopWatch3.ElapsedTicks.ToString());
                    Console.WriteLine("intkey cost(ms): " + stopWatch3.ElapsedMilliseconds.ToString());
            

            输出: 元组键成本(滴答):104 元组键成本(毫秒):0 键成本(滴答):12 键成本(毫秒):0 intkey成本(滴答):66 intkey 成本(毫秒):0

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2016-09-06
              • 2021-05-25
              • 1970-01-01
              • 1970-01-01
              • 2015-08-18
              • 2018-05-27
              • 1970-01-01
              相关资源
              最近更新 更多