HashMap
一、HashMap的结构
首先我们有必要了解一下HashMap的结构:
在JDK1.7及之前的版本中,HashMap的结构是由数组(,这个数组的元素也称为桶(bucket))+ 单项链表
而在JDK1.8及之后的版本中,HashMap的结构则由 数组+ 单项链表/红黑树
1.1 hash表
了解hash表这个数据结构,先了解一下hash函数
hash函数
hash函数就是根据key的值计算出应该存储地址的位置,而哈希表是基于哈希函数建立的一种查找表
在HashMap的hash函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
从hash()方法中,可以看出 HashMap是接收key为null的情况:
①key为null 返回hash值为0
②key不为null 返回 key的hashcode的高16位
亦或
低16; (这种方式是为了解决 哈希冲突)
- 这样就完了吗?不! key!=null 得到的 并非是真正的hash值
在HashMap源码中,但凡涉及到元素的方法,都要对元素的Key进行hash()操作,接下来是获取Key的真正的Hash值:
获取hash值的方式有很多:像
平方取中法
、折叠法
、数字分析法
、相除取模法
。其中
相除取模
是用的最多的,HashMap也用的正是该方法H(key)=hash(Key.hashcode())% p (p为数组的长度)
//在HashMap中 哈希取模的 操作
H(key) = (n - 1) & hash; //只有n为2的次方 才能有 (n - 1) & hash等价于 hash % n //这样做的好处,采用位运算,提升效率
hash冲突
不同元素获取到的hash值可能相等
一般解决hash冲突的方式:
- 开放地址法
- 链地址法 (HashMap使用的方法)
- 再哈希法
1.2HashMap 通过 链地址法解决Hash冲突
首先我们先了解一下HashMap元素的结构
单项链表的节点Node
static class Node<K,V> implements Map.Entry<K,V> {
//实现了Map.Entry
final int hash;
final K key;
V value;
Node<K,V> next; //单向链表
public final int hashCode() {
//节点的hashcode:Key的hashcode 与 value的hashcode 进行异或
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
}
红黑树的节点Node
关于红黑树:可参考(52条消息) B树和红黑树_Beau想躺平的博客-CSDN博客
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
//有一个将单项链表 树化为 红黑树的方法
final void treeify(Node<K,V>[] tab){
...}
//也有一个将红黑树 还原为 单项链表的方法
final Node<K,V> untreeify(HashMap<K,V> map) {
...}
//红黑树 增删改查的 操作
}
二、HashMap操作
2.1 HashMap的 操作常量
public class HashMap<K,V> ... {
//默认初始化数组的长度为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//数组最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//临界值
static final int TREEIFY_THRESHOLD = 8;
//桶由单项链表变为红黑树的条件1:数组长度的临界值,超过64,就可以变为红黑树
static final int MIN_TREEIFY_CAPACITY = 64;
}
2.2HashMap的属性
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
transient int size;
transient int modCount;
final float loadFactor;
2.2创建HashMap
- 初始化过程,不会对数组容量直接确定,只能对 临界值或负载因子进行限定
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY; //判断超过最大值 2^30
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);//临界值要变为 2的次方(便于进行哈希取模)
}
public HashMap(