HashMap源码分析
- 一、哈希(hash)
- 二、HashMap原理
- 1、Map继承体系图
- 2、HashMap存储结构
- 3、Node数据结构
- 4、PUT数据原理分析
- 5、为什么引入红黑树(解决hash碰撞)
- 三、HashMap源码
- 1、核心属性
- 2、构造方法
- 3、PUT方法
一、哈希(hash)
1、简介
hash也称散列,哈希,基本原理就是把任意长度的输入,通过hash算法变成固定长度的输出,这个映射的规则就是对应的hash算法,而原始数据映射后的二进制串就是哈希值
2、hash的特点
1、从hash值不可以反向推导出原始的数据
2、输入数据的微小变化会得到完全不同的hash值,相同的数据会得到相同的值
3、hash算法的指向效率很高效,长的文本也能快速计算出hash值
4、hash算法的冲突概率要小
3、缺点
由于hash的原理是将输入空间的值映射成hash空间内,而hash空间远小于输入的空间
根据抽屉原理,一定会存在不同的输入被映射成相同的输出的情况
抽屉原理:把多于n个的物体放到n个抽屉里,则至少有一个抽屉里的东西不少于两件
二、HashMap原理
1、Map继承体系图
2、HashMap存储结构
从结构实现来讲,HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下如所示:
3、Node数据结构
Node是HashMap的一个内部类,实现了Map.Entry接口,本质是就是一个映射(键值对)。上图中的每个黑色圆点就是一个Node对象。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //用来定位数组索引位置
final K key;
V value;
Node<K,V> next; //链表的下一个node
Node(int hash, K key, V value, Node<K,V> next) { ... }
public final K getKey(){ ... }
public final V getValue() { ... }
public final String toString() { ... }
public final int hashCode() { ... }
public final V setValue(V newValue) { ... }
public final boolean equals(Object o) { ... }
}
4、PUT数据原理分析
1、map.put("鸡汤","来咯");
2、获取"鸡汤"字符串的hash值
3、经过hash扰动函数,使此hash值更散列
4、构造出node对象
hash->1122
key->"鸡汤"
value->"来咯"
Next->null
5、路由算法找出node应当存放在数组的位置
(table.length-1) & node.hash
(16-1) & 1122
B0000 0000 1111 & B0100 0110 0010 ->B0010 = 2
5、为什么引入红黑树(解决hash碰撞)
字符串经hash后得出的二进制后四位相同,经过路由算法会算出同样的值,会导致链表无限长导致效率降低(JDK1.8 红黑树解决)
三、HashMap源码
1、核心属性
//缺省table大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//table最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;
//缺省负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/*
* 链表树化需要满足
* 1、哈希表中的所有元素超过64
* 2、当前链表长度大于树化阈值
*/
//树化阈值(大于该值时允许链表转红黑树)
static final int TREEIFY_THRESHOLD = 8;
//树降级阈值(小于该值时允许红黑树转链表)
static final int UNTREEIFY_THRESHOLD = 6;
//最小树容量(哈希表中的所有元素大于该值是允许树化)
static final int MIN_TREEIFY_CAPACITY = 64;
//哈希表什么时候初始化?
transient Node<K,V>[] table;
//当前哈希表元素个数
transient int size;
//当前哈希表结构修改次数
transient int modCount;
//扩容阈值,当哈希表元素超过该阈值触发扩容
int threshold; //threshold = capacity * loadFactor
//负载因子
final float loadFactor;
2、构造方法
public HashMap(int initialCapacity, float loadFactor) {
//capacity必须大于0且小于MAX_CAP
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//loadFactor必须大于0
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//接下部分代码块
this.threshold = tableSizeFor(initialCapacity);
}
/**
* 返回一个大于等于当前cap的数字,且这个数字一定是2的次方数
*
* cap = 10
* n = 10 - 1 = 9
* 右移:二进制右移几位就是左边加几个0,右边边去掉几位
* 0b1001 | 0b0100 => 0b1101
* 0b1101 | 0b0011 => 0b1111
* 0b1111 | 0b0000 => 0b1111
* 。。。
* 0b1111 = 15
* return 16
*/
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;
}
3、PUT方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}