1、HashMap简介
HashMap
主要用来存放键值对,它基于哈希表的Map
接口实现的。
哈希表:数据结构的物理存储结构只有两种,顺序存储结构
和链式存储结构
。数组就是顺序存储结构,根据下标查找,可以一次定位到元素。哈希表
的主干就是数组
,它利用了这个特性。比如我们要新增或者查找某个元素,把这个元素的关键字通过指定的函数(hash算法)映射
到数组上的某个位置,通过数组下标一次定位就可以完成操作。
那么如果两个不同的关键字通过函数得到相同的值(在数组的存储地址-下标),这就是哈希冲突
。
JDK1.8
较JDK1.7
在处理哈希冲突
上的逻辑有很大的变化。
2、HashMap底层数据结构分析
(1)JDK1.7
及之前的底层数据结构
在JDK1.8
之前,HashMap
底层是数组和链表
结合在一起使用(链表散列
)。
具体实现:HashMap
通过key
的hashCode()
方法得到的值再通过扰动函数
计算得出hash
值,通过(n-1) & hash
得到当前元素在数组中的存放位置(n
是数组的长度),如果当前位置已经存在元素的话,则判断当前元素和要存入的元素的hash
值和key
值是否相同,若相同,则直接覆盖,若不相同,则通过拉链法
来解决。
扰动函数:就是HashMap
中的hash
函数,使用hash
函数的目的就是防止一些key的hashCode方法实现的比较差,数值不够分散。使用hash
函数后可以减少哈希碰撞。
static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是hashcode
// ^ :按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
拉链法:就是将数组和链表结合,创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中。
(2)JDK1.8
底层数据结构
当链表长度大于阈值(默认为8)之后,会调用treeifyBin()
方法,这个方法会根据HashMap
数组来决定是否转为红黑树,只有当数组长度大于或等于64时,才会执行转换红黑树操作,减少搜索时间。否则,执行resize()
方法对链表扩容。
HashMap
类的属性
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
// 序列号
private static final long serialVersionUID = 362498820763181265L;
// 默认的初始容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认的填充因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 当桶(bucket)上的结点数大于这个值时会转成红黑树
static final int TREEIFY_THRESHOLD = 8;
// 当桶(bucket)上的结点数小于这个值时树转链表
static final int UNTREEIFY_THRESHOLD = 6;
// 桶中结构转化为红黑树对应的table的最小大小
static final int MIN_TREEIFY_CAPACITY = 64;
// 存储元素的数组,总是2的幂次倍
transient Node<k,v>[] table;
// 存放具体元素的集
transient Set<map.entry<k,v>> entrySet;
// 存放元素的个数,注意这个不等于数组的长度。
transient int size;
// 每次扩容和更改map结构的计数器
transient int modCount;
// 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
int threshold;
// 加载因子
final float loadFactor;
}