- 默认大小、负载因子以及扩容倍数是多少
- 底层数据结构
- 如何处理 hash 冲突的
- 如何计算一个 key 的 hash 值
- 数组长度为什么是 2 的幂次方
- 扩容、查找过过程
- HashMap
HashMap底层的数据结构
JDK7:数组+链表
JDK8: 数组+链表+红黑树(使用了单向链表,也使用了双向链表,双向链表主要是为了链表操作方便,应该在插入,扩容,链表转红黑树,红黑树转链表的过程中都要操作链表)
JDK8中的HashMap什么时候将链表转化为红黑树?
只有当链表中的元素个数大于8,并且数组的长度大于等于64时才会将链表转为红黑树。
1、hashCode值
hash算法是一种可以从任何数据中提取出其“指纹”的数据摘要算法,它将任意大小的数据映射到一个固定大小的序列上,这个序列被称为hash code、数据摘要或者指纹。比较出名的hash算法有MD5、SHA。hash是具有唯一性且不可逆的,唯一性是指相同的“对象”产生的hash code永远是一样的。
2、Hash表的物理结构
HashMap和Hashtable是散列表,其中维护了一个长度为2的幂次方的Entry类型的数组table,数组的每一个元素被称为一个桶(bucket),你添加的映射关系(key,value)最终都被封装为一个Map.Entry类型的对象,放到了某个table[index]桶中。使用数组的目的是查询和添加的效率高,可以根据索引直接定位到某个table[index]。
(1)数组元素类型:Map.Entry
JDK1.7:
映射关系被封装为HashMap.Entry类型,而这个类型实现了Map.Entry接口。
观察HashMap.Entry类型是个结点类型,即table[index]下的映射关系可能串起来一个链表。因此我们把table[index]称为“桶bucket"。
public class HashMap<K,V>{
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
//...省略
}
//...
}
JDK1.8:
映射关系被封装为HashMap.Node类型或HashMap.TreeNode类型,它俩都直接或间接的实现了Map.Entry接口。
存储到table数组的可能是Node结点对象,也可能是TreeNode结点对象,它们也是Map.Entry接口的实现类。即table[index]下的映射关系可能串起来一个链表或一棵红黑树(自平衡的二叉树)。
public class HashMap<K,V>{
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
//...省略
}
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;
boolean red;//是红结点还是黑结点
//...省略
}
//....
}
public class LinkedHashMap<K,V>{
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
//...
}
//默认初始容量为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//默认负载因子为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//Hash数组(在re