什么应该是哈希表中的关键的最佳实践

时间:2013-01-16 20:19:39

标签: java algorithm data-structures hashmap hashtable

最佳查找结构是HashTable。它提供了平均的持续访问(在最坏的情况下是线性的) 这取决于散列函数。好吧。
我的问题如下。假设HashTable的良好实施例如HashMap是否有关于地图中传递的密钥的最佳实践?我的意思是建议密钥必须是不可变对象,但我想知道是否还有其他建议。
示例键的大小?例如,如果我们使用String作为键,那么在一个好的hashmap中(以上述方式),equals(试图找到密钥)的字符串比较中不会出现“瓶颈”吗?钥匙要保持小吗?或者是否有不应该用作键的对象?例如。一个URL?在这种情况下,您如何选择使用什么作为关键?

4 个答案:

答案 0 :(得分:1)

您应该使用您想要使用的任何键来查找数据结构中的内容,它通常是特定于域的约束。有了这个,请记住,hashCode()equals()都将用于查找表中的键。

hashCode()用于查找密钥的位置,而equals()用于确定您要搜索的密钥是否实际上是我们刚使用hashCode()找到的密钥。

例如,考虑使用separate chaining在表中具有相同哈希码的两个键ab。然后,一旦我们找到包含a的列表的索引,则搜索a.equals(key)将需要对表中的ab进行a测试来自b的{​​{1}}。

答案 1 :(得分:1)

HashMap表现最佳的密钥可能是整数,其中hashCode()equals()实现为:

public int hashCode() {
    return value;
}

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

说,HashMap的目的是将一些对象(值)映射到其他对象(键)。散列函数用于寻址(值)对象的事实是提供快速,恒定时间的访问。

  

建议密钥必须是不可变对象,但我想知道是否还有其他建议。

建议将对象映射到你需要的东西:不要认为什么是更快的;但是想想你的业务逻辑最适合解决要检索的对象。

重要的要求是密钥对象必须是不可变的,因为如果在将密钥对象存储到Map中之后更改密钥对象,则以后可能无法检索关联的值。

HashMap中的关键字Map。您的对象应该只是 map 。如果你牺牲了优化密钥的映射任务,那么你就无法实现Map的目的 - 而不会实现任何性能提升。

我100%同意你问题中的前两条评论:

  

主要的限制是它必须是你想要查找的东西;)
   - 奥利查尔斯沃思

     

一般规则是使用任何你需要查看的关键词    - Louis Wasserman

请记住优化的两条规则:

  1. 别。
  2. (仅限专家)还没有。
  3. 第三条规则是:优化前的个人资料

答案 2 :(得分:0)

我挖掘了实施。我假设hashCode()方法的有效性将是关键因素。

当我查看HashMap()Hashtable()实现时,我发现实现非常相似(有一个例外)。两者都在为所有条目使用和存储内部哈希码,因此hashCode()不会对性能产生如此大的影响。

两者都有许多存储桶,存储值。桶的数量(比如n)和桶内的平均密钥数(比如k)之间的重要平衡。桶在O(1)时间内找到,桶的内容以O(k)大小迭代,但是我们拥有的桶越多,分配的内存就越多。此外,如果许多存储桶为空,则意味着密钥类的hashCode()方法的哈希码不够宽。

算法的工作原理如下:

Take the `hashCode()` of the Key (and make a slight bijective transformation on it)
Find the appropriate bucket
Loop through the content of the bucket (which is some kind of LinkedList)
Make the comparison of the keys as follows:
1. Compare the hashcodes 
    (it is calculated in the first step, and stored for the entry)
2. Examine if key `==` the stored key (still no call) 
    (this step is missing from Hashtable)
3. Compare the keys by `key.equals(storedKey)`

总结:

    每次调用都会调用
  • hashCode()(这是必须的,你不能这样做 没有它)
  • 如果hashCode传播得不好,并且两个键恰好具有相同的哈希码,则会调用
  • equals()

相同的算法适用于get()put()(因为在put()情况下,您可以设置现有密钥的值)。因此,最重要的是如何实施hashCode()方法。这是最常被称为的方法。

两种策略是:快速使其有效(传播良好)。 JDK开发人员努力做到这两点,但并不总是可以同时使用它们。

  • Numeric类型很好
  • Object(和非重写类)很好(hashCode()是原生的),但您无法指定自己的equals()
  • String 不好,遍历字符,但之后缓存(请参阅下面的评论)
  • 任何具有同步hashCode()的类都不好
  • 任何有迭代的类都不好
  • 具有哈希码缓存的类更好一些(取决于用法)

对字符串的评论:为了使其快速,在JDK的第一个版本中,仅对前32个字符进行字符串哈希码计算。但它产生的哈希码并没有很好地传播,因此他们决定将所有字符都带入哈希码。

答案 3 :(得分:0)

  

建议密钥必须是不可变对象,但我想知道是否还有其他建议。

值的关键字应为final

大多数情况下,对象的字段用作键。如果该字段发生更改,则地图无法找到它:

void foo(Employee e) {
  map.put(e.getId(), e);
  String newId = e.getId() + "new";
  e.setId(newId);
  Employee e2 = e.get(newId);
  // e != e2 !
}

所以Employee根本不应该有setId()方法,但这很难,因为当你写Employee时,你不知道它将被键入什么。