HashMap概述
本文中的HashMap使用java8版本的。
hash表知识复习
简单来说,哈希表也称作散列表,是一种数据结构。它是在对关键字做某种运算后直接确定其元素相应的位置(地址)。所以,哈希法又称为散列地址编码法。用散列法存储的表叫作散列表。
假设R是长度为n的表,Ri(1≤i≤n)为表中某一元素,Ki是其关键字,则在关键字Ki和表中元素Ri的地址(位置)之间存在着一定的函数关系,即: LOC(Ri)=h(Ki)
散列函数比较灵活,有多种选取的方式。如果多个关键字通过散列函数映射到了同一个地址上就叫做散列冲突。
解决散列冲突的方法有两种:
1.开放地址法:
开放地址法又可以分为线性探测和再散列等
2.拉链法
就是将映射结果为相同地址的关键字用链表保存,放在哪个地址当中。
HashMap整体架构
在jdk1.7中HashMap采用数组+链表方式,在jdk1.8中,改为了采用数组+链表/红黑树的数据结构。当hash数组的每个位置的链表个数超过某个阈值的时候就会采用红黑树来保存。这样做的原因是,如果只采用数组,当链表过长的时候,会降低查询效率。比如数组的大小为1的时候,还是采用链表,那么查找算法会退化为线性查找。
这个图像形象描述了hashmap的结构。
HashMap原理解析
重要字段
/**
* table数组初始化容量,必须为2的整数幂
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
*hashmap的最大容量,必须为2的整数幂次方,而且不能大于1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 负载因子
* 当容器中<key, value>的数量超过capacity * DEFAULT_LOAD_FACTOR时,需要扩容
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 链表转红黑树阈值
* 当某个hash值下<key, value>用链表存储,并且链表长度不小于该值,就需要转成红黑树
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 红黑树转链表阈值
* 当某个hash值下<key, value>是用红黑树存储,并且树中的节点数小于该值,就需要转成链表
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 最小树形化容量阈值
* 当哈希表中的容量 > 该值时,才允许将链表转成红黑树操作,否则直接扩容。
* 为了避免进行扩容、链表转红黑树选择的冲突,并且这个值不能小于 4 * TREEIFY_THRESHOLD(链表转红黑树阈值)
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* table数组,也称hash桶数组
*/
transient Node<K,V>[] table;
/**
* entrySet属性,把<K,V>存放到Set容器中(一般hashmap的遍历用此属性)
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* 容器key-value数量(注意与容器的容量(容器可存放的数量)不同)
*/
transient int size;
/**
* 容器进行结构性调整(增加或者删除键值对等操作,不包括修改value值)的次数
*/
transient int modCount;
/**
* 容器中能容纳的key-value极限,capacity * loadFactor,超过就需要扩容
*/
int threshold;
/**
* 负载因子,默认是0.75(前面类的静态属性已经定义过了)
*/
final float loadFactor;
注意:这里的threshold是使用的capacity * loadFactor,而不是用的数组的长度去乘的。size代表的是当前map中键值对的个数。
构造方法
public HashMap(int initialCapacity, float loadFactor) {
// 检查initialCapacity的合法性
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
// 检查initialCapacity是否超过了可设置的最大容量(类静态属性)
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 检查loadFactor负载因子的合法性
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//初始化threshold稍微复杂一点,tableSizeFor方法解析见本博客尾端
this.threshold = tableSizeFor(initialCapacity);
}
/**
* @param initialCapacity 初始化容量大小
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
//默认负载因子为0.75
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* 只设置负载因子为0.75,其它值全部默认
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
/**
* 复制构造函数,将另外一个map初始化构造
*
* @param m 其它map容器
* @throws NullPointerException
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
//将m容器中的所有entry放入新建的容器对象中
putMapEntries(m, false);
}
map的构造函数中并未对table进行初始化。table的初始化操作是在第一次 put的时候发生的。
get操作
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//首先判断table不为空并且保存该key的数组位置不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//找到了
if