首先JDK1.8对HashMap的实现做出了改变,主要是以前是利用数组加链表的方式存储,而1.8之后对链表设置了阙值,到达这个阙值就改用了红黑树,由于当链表的长度特别长的时候,查询效率将直线下降,查询的时间复杂度为 O(n)。
public class HashMap extends AbstractMap
implements Map, Cloneable, Serializable
继承:
AbstractMap:实现了大部分Map接口的方法
实现:
Cloneable:实现其的clone方法,支持深拷贝一个对象
Map:本人一开始在这里有些许疑惑,因为继承的AbstractMap类是对Map接口的实现,而在此又进行一次实现。通过查找相关知识,得到比较有权威性的答案是这是由于类库设计者的拼写错误。
Serializable:一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化
private static final long serialVersionUID = 362498820763181265L;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* The bin count threshold for untreeifying a (split) bin during a
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
* most 6 to mesh with shrinkage detection under removal.
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
* between resizing and treeification thresholds.
*/
static final int MIN_TREEIFY_CAPACITY = 64;
说明
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;
}
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;
}
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;
}
}
**说明:**每个节点包含hash值,key,value以及指向下一个Node的next。以及一些方法,比如构造方法,get和set方法,以及toString()同时,声明了Node节点的equals()和hashCode()方法。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
HashMap最常用的方法之一,put方法,将输入的键值对,根据key计算hash存入到散列表中相应的位置下的链表或树中去。
可以发现,主要调用putval(),传入参数hash(key)调用了hash();key,value,false和true;
hash( Object key)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
**说明:**根据传入的key,若key为null则返回0,否则返回key类型自定义的hashCode()获取hash值,并与h右移16位后的二进制数进行异或求值并返回作为哈希值。
putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //新建一个节点数组tab,以及节点p,int n,i
if ((tab = table) == null || (n = tab.length) == 0) //赋值为transient修饰的table全局变量,看时候构建了散列表
n = (tab = resize()).length; //若没有,调用resize()构造新的散列表
if ((p = tab[i = (n - 1) & hash]) == null) //(n - 1) & hash计算在散列表中的下标,若不存在则直接新建一个节点存入其中
tab[i] = newNode(hash, key, value, null); //newNode方法中调用Node的构造方法创建新节点
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //若tab相应下标存储的key值相同,则覆盖
e = p;
else if (p instanceof TreeNode) //判断p是否是个树节点,则调用putTreeVal添加一个树节点
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else { //都不是,则在tab的相应位置的链表上插入一个新节点
for (int binCount = 0; ; ++binCount) {
//没有相同的key,则在链尾插入一个节点,若此结点此结点插入后,达到树状阈值则调用treeifyBin()【将链表转变为红黑树结构】
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果,有相同key则,退出循环,而此时e就是与添加键值对有着相同key的节点
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e; //接着遍历,相当于p = p.next
}
}
//如果e不为空,说明有相同key值的节点存在,根据onlyIfAbsent参数决定是否改变value值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e); //响应方法,在hashMap中是空的方法体
return oldValue;
}
}
++modCount; //此参数表示HashMap在结构上被修改的次数
if (++size > threshold) //size:此映射中包含的键值映射数。threshold:容器*加载因子及扩容的阈值
resize();
afterNodeInsertion(evict); //响应方法,在hashMap中是空的方法体
return null;
}
参数介绍
**说明:**本方法主要负责具体的添加新节点操作,根据不同条件决定是直接添加,链表添加节点还是构建红黑树并添加节点Node的三种情况。并且最后也做了对于扩容的判断。
HashMap转换红黑树的前提
将链表转换为红黑树之前会判断,如果当前数组的长度小于64,那么会选择先进行数组扩容,而不是转换为红黑树。
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
get方法主要调用getNode实现
getNode()
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)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;
}
说明: 判断散列表是否不为null,若不为空则根据hash值判断第一个节点 ----->判断子节点是否为树节点------>调用getTreeNode(hash,key),否则遍历链表找到相应的节点并返回。
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
remove(),调用removeNode实现主要功能
removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable)
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//获取key相对于的节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
//获取的节点不为null,且不要求value相同及matchValue=false,若为树节点则调用removeTreeNode()并需要判断是否达到转换链表阈值,否则进行链表的删除
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
说明:在remove时需要注意的点,主要是关于找到的节点类型不同,进行不同的删除操作,对于树节点还有判断是否达到最低树结构的节点树,否则要将树结构转变成链表结构。