第一:HashMap的概念
<span style="font-size:14px;">HashMap 基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。</span>
第二:什么是哈希表?
<span style="font-size:14px;"> 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度,这个映射函数叫做散列函数,存放记录的数组叫做散列表 PS:给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。</span>
<span style="font-size:14px;">
</span>
第三:什么是HashCode?
<span style="font-size:18px;">hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值。</span>
<span style="font-size:14px;">
</span>
java Hash Map 就是根据对象的hashCode的值,然后通过散列函数计算出存放的地址(也就是索引值)。但是如果遇到计算出来的值相同的话,这种情况叫做哈希冲突,ps:y = x % H ,如果 x = object.hashCode(); 则y 计算出来的值 就可以能相同,当然出现社种冲突的时候,我们一般可以有以下方法解决。
- 开放地址法
开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,...,k(k<=m-1)
其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。如果di值可能为1,2,3,...m-1,称线性探
测再散列。
如果di取1,则每次冲突之后,向后移动1个位置.如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,...k*k,-k*k(k<=m/2)
称二次探测再散列。如果di取值可能为伪随机数列。称伪随机探测再散列
- 再哈希法
当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。
比如上面第一次按照姓首字母进行哈希,如果产生冲突可以按照姓字母首字母第二位进行哈希,再冲突,第三位,直到不冲突为止
- 链地址法
将所有关键字为同义词的记录存储在同一线性链表中。
- 建立一个公共溢出区
假设哈希函数的值域为[0,m-1],则设向量HashTable[0..m-1]为基本表,另外设立存储空间向量OverTable[0..v]用以存储发生冲突的记录。
经过以上方法,基本可以解决掉hash算法冲突的问题。
第四:HashMap源码学习
HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Serializable
因此HashMap是实现Map接口的,同时 实现
Serializable接口,所以可以序列化。每个HashMap内部都有一个HashMapEntry 数级对象。
我们来看一下 HashMap的 put方法
@Override public V put(K key, V value) {
if (key == null) {
return putValueForNullKey(value);
}
int hash = secondaryHash(key.hashCode());
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
// No entry for (non-null) key is present; create one
modCount++;
if (size++ > threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
addNewEntry(key, value, hash, index);
return null;
}
如果Key值 为空,则加入
putValueForNullKey(value);
先计算hash 值 int hash = secondaryHash(key.hashCode()); 然后 根据 hash值计算index值 :
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
遍历数组查找是否有已经相同key存在,则更新原来的值。否则添加到数组中。
addNewEntry(key, value, hash, index)
我们再来看一下 get方法 :
public V get(Object key) {
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
return e == null ? null : e.value;
}
// Doug Lea's supplemental secondaryHash function (inlined)
int hash = key.hashCode();
hash ^= (hash >>> 20) ^ (hash >>> 12);
hash ^= (hash >>> 7) ^ (hash >>> 4);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
return e.value;
}
}
return null;
}
根本是根据hashCode计算index (
hash & (tab.length - 1).取相应的Entry,然后对比key值,返回value
。 。