哈希表
哈希表也称散列表(Hash),Hash表是基于健值对(key - value)直接进行访问的数据结构。但是他的底层是基于数组的,通过特定的哈希函数把key映射到数组的某个下标来加快查找速度,对于哈希表来说,查找元素的复杂度是O(1)
我们来看一下HashMap里面的Hash函数是怎么实现的
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//计算位置i
i = (n - 1) & hash
通过hash找位置i的运算,查找位置i的规则是i = (n - 1) & hash。其中n就是数组的长度,由于n是2的幂次,那么n - 1的高位应该全部为0。如果hash值只用自身的hashcode的话,那么i只会和hash的低位做 & 操作。这样一来,index的值就只有低位参与运算,高位毫无存在感,从而会带来哈希冲突的风险。所以在计算key的哈希值的时候,用其自身hashCode值与其低16位做异或操作。这也就让高位参与到index的计算中来了,即降低了哈希冲突的风险又不会带来太大的性能问题。
冲突
使用hash函数计算,难免会出现地址冲突,即对于不同的key会计算出相同的数组位置,这时怎么解决呢
开放地址法
开发地址法中,若数据项不能直接存放在由哈希函数所计算出来的数组下标时,就要寻找其他的位置。其他位置的寻找有三种方法:线性探测、二次探测以及再哈希法。
链地址法
在哈希表每个单元中设置链表(即链地址法),某个数据项的关键字值还是像通常一样映射到哈希表的单元,而数据项本身插入到这个单元的链表中。其他同样映射到这个位置的数据项只需要加到链表中,不需要在原始的数组中寻找空位。HashMap中就是使用链地址法解决Hash冲突的
if ((e = p.next) == null) {
// 如果p的next为空,将新的value值添加至链表后面
p.next = newNode(hash, key, value, null);
HashMap(1.8)
构造函数,还是一些简单的初始化过程,不再赘述
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* Constructs a new <tt>HashMap</tt> with the same mappings as the
* specified <tt>Map</tt>. The <tt>HashMap</tt> is created with
* default load factor (0.75) and an initial capacity sufficient to
* hold the mappings in the specified <tt>Map</tt>.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) {
// pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t =