前言
在日常工作中高频次使用HashMap
这个数据结构,且在上次的求职过程中也遇到了相关的面试题。今晚通过阅读源码了解该数据结构的内部设计。JDK版本1.8.0_231
正文
继承关系
HashMap继承了AbstractMap
类,实现了Map
Cloneable
Serializable
接口。
Map
接口包含一些常用的操作方法Cloneable
表示可以进行拷贝Serializable
表示实现了序列化
构造方法
HashMap类存在4个构造方法
1.HashMap(int initialCapacity, float loadFactor)
/**
* @param initialCapacity 初始容量
* @param loadFactor 加载因子 默认值:0.75
*/
public HashMap(int initialCapacity, float loadFactor) {
// 初始容量小于0抛异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
// 初始容量大于最大值 1 << 30,将最大值设置为容量
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 加载因子小于0或为非数字类型抛异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
// 通过tableSizeFor方法获取临界值
this.threshold = tableSizeFor(initialCapacity);
}
HashMap(int initialCapacity)
// 调用上面的构造函数获取临界值
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
在阿里巴巴《Java开发手册》中有如下建议:
HashMap()
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
HashMap(Map extends K, ? extends V> m)
// 将一个map合并
public HashMap(Map extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
添加元素
通过调用put()
方法添加数据,
// hash()方法用于获取key对应的hash值
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// 当key为null时返回0 此处可以看出hashmap是支持null值的;
// 当key不为null时获取key对应的hashCode并赋值给变量h, 变量h右移16位; 再进行异或运算并返回结果
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
调用putVal()
方法进行元素的添加
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
HashMap.Node[] tab;
HashMap.Node p;
int n, i;
// 首先判断table是否包含数据,没有数据则要进行第一次扩容
if ((tab = table) == null || (n = tab.length) == 0)
// 调用resize()方法进行初始化
n = (tab = resize()).length;
// 对hash码进行取模运算获取key所在的位置,若p == null 表示为新元素
if ((p = tab[i = (n - 1) & hash]) == null)
// 将新元素插入到数组的第i个位置
tab[i] = newNode(hash, key, value, null);
// 不是新元素
else {
HashMap.Node e;
K k;
// 通过equals()方法判断元素是否存在,若存在则替换
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如判断此时是否为树结构
else if (p instanceof TreeNode)
e = ((HashMap.TreeNode) p).putTreeVal(this, tab, hash, key, value);
else {
// 如果此时为链表结构,将该元素追加到最后面
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 如果此时链表长度大于等于8 将链表转换为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// 修改记录次数
++modCount;
// 如果超过临界值 再进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
单链表结构用于存储数据
static class Node implements Map.Entry {
// key所代表的hash值
final int hash;
// 键
final K key;
// 值
V value;
// 指向下一个元素 (单链表)
HashMap.Node next;
Node(int hash, K key, V value, HashMap.Node 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;
}
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
// 通过equals()防火判断两个key是否相等 [在之前的面试中,有面试官提到了这个问题]
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;
}
}
扩容方法resize()
final HashMap.Node[] resize() {
// table表述存储数据的数组,如果是第一次添加数据此时为空 反之是之前已添加的数据
HashMap.Node[] oldTab = table;
//
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// threshold表示临界值
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
} else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
} else if (oldThr > 0)
newCap = oldThr;
else {
// 若在使用hashmap时没有指定容量且是第一次添加数据,会在这里继续数组的初始化;此时容量为16,临界值为12
// 默认的容量 1 << 4 == 16
newCap = DEFAULT_INITIAL_CAPACITY;
// 临界值:16 * 0.75
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float) newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
// 将新的临界值赋值给threshold
threshold = newThr;
@SuppressWarnings({"rawtypes", "unchecked"})
// 创建一个新数组
HashMap.Node[] newTab = (HashMap.Node[]) new HashMap.Node[newCap];
table = newTab;
// 扩容之后做数据迁移
// 如果老数组中存在数据,则将里面的数据赋值给新数组,再将老数组中的值逐一设为null
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
HashMap.Node e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// e.next == null 表述没有出现hash冲突
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 出现了hash冲突
// 如果是红黑树
else if (e instanceof TreeNode)
((HashMap.TreeNode) e).split(this, newTab, j, oldCap);
else {
// 将原有数据做迁移
HashMap.Node loHead = null, loTail = null;
HashMap.Node hiHead = null, hiTail = null;
HashMap.Node next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
} else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
查找元素
使用get()
方法获取HashMap中数据
// 如果getNode()方法取回的值不存在返回null,返回获取value值
public V get(Object key) {
Node e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
getNode()
获取数据前先使用hash()
方法获取key对应的hash值
final HashMap.Node getNode(int hash, Object key) {
HashMap.Node[] tab;
HashMap.Node first, e;
int n;
K k;
// 将原数组中的数据赋值给tab 并判断tab数组的长度是否大于0 并且看能否在数组中获取key所在的位置且不为null 校验未通过返回Null
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
// 判断是否是hash冲突的元素
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 如果出现hash冲突会将元素添加到链表中,如果next!=null表示为链表结构 需要从链表中获取
if ((e = first.next) != null) {
// 当链表的长度达到8时 将链表转换为;此时判断是否是红黑树,并从其中获取数据
if (first instanceof TreeNode)
return ((HashMap.TreeNode) first).getTreeNode(hash, key);
// 遍历链表 并获取数据
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
总结
通过上面源码分析,可以看出HashMap是由数组,单链表和红黑树所组成。
- 添加元素:首先元素依次保存在数据中,如果此时发生hash冲突,则创建一个单链表在该hash位上,当单链表长度大于等于8时,将单链表转换为红黑树
- 查找元素:判断key对应的hash值是否存在于数组中,如果存在则从其中取出,如果元素next属性不为null,则依次判断树和链表中是否存在再取出,如果都不存在,返回null