散列是一种用于以常数平均时间执行插入、删除和查找的技术。HashMap是基于哈希表的Map接口实现,该实现提供了所有可选的映射操作,并允许使用 空值和空键。(HashMap 类大致等同于Hashtable,除了它是不同步的并且允许为空值。)这个类不能保证Map的顺序; 特别是,它不能保证顺序会随着时间的推移保持不变。
下面就开始我们的源码分析吧:
老规矩,先贴一张UML图:
①:HashMap直接继承自AbstractMap抽象类,在AbstractMap已经实现了Map接口中的所有方法,只有public abstract Set<Entry<K,V>> entrySet()
方法是抽象,由子类实现自己的entrySet()方法。在HashMap中的一个实例域也和此方法有关联。后面会分析。
②HashMap实现了Map接口,Map中不能使用Map作为key,但Map可以作为value,但是不建议这样做。下图是JDK1.8官方文档给出的解释。
③实现了Cloneable表明HashMap支持可克隆。
④实现了Serializable接口,表明HashMap支持序列化。
好啦,终于到了源码分析的部分了:
下面是HashMap存储结构图:
(该图来自美团技术团队,我分析HashMap的时候,也参考了这里的内容。)
老规矩,我们还是先分析HashMap中的实例域部分(也就是HashMap的属性),但是HashMap比较复杂,它的实例域部分的类型是HashMap中的一个内部类,而对应的内部类又实现了Map接口中声明的一个接口(Map.Entry<K,V>
),是不是听起来有点诡异(⊙o⊙)…,为了更好的学习HashMap,我们还是从最外层(也就是Map.Entry<K,V>
)开始看起吧。其实很简单的….
1:我们先看看HashMap中的哈希桶(Node<K,V>
),它是HashMap中的一个静态内部类,它实现了Map.Entry<K,V>
接口,该接口是Map接口内声明的一个内部接口,它表示Map中的一个实体(也就是一个键值对(key-value对))
2:我们先看看Map.EnTRY<K,V>
这个接口的源码:
从代码中可以看出,我们可以直接通过该Entry对象获取相应的键和值。它提供了很多有用的方法,代码很简单。不过最后提供了JDK1.8之后新增的方法,主要基于Stream做操作,目前我也仅仅停留在会用阶段,源码分析不来(⊙o⊙)…(后面关于Stream的部分的分析直接跳过(艹皿艹 ))
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
}
3:我们先看一下HashMap中的第一个字段:transient Node<K,V>[] table
,它就是我们的哈系桶数组。接下来给出Node<K,V>
的源码。
4:Node<K,V>
(哈系桶),它实现了Map.Entry<K,V>
接口,下面给出源码:
下面我们逐个介绍一下它的字段的作用:
hash:主要用来定位当前Node在数组位置的索引。
key:键值对中的键。
value:键值对中的值。
Node<K,V>
next指代链表中的下一个Node。
部分解释写在 了代码里面。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
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;
}
//该方法实现Map.Entry<K,V>接口。
public final K getKey() { return key; }
//该方法实现Map.Entry<K,V>接口。
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
//该方法实现Map.Entry<K,V>接口。设置新值,并返回旧值。