HashMap底层原理 (一)基本结构和hash算法
HashMap数据结构:
在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,而JDK1.8中,HashMap采用位桶+链表+红黑树实现。1.8中档列表中的元素超过hashMap的TREEIFY_THRESHOLD字段所确认的值时,链表转换成为红黑树。而当红黑树的元素小于UNTREEIFY_THRESHOLD字段所设置的值时,重新转换成为链表
注:
- TREEIFY_THRESHOLD默认值为8
- UNTREEIFY_THRESHOLD默认值为6
我们以JDK1.8来做为分析源码,那么hashMap的数据结构如图所示:
根据hashmap的源码内部定义类Node
//node的部分源码 hashMap的每个元素都是已Node对象来存储
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//通过hashmao扰乱过后的hash值
final K key;//传入的key
V value; //传入的值
Node<K,V> next; //下一个值
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
}
HashMap的构造函数
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//将传入的指定数组长度转换成最近高位的2的次方长度
this.threshold = tableSizeFor(initialCapacity);
}
/**
* initialCapacity指定的数组长度
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; //就只是设置扩容因子为默认的扩容因子
}
/** 将指定传入的数转换成最近的2的次方数据 */
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
通过上面的构造函数可以看出.hashMap的桶位长度一定为2的次方数据,至于为什么2的次方数,通过put中的计算桶位的公式(n - 1) & hash 来说明
//分别取n=6和n=7和n=8来看 hash假定为3 通过hash已4,3和2来举例 具体2进制运算过程这里不做演算
System.out.println("======== n=6 hash 分别为1,2,3 =========");
System.out.println((6 - 1) & 1);//结果为1
System.out.println((6 - 1) & 2);//结果为0
System.out.println((6 - 1) & 3);//结果为1
System.out.println("======== n=7 hash 分别为1,2,3 =========");
System.out.println((7 - 1) & 1);//结果为0
System.out.println((7 - 1) & 2);//结果为2
System.out.println((7 - 1) & 3);//结果为2
System.out.println("======== n=8 hash 分别为1,2,3 =========");
System.out.println((8 - 1) & 1);//结果为1
System.out.println((8 - 1) & 2);//结果为2
System.out.println((8 - 1) & 3);//结果为3
通过上列可以确认用2的次方数结合计算公式可以最小程度的避免hash碰撞。
HashMap的hash方法
在put方法中
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
先分析下hash方法
//hashmap源码
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//修改后 为了看的更清楚
static final int hash(Object key) {
int h;
h = key.hashCode();
int y = h>>>16;
int z = h ^ y
return (key == null) ? 0 : z;
}
//Object 中的hashCode
public native int hashCode();
逐句分析
h=key.hashCode();根据Object的hashCode方法 ,所有的重写或者至直接使用都是会返回一个int值,也就是会返回一个32位的2进制数,
y=h>>>16,将h右移16位 比如
h:1010 1001 0010 0010 1101 0101 0010 1101 >>>16后变为 0000 0000 0000 0000 1010 1001 0010 0010
z=h^y, 将高位数也加入进来一起做运算,进一步降低hash碰撞。