【问题标题】:Hashtable doubling?哈希表翻倍?
【发布时间】:2010-10-19 22:43:29
【问题描述】:

我不知道标题是否有意义,但我想知道当您向其中添加项目时哈希表如何扩大?

是否像List<T> 一样,当达到限制时它的大小会翻倍?如果是这样,那么这种加倍是否会从头开始重新创建集合(List<T> 也可以回答这个问题,因为我不确定它是否这样做)?

最后,如果它确实从头开始重新创建,那么这个特殊的 Add 操作对于不知道已达到限制的用户来说将是非常昂贵的,对吧?

【问题讨论】:

    标签: c# .net list hashtable


    【解决方案1】:

    我相信HashtableDictionary<TKey, TValue> 在当前计数加倍后会扩展为下一个素数,例如31 到 67。

    据我了解,调整大小不涉及重新计算哈希(因为它们与条目一起存储),而是涉及将每个条目放入其新存储桶中,存储桶编号是基于该存储桶的哈希码和桶数。

    您询问了List<T> - 这真的很简单。该列表由一个数组支持,您只需要创建一个具有正确大小的新数组,并复制当前数组的内容。比如:

    private void Resize(int newCapacity)
    {
        T[] tmp = new T[newCapacity];
        Array.Copy(backingArray, tmp, backingArray.Length);
        backingArray = tmp;
    }
    

    【讨论】:

    • 有趣。我想知道它是否有一个素数列表,或者它是否即时计算它们。如果它计算,那计算可能比副本更昂贵!
    • 我相信它会即时计算它们...但是如果您要从大约 100 万个条目增加到大约 200 万个条目(即它是一个 big 地图),您仍然只需要针对大约 1000 个潜在除数检查每个可能的素数。然后,您必须为一百万个条目找到合适的存储桶!
    • 我想这是一个经典的性能与空间问题......因为它为每个存储的对象增加了 4 倍的存储空间。也许我应该检查一下来源......
    • 什么作用?不重新计算哈希码?如果您不存储它,那么每次在进行查找时获得 潜在 匹配时,您都必须重新计算它。 “桶位置”冲突比真正的哈希冲突更有可能发生。
    • @Mehrdad:您能否在回答中多解释一下该评论?
    【解决方案2】:

    大小并不总是翻倍,但会根据项目的数量而变化。

    对于一个列表,这并不像重新创建一个字符串或数组那样昂贵,因为只需将指针从一个列表复制到另一个列表,并且可以非常有效地完成。

    对于哈希表/字典,必须重新分配项目,这可能非常昂贵。最好提前用估计的大小初始化哈希表。

    【讨论】:

      【解决方案3】:

      这当然取决于你的哈希实现。

      有些哈希值加倍,有些将它们的大小更改为其他任意大小(例如,下一个素数)。

      大多数哈希在更改其缓冲区大小后都需要重新哈希,这“只是”移动指针,但仍然与哈希大小成线性关系。但是,一些哈希使用一致的哈希,这减少了移动元素的需要(通常,只有一小部分元素需要移动)。

      【讨论】:

      • 他在询问具体的 .NET Hashtable 实现。
      【解决方案4】:

      如果有兴趣,为什么不深入reflector 做一些研究:

      private void Insert(object key, object nvalue, bool add)
      {
          uint num;
          uint num2;
          if (key == null)
          {
              throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));
          }
          if (this.count >= this.loadsize)
          {
              this.expand();
          }
          else if ((this.occupancy > this.loadsize) && (this.count > 100))
          {
              this.rehash();
          }
          uint num3 = this.InitHash(key, this.buckets.Length, out num, out num2);
          int num4 = 0;
          int index = -1;
          int num6 = (int) (num % this.buckets.Length);
      Label_0071:
          if (((index == -1) && (this.buckets[num6].key == this.buckets)) && (this.buckets[num6].hash_coll < 0))
          {
              index = num6;
          }
          if ((this.buckets[num6].key == null) || ((this.buckets[num6].key == this.buckets) && ((this.buckets[num6].hash_coll & 0x80000000L) == 0L)))
          {
              if (index != -1)
              {
                  num6 = index;
              }
              Thread.BeginCriticalRegion();
              this.isWriterInProgress = true;
              this.buckets[num6].val = nvalue;
              this.buckets[num6].key = key;
              this.buckets[num6].hash_coll |= (int) num3;
              this.count++;
              this.UpdateVersion();
              this.isWriterInProgress = false;
              Thread.EndCriticalRegion();
          }
          else if (((this.buckets[num6].hash_coll & 0x7fffffff) == num3) && this.KeyEquals(this.buckets[num6].key, key))
          {
              if (add)
              {
                  throw new ArgumentException(Environment.GetResourceString("Argument_AddingDuplicate__", new object[] { this.buckets[num6].key, key }));
              }
              Thread.BeginCriticalRegion();
              this.isWriterInProgress = true;
              this.buckets[num6].val = nvalue;
              this.UpdateVersion();
              this.isWriterInProgress = false;
              Thread.EndCriticalRegion();
          }
          else
          {
              if ((index == -1) && (this.buckets[num6].hash_coll >= 0))
              {
                  this.buckets[num6].hash_coll |= -2147483648;
                  this.occupancy++;
              }
              num6 = (int) ((num6 + num2) % ((ulong) this.buckets.Length));
              if (++num4 < this.buckets.Length)
              {
                  goto Label_0071;
              }
              if (index == -1)
              {
                  throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_HashInsertFailed"));
              }
              Thread.BeginCriticalRegion();
              this.isWriterInProgress = true;
              this.buckets[index].val = nvalue;
              this.buckets[index].key = key;
              this.buckets[index].hash_coll |= (int) num3;
              this.count++;
              this.UpdateVersion();
              this.isWriterInProgress = false;
              Thread.EndCriticalRegion();
          }
      }
      

      【讨论】:

        【解决方案5】:

        来自Hashtable.Add() 上的 MSDN 页面:

        如果 Count 小于容量 Hashtable,这个方法是 O(1) 手术。如果容量需要 增加以适应新的 元素,此方法变为 O(n) 运算,其中 n 是计数。

        由于 List 具有相同的注释,我假设它们在内存分配方面的内部工作方式相似。

        【讨论】:

          【解决方案6】:

          哈希表使用存储桶工作,每个存储桶可以容纳多个项目(至少在大多数实现中,有一些在已经使用的存储桶的情况下会重用其他存储桶)。桶的数量通常是素数,因此将哈希码除以桶的数量会返回可接受的“好”哈希分布。

          通常,有一个特定的填充因子会触发更多桶的添加,从而触发哈希表的重建。由于哈希除以桶数,因此需要根据新的桶索引重新分配实例,这基本上是从头开始重新创建。

          对于 .NET 哈希表,您可以在 some constructors 中指定“加载因子”。来自 MSDN:

          负载系数是最大比例 元素到桶。更小的负载 因素意味着更快的查找成本 增加内存消耗。一种 负载因子 1.0 是最佳平衡 在速度和大小之间。

          【讨论】:

            猜你喜欢
            • 2015-12-31
            • 1970-01-01
            • 1970-01-01
            • 2011-12-10
            • 2023-03-31
            • 2021-06-22
            • 2023-03-27
            • 2012-09-16
            • 2013-03-21
            相关资源
            最近更新 更多