目录
1. HashMap
概述
HashMap
是通过 put(key,value)
存储,get(key)
来获取。当传入 key
时,HashMap
会根据 key
的 hashCode()
方法计算出 hash
值,根据 hash
值将 value
保存在 bucket
(桶)里。当计算出的 hash
值相同时,称之为 hash
冲突。HashMap
的做法是用链表和红黑树存储相同 hash
值的 value
2. jdk 1.8
之前与之后的 HashMap
- 在
jdk1.8
之前的HashMap
是由 数组 + 链表 来实现的,数组是HashMap
的主体。链表主要是为了解决hash
冲突的 - 在
jdk1.8
之后的HashMap
是由 数组 + 链表 + 红黑树 来实现的,在解决hash
冲突时有了较大的变化。当链表长度大于阈值8
时,并且数组的长度大于64
时,此时此索引位置上的所有数据改为使用红黑树存储
3. HashMap
的数组,链表,红黑树之间的转换
- 当创建
HashMap
集合对象的时候,在jdk1.8
之前,是在它的构造方法中创建了一个默认长度是16
的Entry[] table
的数组来存储键值对数据的。而从jdk1.8
开始,是在第一次调用put
方法时创建了一个默认长度是16
的Node[] table
的数组来存储键值对数据的 - 数组创建完成后,当添加一个元素
(key,value)
时,首先计算元素key
的hash
值,以此确定插入数组中的位置。但是可能存在同一hash
值的元素已经被放在数组同一位置了,这时就添加到同一hash
值的元素的后面,他们在数组的同一位置,这就形成了单链表,同一各链表上的Hash
值是相同的。当链表长度大于阈值8
时,并且数组的长度大于64
时,此时此索引位置上的所有数据改为使用红黑树存储,这样大大提高了查找的效率 - 在转换为红黑树存储数据后,如果此时再次删除数据,当红黑树的节点数小于
6
时,那么此时的红黑树将转换为单链表结构来存储数据
4. HashMap
扩容机制
默认情况下,数组大小为 16
,那么当 HashMap
中元素个数超过 16 * 0.75 = 12
(这个值就是代码中的 threshold
值,也叫做临界值)的时候,就把数组的大小扩展为 2*16 = 32
,即扩大一倍,然后重新计算每个元素在数组中的位置
0.75
这个值称为负载因子,那么为什么负载因子为 0.75
呢?
这是通过大量实验统计得出来的,如果过小,比如 0.5
,那么当存放的元素超过一半时就进行扩容,会造成资源的浪费;如果过大,比如 1
,那么当元素满的时候才进行扩容,会使 get,put
操作的碰撞几率增加
5. HashMap
源码
5.1. HashMap
的基本属性
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
// 序列号
private static final long serialVersionUID = 362498820763181265L;
// 默认的初始容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认的填充因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 当桶(bucket)上的结点数大于这个值时会转成红黑树;+对应的table的最小大小为64,即MIN_TREEIFY_CAPACITY ;这两个条件都满足,会链表会转红黑树
static final int TREEIFY_THRESHOLD = 8;
// 当桶(bucket)上的结点数小于这个值时树转链表
static final int UNTREEIFY_THRESHOLD = 6;
// 桶中结构转化为红黑树对应的table的最小大小
static final int MIN_TREEIFY_CAPACITY = 64;
// 存储元素的数组,总是2的幂次倍
transient Node<k,v>[] table;
// 存放具体元素的集
transient Set<map.entry<k,v>> entrySet;
// 存放元素的个数,注意这个不等于数组的长度。
transient int size;
// 每次扩容和更改map结构的计数器
transient int modCount;
// 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
int threshold;
// 填充因子
final float loadFactor;
}
5.2. HashMap
中涉及到的数据结构
5.2.1. 链表节点(单链表)
Node
是 HashMap
的一个内部类,实现了 Map.Entry
接口,本质上是一个单链表的数据结构。链表中的每个节点就是一个 Node
对象
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 int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
// 判断两个node是否相等,若key和value都相等,返回true。可以与自身比较为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