简介
HashMap存放的键值对,是常用的Java集合之一。
JDK1.8之前HashMap是由数组+链表组成,主体是数组,链表用来解决哈希冲突。
JDK1.8之后,当链表长度大于8链表会转为红黑树(如果数组长度小于64,则会优先扩容数组)。
结构
基本结构
HashMap的结构如上图所示。
HashMap通过对key的hashcode扰动后得到hash值,然后(数组长度-1)&hash值得到数组的下标,
若该位置已经有数据了,就判断两个key和hash值是否相同,若相同直接覆盖,否则通过拉链法解决
hash冲突。关于扰动函数和拉链法下面会讲到。
扰动函数和拉链法
扰动函数
static final int hash(Object key) {
int h;
//key.hashCode():获得key的hashCode
//^:异或运算(二进制位,相同为0,不同为1)
//>>>:右移
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
以上是JDK1.8 HashMap扰动函数的源码。
扰动函数是为了减少哈希碰撞。打个比方,数组下标的算法在上面已经说明了,如果数组的长度为
15,二进制则是0000000000001111,与hashCode进行与运算,高位全部归零,只取最后四位,
碰撞会非常严重,如果散列设计本来就不好,情况则会更加糟糕。经过扰动处理后,hashCode的
高位和低位进行了一个混合,增大了低位的随机性。最后和数组长度取模得到下标。
拉链法
拉链法上面的图已经画出来了,就是发生hash碰撞时,将发生碰撞的数据追加到链表后面。
流程及属性
基本属性
//数组,长度一直为2的幂次倍
transient Node<K,V>[] table;
//具体的数据
transient Set<Map.Entry<K,V>> entrySet;
//数据个数
transient int size;
//计数器。每次更改或扩容map
transient int modCount;
//临界值(加载因子*容量),实际大小超过临界值会进行扩容
int threshold;
//加载因子,默认0.75,越小数组存放的数据小,越大数组存放的数据多
final float loadFactor;
上面是HashMap的属性。
static class Node<K,V> implements Map.Entry<K,V> {
//hash值,hash冲突时用来比对
final int hash;
//键
final K key;
//值
V value;
//下一个节点指针
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
上面是node节点属性。
流程图
数据插入的流程大致如上图所示。
结束
这里讲到了HashMap一些常见的属性和流程,接下来会讲其他几种集合。如果发现错误或者需要改进的地方,欢迎大家及时指正。