1.前言
在我们面试过程中,经常会遇到要求说HashMap的底层实现,在JDK源码中,Oracle公司给出了我们HashMap的源码,通过阅读HashMap的源码,我们可以很清楚的知道HashMap是怎么实现的。下面我们开始阅读HashMap的源码吧。
2.关于HashMap的类的继承与实现
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
通过上面的代码我们知道了HashMap继承了AbstractMap这个类,实现了Map,Cloneable还有Serializable这个接口
3.HashMap类中的常量
private static final long serialVersionUID = 362498820763181265L;//序列化ID
/**
* 默认的初始容量一定是2的倍数,在这里是16
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 <<4;
/**
* 最大容量一定是2的倍数,而且小于2的30次方
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认的负载因子是0.75
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 当HashMap桶中的箱子的数量达到8的时候就会将
* 箱子的数据结构由链表转换成红黑树
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 由树转换成链表的阈值
* 在HashMap进行扩容的时候,当树中结点的个数小于6的时候有,由树转换成链表
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 桶可能被树化的最小容量,至少为4*TREEIFY_THRESHOLD,
* 避免扩容和树化阈值之间的冲突。
* 只有当表的容量达到64之后才能进行树化
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* 表,数组实现的结构
*/
transient Node<K,V>[] table;
/**
* 保存缓存的entrySet
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* HashMap中存在的键值对数量
*/
transient int size;
/**
* HashMap结构被修改的次数
*/
transient int modCount;
/**
* 下一个调整大小的值,容量*负载因子
*
* @serial
*/
int threshold;
/**
* Hash Table的负载因子
*
* @serial
*/
final float loadFactor;
4.HashMap中的数据结构
HashMap中在底层的实现主要是基于数组和链表实现的,当然还有红黑树。在处理冲突的时候应用的是链表和红黑树相结合的方法,具体的实现,请看下面的图片:
我们知道HashMap存储的数据都存放在Entry这个数据结构中。Entry是HashMap的存储结点。
具体的JDK源码如下:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//final类型
final K key; //final类型
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; }
/**
* 计算HashCode
* 是由key的hashCode和value的HashCode进行异或产生的。
*/
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
/**
* 结点比较,地址相同则相同,Key和Value相同则相同。
*/
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;
}
}
5.HashMap构造函数
HashMap一共4个构造函数,分别如下:
/**
* 默认构造函数,负载因子为默认的0.75
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* 构造函数初始化 初始容量和负载因子
*/
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;
this.threshold = tableSizeFor(initialCapacity);
}
/**
* 构造函数初始化 初始容量
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* 构造函数,对Map进行复制构造HashMap
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
//承接上面的方法
/**
* Implements Map.putAll and Map constructor.
*
* @param m the map
* @param evict false,当刚开始构造的时候
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
//判断原先的table是否为空,初始化参数
if (table == null) { // pre-size
//计算需要的表的长度
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
//设置阈值
threshold = tableSizeFor(t);
}
//超过阈值,进行扩容
else if (s > threshold)
resize();
//遍历Map插入Map的结点进入HashMap
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}