我正在研究Java中HashMap
的实现,并且一度陷入困境
如何计算indexFor
函数?
static int indexFor(int h, int length) {
return h & (length-1);
}
由于
答案 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)
使用AND
在h
上执行逐位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 )。这是我执行上面的代码
图像中的表在这里
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
什么是threadhold
? threadhold
的计算公式如下
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
。
每当您将新条目放入HashMap
且HashMap
需要创建新条目时(请注意,当键相同时,HashMap
不会创建新条目,它只会覆盖存在的新值条目),然后size++
void createEntry(int hash, K key, V value, int bucketIndex) {
...
++this.size;
}
希望对您有帮助
答案 4 :(得分:-1)
bucket_index =(i.hashCode()&& 0x7FFFFFFFF)%hashmap_size可以解决问题