Java中的HashMap实现。桶指数计算如何工作?

时间:2012-06-04 09:45:56

标签: java hashmap

我正在研究Java中HashMap的实现,并且一度陷入困境 如何计算indexFor函数?

static int indexFor(int h, int length) {
   return h & (length-1);
}

由于

5 个答案:

答案 0 :(得分:97)

散列本身是由您尝试存储的对象的hashCode()方法计算的。

您在此处看到的是根据哈希h计算存储对象的“存储桶”。理想情况下,为了避免碰撞,您将拥有与h的最大可实现值相同数量的存储桶 - 但这可能对内存要求太高。因此,您通常拥有较少数量的存储桶,存在碰撞危险。

如果h是1000,但您的基础数组中只有512个存储桶,则需要知道放置对象的位置。通常,mod上的h操作就足够了,但这太慢了。鉴于HashMap的内部属性,底层数组总是的桶数等于2^n,Sun的工程师可以使用h & (length-1)的概念,它确实一个bitwise AND,其数字由所有1组成,实际上只读取哈希的n最低位(与执行h mod 2^n相同,仅多更快。)

示例:

     hash h: 11 1110 1000  -- (1000 in decimal)
   length l: 10 0000 0000  -- ( 512 in decimal)
      (l-1): 01 1111 1111  -- ( 511 in decimal - it will always be all ONEs)
h AND (l-1): 01 1110 1000  -- ( 488 in decimal which is a result of 1000 mod 512)

答案 1 :(得分:31)

它不计算哈希,它正在计算

表达式h & (length-1)使用ANDh上执行逐位length-1,这类似于位掩码,仅返回低位h,从而制作h % length的超快变体。

答案 2 :(得分:2)

正在计算将存储条目(键值对)的哈希映射的桶。存储桶ID为hashvalue/buckets length

哈希映射由桶组成;对象将根据存储桶ID放置在这些存储桶中。

任何数量的对象实际上都可以根据hash code / buckets length值落入同一个存储桶中。这被称为“碰撞”。

如果许多对象属于同一个存储桶,则会调用其equals()方法来消除歧义。

碰撞次数与铲斗的长度间接成比例。

答案 3 :(得分:2)

上面的答案很好,但我想解释更多为什么Java可以使用indexFor创建索引

例如,我有一个类似的HashMap(此测试是在Java7上进行的,我看到Java8对HashMap进行了很多更改,但我认为这种逻辑仍然非常好)

// Default length of "budget" (table.length) after create is 16 (HashMap#DEFAULT_INITIAL_CAPACITY)
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("A",1); // hash("A")=69, indexFor(hash,table.length)=69&(16-1) = 5
hashMap.put("B",2); // hash("B")=70, indexFor(hash,table.length)=70&(16-1) = 6
hashMap.put("P",3); // hash("P")=85, indexFor(hash,table.length)=85&(16-1) = 5
hashMap.put("A",4); // hash("A")=69, indexFor(hash,table.length)=69&(16-1) = 5
hashMap.put("r", 4);// hash("r")=117, indexFor(hash,table.length)=117&(16-1) = 5

您可以看到键为"A"的条目的索引和键为"P"的对象和键为"r"的对象具有相同的索引( = 5 )。这是我执行上面的代码

之后的调试结果

enter image description here

图像中的表在这里

public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
    transient HashMap.Entry<K, V>[] table;
    ...
}

=>我明白了
如果索引不同,则新条目将添加到表中
如果索引为相同,并且hash相同,则新值将更新
如果索引为相同,并且hash不同,则新条目将指向旧条目(例如LinkedList)。然后,您知道为什么Map.Entry具有字段next

static class Entry<K, V> implements java.util.Map.Entry<K, V> {
        ...
        HashMap.Entry<K, V> next;
}

您可以通过阅读HashMap中的代码来再次进行验证。

像现在一样,您可以认为HashMap 无需更改大小(16),因为indexFor()始终返回值<= 15,但它不返回正确。
如果您查看HashMap代码

 if (this.size >= this.threshold ...) {
      this.resize(2 * this.table.length);

HashMap将在size> = threadhold

时调整表的大小(表的两倍长度)。

什么是threadholdthreadhold的计算公式如下

static final int DEFAULT_INITIAL_CAPACITY = 16;
static final float DEFAULT_LOAD_FACTOR = 0.75F;
...
this.threshold = (int)Math.min((float)capacity * this.loadFactor, 1.07374182E9F); // if capacity(table.length) = 16 => threadhold = 12

size是什么? size的计算方式如下。
当然,size在这里不是 table.length
每当您将新条目放入HashMapHashMap需要创建新条目时(请注意,当键相同时,HashMap不会创建新条目,它只会覆盖存在的新值条目),然后size++

void createEntry(int hash, K key, V value, int bucketIndex) {
    ...
    ++this.size;
}

希望对您有帮助

答案 4 :(得分:-1)

bucket_index =(i.hashCode()&& 0x7FFFFFFFF)%hashmap_size可以解决问题