1. 简述
hash表即散列表,实际是应对直接寻址技术(数组)局限性而设计的一种数据结构。通过hash函数 h(K) 将数据映射到一个较小的地址范围。
通过hash函数映射的地址不可避免会发生冲突,即哈希碰撞。通常可以通过链接法和开放寻址法解决。
链接法:同一位置元素放在一个链表中,一次查找时间复杂度O(1+链表长度)
开放寻址:发生冲突时查找下一个空槽放置,使用辅助探查函数h'(k)探查槽,如果冲突再使用探查函数探查
- 线性探查:h(k,i)= ( h'(k) + i ) mod m
- 二次探查:h(k,i) = ( h'(k) + ai + bi^2 ) mod m
- 双重散列:h(k,i) = ( h'(k) + ih''(k) ) mod m
除法散列法 h(k) = k mod m,k为关键字,m为散列表大小。m不取2的幂且尽量远离。
乘法散列法 h(k) = m(kA mod 1),m一般取2的幂,0<A<1,A=(√5 - 1)/2=0.618...比较理想。
全域散列法,从一组精心设计的散列函数中随机选择。
2. java中的应用
a. HashSet、LinkedHashSet、HashMap、LinkedHashMap都是使用HashMap中的散列函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
//(对象的hash code) 异或 (对象的hash code无符号右移16位)
}
数组下标i=(n-1) & hash,如下HashMap.putVal()方法
使用链接法解决冲突
b. ConcurrentHashMap是线程安全的,散列函数如下
static final int spread(int h) {
//h=key.hashCode()
//static final int HASH_BITS = 0x7fffffff;
return (h ^ (h >>> 16)) & HASH_BITS;
}
数组下标i=(n-1) & hash,如下ConcurrentHashMap.putVal()方法
c. HashTable同样是线程安全的
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
Entry<K,V> entry = (Entry<K,V>)tab[index];
//其中tab.length是数组长度,会有自动扩容逻辑
//扩容逻辑在HashTable.rehash()方法中,如下语句即原容量*2+1
int newCapacity = (oldCapacity << 1) + 1;
其中entry是HashTable.Entry<K,V>内部类对象,具有链表结构,解决hash冲突