前言
前面先做了红黑树的讲解平衡二叉树和红黑树-优快云博客,就是为了为了Map集合做铺垫,Map的几个实现集合底层都用到了红黑树。由于HashMap的东西有点多,HashTable和TreeMap下篇再说明。
一、HashMap
hashMap底层是哈希表+哈希桶(数组或红黑树)
Set篇的几张图会漂亮一点
1.几个重要的内部类
(1)节点类型
下面是两个节点内部类的源码:
链表节点类,最基本存储数据的节点类型。
树节点类,树化后的哈希桶的节点的类型
(2)利于遍历的内部类
// 定义一个内部类EntrySet,继承自AbstractSet,泛型为Map.Entry<K,V>
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
// 返回EntrySet中元素的数量,即HashMap的大小
public final int size() { return size; }
// 清空EntrySet中的所有元素,实际调用HashMap的clear方法清空整个Map
public final void clear() { HashMap.this.clear(); }
// 返回一个迭代器,用于遍历EntrySet中的所有元素
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
// 检查指定对象是否在EntrySet中存在
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry)) // 如果传入的对象不是Map.Entry类型,直接返回false
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o; // 将传入的对象转换为Map.Entry类型
Object key = e.getKey(); // 获取Map.Entry的key
Node<K,V> candidate = getNode(hash(key), key); // 通过hash和key获取候选节点
return candidate != null && candidate.equals(e); // 判断候选节点是否等于传入的Map.Entry对象
}
// 移除EntrySet中指定的对象,如果成功移除则返回true
public final boolean remove(Object o) {
if (o instanceof Map.Entry) { // 如果传入的对象是Map.Entry类型
Map.Entry<?,?> e = (Map.Entry<?,?>) o; // 将传入的对象转换为Map.Entry类型
Object key = e.getKey(); // 获取Map.Entry的key
Object value = e.getValue(); // 获取Map.Entry的value
return removeNode(hash(key), key, value, true, true) != null; // 通过hash、key和value尝试移除节点,如果成功返回true
}
return false; // 如果传入的对象不是Map.Entry类型,返回false
}
// 返回一个Spliterator,用于分割并行处理EntrySet中的元素
public final Spliterator<Map.Entry<K,V>> spliterator() {
return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
// 对EntrySet中的每个元素执行指定的操作
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
Node<K,V>[] tab; // 创建Node数组引用tab
if (action == null) // 如果传入的操作action为null,抛出NullPointerException异常
throw new NullPointerException();
if (size > 0 && (tab = table) != null) { // 如果EntrySet的大小大于0且table不为空
int mc = modCount; // 记录当前的修改次数
for (int i = 0; i < tab.length; ++i) { // 遍历table数组
for (Node<K,V> e = tab[i]; e != null; e = e.next) // 遍历table数组中的链表
action.accept(e); // 对链表中的每个元素执行action操作
}
if (modCount != mc) // 如果在执行过程中有其他线程修改了EntrySet
throw new ConcurrentModificationException(); // 抛出ConcurrentModificationException异常
}
}
}
EntrySet这个内部类的作用:方便遍历,取键值
A. k-v 最后是 HashMap$Node node =newNode(hash,key,value,null)
B. k-v为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素的类型Entry,而一个Entry。
对象就有k,v EntrySet<Entry<K,V>>即:transient Set<Map.Entry<K,V>> entrySet;
C. entrySet中,定义的类型是Map.Entry,但是实际上存放的还是HashMap$Node
这是因为 static class Node<K,V>implements Map.Entry<K,V>
D. 当把HashMap$Node对象存放到entrySet就方便我们的遍历,因为Map.Entry提供了重要方法
K getKey(); V getValue();
2.几个重要的变量和常量
哈希表即节点类型数组
默认初始容量16
默认的加载因子0.75
哈希表的最大容量 2^30
哈希桶树化的链表临界值8(达到这个值8还要哈希表达到最小树化容量64)
树化的另外一个条件哈希表达到最小树化容量64
3.几个重要的方法
添加元素时分三种情况:
1.数组位置为null
2.数组位置不为null,键不重复,挂在下面形成链表或者红黑树
3.数组位置不为null,键重复,元素覆盖
(1)put()方法
//参数一:键
//参数二:值
//返回值:被覆盖元素的值,如果没有覆盖,返回null
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
(2)hash()方法
//利用键计算出对应的哈希值,再把哈希值进行一些额外的处理
//简单理解:返回值就是返回键的哈希值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
(3)putVal()方法
//参数一:键的哈希值
//参数二:键
//参数三:值
//参数四:如果键重复了是否保留
// true,表示老元素的值保留,不会覆盖
// false,表示老元素的值不保留,会进行覆盖
//参数五:会传入一个空方法没有任何意义
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//定义一个局部变量,用来记录哈希表中数组的地址值。
Node<K,V>[] tab;
//临时的第三方变量,用来记录键值对对象的地址值
Node<K,V> p;
//表示当前数组的长度
int n;
//表示索引
int i;