一、HashMap的类定义
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
1.继承了AbstractMap抽象类。实现了Map接口 (拥有Map的基本操作)
2.实现了Cloneable接口(表示可以进行拷贝,浅拷贝。即引用数据类型的修改会影响源数据)
3.实现了Serializable接口(表示可以进行本地化存储和读取)
二、HashMap的特点
1.允许key和value为null
2.非线程安全
3.不保证有序(如插入顺序)
三、HashMap的数据结构
JDK1.8对HashMap进行了比较大的优化,底层数据结构实现由之前的“数组+链表”改为“数组+单项链表+红黑树”。
当链表节点较少时仍然是以链表存在,当节点大于较多时(大于8)会转为红黑树。
关于红黑树与链表之间的转换条件:
链表越来越长时会考虑二叉树中的红黑树
当链表>8时,会转为红黑树;
当红黑树的节点个数 <= 6 时红黑树转为链表结构
死循环问题:
JDK1.7之前是头插法, 有弊端:性能不好,容易形成死循环
JDK1.8是尾查法 避免死循环
JDK 1.8 以前,Java 语言在并发情况下使用 HashMap 造成 Race Condition,从而导致死循环。程序经常占了 100% 的 CPU,查看堆栈,卡在了 “HashMap.get()” 这个方法上了,重启程序后问题消失。
JDK 1.8 以前,导致死循环的主要原因是扩容后,节点的顺序会反掉.如下图:扩容前节点 A 在节点 C 前面,而扩容后节点 C 在节点 A 前面。
四、HashMap的存储流程
4.1 数组元素 & 链表节点的 实现类
HashMap
中的数组元素 & 链表节点 采用Node
类 实现 ,与 JDK 1.7 的对比(Entry类),只是换了名字
源码如下
/**
* HashMap的内部类,实现了Map.Entry接口,本质是一个映射(键值对)
* 实现了getKey()、getValue()、equals()、hashCode()等方法
**/
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 V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
/**
* 作用:判断2个Entry是否相等,必须key和value都相等,才返回true
*/
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;
}
}
4.2 红黑树节点 实现类
HashMap
中的红黑树节点 采用TreeNode
类 实现
/**
* 红黑树节点 实现类:继承自LinkedHashMap.Entry<K,V>类
*/
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // 父节点
TreeNode<K,V> left; // 左子树
TreeNode<K,V> right; // 右子树
TreeNode<K,V> prev; // 删除辅助节点
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
// 返回当前节点的根节点
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
五、HashMap的使用
- 遍历
HashMap
- 注:对于遍历方式,推荐使用针对 key-value对(Entry)的方式:效率高
- 原因:
-
- 对于遍历keySet 、valueSet,实质上遍历了2次:第一次转为 iterator 迭代器遍历、第二次从 HashMap 中取出 key 的 value 操作(通过 key 值 hashCode 和 equals 索引)
-
- 对于遍历 entrySet ,实质遍历了1次,获取存储实体Entry(存储了key 和 value )
Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); for(Map.Entry<String, Integer> entry : entrySet){ System.out.print(entry.getKey()); System.out.println(entry.getValue()); }
-