HashMap源码学习
简介
HashMap是数组+链表实现,采用key/value的键值对,每个key对应唯一的value,查询和修改的效率很快,能达到O(1)的平均时间复杂度,是非线程安全的且不能保证元素的存储的顺序。
存储结构
采用数组+链表,出现hash冲突的时采用链表解决hash冲突。hashmap定义了一个数组变量transient Node<K,V>[] table;Node是一个静态内部类实现了Map.Entry<K,V>,采用链表指向下一个节点。
final int hash;
final K key;
V value;
Node<K,V> next;
HashMap采用数组+链表+红黑树,一个数组下标位存储Node链表。在添加元素时会根据key计算出hash值算出在数组的下标位。
当链表长度超多了8时会转化为红黑树并再元素减少到6时候又会把红黑树转为链表来提高效率。数组的查询效率O(1),链表是O(n),红黑树的查询效率O(log n)。
属性变量
/**
* 默认数组长度为16,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;
/**
* 当一个数组的链表长度大于等于8时转化为红黑树
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 当一个数组的链表是红黑树结构但后续删减之后小于等于6时把红黑树转为普通链表
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 当数组的长度大于
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* 数组,又叫作桶(bucket)
*/
transient Node<K,V>[] table;
/**
* 作为entrySet()的缓存
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* 元素的数量
*/
transient int size;
/**
* 修改次数,用于在迭代的时候执行快速失败策略
*/
transient int modCount;
/**
* 当桶的使用数量达到多少时进行扩容,threshold = capacity * loadFactor
*/
int threshold;
/**
* 装载因子
*/
final float loadFactor;
内部类
HashMap的数组是Node数组,Node是一个典型的单链表节点,其中,hash用来存储key计算得来的hash值。
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;
}
}
上面提到当链表会转化为红黑树,HashMap定义了TreeNode的静态内部类,是一个树形结构。定义如下
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;
}
构造方法
空构造方法,属性使用默认值
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; //指定扩容因子0.75
}
指定容器大小和扩容因子的构造函数,HashMap(int initialCapacity)也是调用这个函数
public HashMap(int initialCapacity, float loadFactor) {
// 检查传入的初始容量是否合法
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 检查装载因子是否合法
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
put(K key, V value)
添加元素
public V put(K key, V value) {
//调用hash(key)计算key的hash值
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
//若key为null,则hash值为0;否则使用key的hashcode获取hash并让hash和高位的16位异或获取hash值
//int类型4个字节32位,相当于让高16位和低16位异或,保证计算出的hash更分散。
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//往容器添加元素
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//tab数组临时变量,p用于链表查找,n记录数组长度,i数字索引临时变量。
Node<K,V>[] tab; Node<K,V> p; int n, i;
//当数组位空或数组长度为0时,进行扩容范围新的数组长度
if ((tab = table) == null || (n = tab.length) == 0)
//调用resize进行扩容
n =