最近看了下HashMap的源码,来记录下它的实现原理。
HashMap最一开始是一个基于链表的数组结构,数组的每一个索引位置上都链着具有相同hash计算结果的键值对数据,为了提高查找效率,jdk1.8以后当数组某个索引位置的节点数大于8时,会自动转换为红黑树。
首先要了解HashMap的静态内部类Node<K,V>,即数组索引位置上的链表节点,它继承自Map.Entry<K,V>,也就是我们经常遍历Map时用到的对象类型,储存了Key和Value的值,存入HashMap里的键值对都是以该对象类型存在。
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;
}
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(value, e.getValue()))
return true;
}
return false;
}
}
还有树节点TreeNode<K,V>,当数组某索引上的键值对数量大于8时会转换为红黑树数据结构,里有很多维持红黑树数据结构的方法,这里不做阐述,想了解的同学可以先去学习红黑树再来看这个结构,会更快理解.
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;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
//......省略很多方法
}
接着看看HashMap的成员变量:
/**
* 序列号,序列化时使用
*/
private static final long serialVersionUID = 362498820763181265L;
/**
* 数组默认容量是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;
/**
* 当某个桶的节点数大于8时,会转换为红黑树
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 当某个桶的节点数小于6时,会从红黑树又转换为链表
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 当hashmap中的元素数量大于64时,也会转换为红黑树
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* 存储键值对元素的数组,也是HashMap结构的核心
*/
transient Node<K,V>[] table;
/**
* 将键值对数据包装成Set结构,用于遍历
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* hashmap内存储的元素数量
*/
transient int size;
/**
* hashmap的修改次数
*/
transient int modCount;
/**
* 当元素数量达到此临界值时会进行扩容
*/
int threshold;
/**
* 也是加载因子
*/
final float loadFactor;
HashMap的构造方法主要围绕数组容量和加载因子的:
/**
* 设置初始容量和加载因子
*/
public HashMap(int initialCapacity, float loadFactor) {