开局一张图,内容全靠....
![官方文档HashMap的结构体系图](https://img-blog.csdnimg.cn/20200803145744638.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMzQxNjU2,size_16,color_FFFFFF,t_70)
HashMap的概述
HashMap是java.util包下的一个集合,HashMap继承了AbstractMap并实现了Map、CloneAble和Serilizable接口。特点是允许使用null键值对、是无序的且线程不安全。底层结构实现是数组+链表+红黑树(jdk1.8+)
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
......
}
HashMap常见问题
1、HashMap和Hashtable的区别
区别一:
官方文档:HashMap是基于哈希表Map的实现,这个实现提供了所有可选的映射操作,并允许空值和空键。HashMap类大致相当于Hashtable,只是它是线程不同步的,并且允许空值。这个类不能保证映射的顺序,特别是,它不能保证顺序随时间保持不变。
Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.
区别二:
根据HashMap源码来看一些默认属性
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大容量,如果某个具有参数的构造函数隐式指定了更高的值,则使用该值。必须是2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//在构造函数中未指定时使用的加载因子。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//阈值,超过此阈值转为用红黑树结构存储
static final int TREEIFY_THRESHOLD = 8;
//阈值,低于此阈值转为用链地址的方法存储
static final int UNTREEIFY_THRESHOLD = 6;
//最小表容量
static final int MIN_TREEIFY_CAPACITY = 64;
DEFAULT_INITIAL_CAPACITY:初始长度是1*2的四次方,也就是16,对于HashTable,根据源码可知是11
官方文档:Constructs a new, empty hashtable with a default initial capacity (11)
区别三:计算Hash值的方式
//hashMap
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//hashtable
int hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
2、jdk1.8中HashMap的存储是如何实现的?
上源码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判断哈希表是否初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;//执行初始化操作
// 使用hash与数组长度减一的值进行异或得到分散的数组下标,预示着按照计算现在的
// key会存放到这个位置上,如果这个位置上没有值,那么直接新建k-v节点存放
// 其中长度n是一个2的幂次数
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 如果走到else这一步,说明key索引到的数组位置上已经存在内容,即出现了碰撞
// 这个时候需要更为复杂处理碰撞的方式来处理,如链表和树
else {//有节点
Node<K,V> e; K k;
// 其中p已经在上面通过计算索引找到了,即发生碰撞那一个节点
// 比较,如果该节点的hash和当前的hash相等,而且key也相等或者
// 在key不等于null的情况下key的内容也相等,则说明两个key是
// 一样的,则将当前节点p用临时节点e保存
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果当前节点p是(红黑)树类型的节点,则需要特殊处理
// 如果是树,则说明碰撞已经开始用树来处理,后续的数据结构都是树而非
// 列表了
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//不是,则为链表
for (int binCount = 0; ; ++binCount) {
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完全相等的节点,则用新节点替换老节点
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 此时的e是保存的被碰撞的那个节点,即老节点
if (e != null) {
V oldValue = e.value;
// onlyIfAbsent是方法的调用参数,表示是否替换已存在的值,
// 在默认的put方法中这个值是false,所以这里会用新值替换旧值
if (!onlyIfAbsent || oldValue == null)
e.value = value;//赋予新值
afterNodeAccess(e);
return oldValue;
}
}
// map变更性操作计数器
// 比如map结构化的变更像内容增减或者rehash,这将直接导致外部map的并发
// 迭代引起fail-fast问题,该值就是比较的基础
++modCount;
//判断是否需要扩容
if (++size > threshold)
resize();
//空操作
afterNodeInsertion(evict);
return null;
}