HashMap是java中的一种数据类型,它实现了Hash表这种数据结构。这是我们首先要知道的。
首先要了解的:
1:HashMap是基于数组来实现Hash表的,数组就像是内存空间,它的每一个index就是一个内存的地址,即数组的下标就好比代表了一个内存地址。
2:HashMap的每一个记录就是一个Entry<K,V对象,数组中存储的就是这些对象。
3:HashMap的哈希函数就是=计算出hashcode+计算出数组下标index。
4:HasmMap使用链地址法来解决冲突,每一个Entry<>对象都有一个引用next来指向下一个Entry<>。
HashMap相比HashTable来说,它允许使用NULL值,并且非同步,除此之外,大体相同。
来看一下常见的HashMap:
下面来看一下HashMap的存储(put)源码,方便我们更好的理解它的实现方式。
public V put(K key, V value){
if (table == EMPTY_TABLE) {
inflateTable(threshold);//table会被初始化为长度16,且hashSeed会被赋值;
}
if(key == null){
return putForNullKey(value); //如果为null,就调用putForNullKey方法处理
}
// a). 计算key的hashCode,下面详细说
int hash = hash(key);
// b). 根据hashCode计算index
int i = indexFor(hash, table.length);
// c). 做覆盖,遍历index位置的Entry链表,*不是解决*冲突
for(Entry<K,V> e=table[i];e!=null;e=e.next){
Object k;
if(e.hash == hash &&((k = e.key) == key || key.equals(k)))
// hashCode和equals都相等则表明:本次put是覆盖操作,下面return了被覆盖的老value
V oldvalue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
//如果i索引处的Entry为null,则表明该处还没有Entry
modCount++;
// d). 添加Entry,并解决冲突
// 如果需要增加table长度(size>threshold)就乘2增加,并重新计算每个元素在新table中的位置和转移
addEntry(hash, key, value, i);
return null;//增加成功最后返回null
}
}
/** a). 为了防止低质量的hash函数,HashMap在这里会重新计算一遍key的hashCode **/
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {//字符串会被特殊处理,返回32bit的整数(就是int)
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();//将key的hashCode与h按位异或,最后赋值给h
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
/**
* b). 计算此hashCode该被放入table的哪个index
*/
static int indexFor(int h, int length) {
return h & (length-1);//与table的length - 1按位与,就能保证返回结果在0-length-1内
}
通过按位与保证索引值总是在位于Table的数组内
/**
* 解决冲突:链地址法
* d). addEntry(hash, key, value, i)最终是调用了此函数
*/
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];// table是一个普通数组,每一个数组都有固定的长度,它的长度就是HashMap的容量
// put添加新元素是直接new Entry放在链头,如果有老的(有冲突)则将next设置为老的,如果没有正好设置next为null
table[bucketIndex] = new Entry<>(hash, key, value, e);// 在构造函数中,e代表next
size++;
}
综上所述:当HashMap添加key-value时,先由key的hashCode返回值决定该key-value的存储位置,即上诉图片的最前面那一列(0,1,2,3,4......),当两个Entry的hashCode返回值相同时,将由key通过equals比较来判断是采取覆盖行为(返回true)还是采取产生Entry链行为(返回false)。
所以,如果要保证使用HashMap高效率搞性能,应该从三方面保证:
1:提供高效的hash算法,保证hash值尽量不同
2:提供高效的算法,保证Hash值到内存地址(数组索引)的映射速度。
3:根据内存地址(数组下标)可以迅速找到对应的值。(尽量不要产生Entry链)。