【问题标题】:Does it make sense to use an object hash code as a memory cache key?使用对象哈希码作为内存缓存键是否有意义?
【发布时间】:2014-04-21 18:22:14
【问题描述】:

我正在尝试在 MemoryCache 对象中缓存昂贵函数的结果。

MemoryCache 需要一个字符串键,所以我想知道执行以下操作是否有效:

string key = Char.ConvertFromUtf32(myObject.GetHashCode());
if (!_resourceDescriptionCache.Contains(key))
{
    _resourceDescriptionCache[key] = ExpensiveFunction(myObject);
}
return (string)_resourceDescriptionCache[key];

使用单个 UTF32 字符作为可能很大的缓存的键感觉很奇怪。

【问题讨论】:

  • 我收到一个错误 UTF32 value must be between 0x000000 and 0x10ffff 所以我想我不能通过这种方式将 Int32 转换为 char。
  • 并非每个 32 位值都代表一个有效的 UTF32 代码点。简单,不是最快但相当有效的方法是使用哈希码的十六进制表示。凭记忆,myObject.GetHashCode().ToString("X")
  • 谢谢大家,非常有帮助的cmets
  • 2 年后,我很尴尬我曾经问过这个问题。我们如何成长!

标签: c# caching hash memorycache


【解决方案1】:

这取决于。

在很多情况下,使用 GetHashCode() 可能会导致错误行为:

哈希码用于在基于哈希表的集合中进行高效插入和查找。哈希码不是永久值。出于这个原因:

  • 不要序列化哈希码值或将它们存储在数据库中。
  • 请勿使用哈希码作为键从键控集合中检索对象。
  • 不要跨应用程序域或进程发送哈希码。在某些情况下,可以基于每个进程或每个应用程序域计算哈希码。

http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx

如果内存缓存发生(或将来可能发生)在与调用它的代码不同的进程或应用程序域中,则第 3 个条件失败。

使用单个 UTF32 字符作为可能很大的缓存的键感觉很奇怪。

如果你缓存了足够多的东西,由于Birthday Problem,32 位哈希的冲突率可能会高得令人不安。

在缓存数以千万计的东西时,我使用了一个名为 City Hash(由 Google 创建,开源)的 64 位哈希,并取得了很好的成功。您也可以使用 Guid,尽管与 64 位哈希相比,GUID 用于维护密钥的内存是 64 位哈希的两倍。

【讨论】:

  • 这是一个很好的观点。我违反了规则 #2 - 使用哈希码作为集合的键。现在我不知道如何产生一个便宜的好缓存键。例如,序列化对象会太昂贵。
  • ToHashCode() 目前是如何实现的?据推测,它作用于应该是唯一(足够)的属性或属性组合。您可以通过连接这些属性的字符串表示来构建您的字符串键。
  • 不幸的是,这个类实际上处理对象类型,所以GetHashCode() 以对象实现它的任何方式实现。如果该类型破坏了它的GetHashCode() 方法,并且存在冲突,那么冲突将共享相同的ExpensiveFunction(object) 值(就像一个序列化程序)——即使它们会产生不同的结果。我尽我所能通过实际使用key = myObject.GetType() + Char.ConvertFromUtf32(myObject.GetHashCode()); 来隔离这种效果
  • @Alain:您正在有效地将每个对象类型的 GetHashCode 实现为您的哈希键。如果你真的在处理任何对象,那可能是你能做的最好的事情。如果有能力让所有对象至少实现一个通用接口,你也可以让它们提供更好的哈希键。您仍然遇到与使用我的回答中提到的 GetHashCode() 相关的问题(另请查看链接,还有一些其他问题我认为不太可能适用,因此我没有复制这些问题)。
【解决方案2】:

哈希码可能会发生冲突。 return 0;GetHashCode 的有效实现。多个键将共享一个缓存槽,这不是您想要的......您会混淆对象。

如果您的代码不适用于 return 0; 作为 GetHashCode 的实现,则您的代码已损坏。

选择更好的缓存键。

【讨论】:

  • 怎么样:key = myObject.GetType() + Char.ConvertFromUtf32(myObject.GetHashCode());,这样正确实现 GetHashCode() 的类型就不会受到故意未能实现它的类型的影响?
  • 对于某些上下文,ExpensiveFunction() 是一个序列化程序,它反映在对象上,以便在一种“浏览器”窗口中预览其内容。每个对象都有一个正确的“预览”对我来说不太重要,更重要的是我不会浪费太多资源来重新序列化每个对象每次出现在对象浏览器中。
  • (在前面的评论中我误解了你的意思。)不过,你认为 myObject.GetHashCode() 是唯一的键。如果这实际上是 100% 的情况,那么这将起作用。 It's less important to me that every object has a correct 'preview' - 那么你的方法会奏效。即使是有损缓存键也可以。我不完全理解序列化注释,但也许您可以散列序列化表示的前 32 个字节并将其用作键。
  • @usr:如果使用的序列化格式具有任何类型的公共标头,则前 32 个字节将为散列函数提供不太理想的输入(例如,如果每个序列化值都以 <xml> 开头)。
  • 那和一个序列化的表示恰好是我存储在缓存中的内容,因此生成它来生成一个密钥会破坏这种情况下的目的。
【解决方案3】:

内存缓存由普通 C# 字典支持。它真的没有太大的不同,除了它提供过期的事实

发生碰撞的几率是 2^32,这是一个整数的大小。即使您确实设法发生碰撞,字典也有安全措施(通过在碰撞时使用 Equals)

编辑:只有当字典被赋予未更改的键时才会处理键冲突(例如:Dictionary())。在这种情况下,由于 MemoryCache 使用字符串,所以没有碰撞检测。

【讨论】:

  • potentially large cache = 潜在的高冲突率en.wikipedia.org/wiki/Birthday_problem
  • 字典中的冲突使用更昂贵的Equals 方法解决。当使用哈希码作为字典哈希冲突的键时,会导致不正确的行为,而不仅仅是更昂贵的计算。这是一个巨大的差异。
  • 在提供的代码示例中,它使用了 object.GetHashCode(),它可能非常独特。并且可能很大,如果它有几百万个键,字典仍然可以很好地处理它;毕竟有大约 42 亿个可能的密钥。生日问题不适用,除非实现了一个自定义哈希生成器,它只生成有限的密钥集。
  • @Dan:只有 100,000 个对象,就有 68% 的机会发生至少一次哈希冲突。对于 1,000,000 个条目,概率非常接近 100%。 lazycackle.com/…
猜你喜欢
  • 2021-07-19
  • 2011-04-16
  • 2011-03-29
  • 1970-01-01
  • 1970-01-01
  • 2016-06-30
  • 2017-07-29
  • 2011-05-19
  • 1970-01-01
相关资源
最近更新 更多