再看之前,需要先理解位移 先看位移运算,按照我们的日常使用-从new开始,来一步步分析源代码。(核心源码每一步都有注释)
第一步:Hash构造函数
主要两类;第一类,不指定初始化容量
public HashMap() {
this.loadFactor = 0.75F;
}
第二类,指定初始容量和负载因子
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);
}
可以看到在new Map的时候并没有进行任何底层数据结构的初始化,而是放到第一次put的时候。
再来看 tableSizeFor 方法,主要是返回大于等于初始化cap的最近的一个2的n次方:
//比如var0 = 5 -> 则表示 为 0000 0101
int var1 = var0 - 1; //var1 = 4,减一的目的如果 var0 = 4 也找到 8了,其实4本身已经满足了。
var1 |= var1 >>> 1; //0100 >>> 1 = 0010, 0100 | 0010 = 0110
var1 |= var1 >>> 2; //0110 >>> 2 = 0001, 0110 | 0001 = 0111
var1 |= var1 >>> 4; // 0111 >>> 4 = 0000,0000|0111 = 0111
var1 |= var1 >>> 8; // 0111 >>> 8 = 0000,0000|0111 = 0111
var1 |= var1 >>> 16; // 0111 >>> 16 = 0000,0000|0111 = 0111 此时的var1 = 7
//因为16已经可以达到32位,继续就没意思了
return var1 < 0 ? 1 : (var1 >= 1073741824 ? 1073741824 : var1 + 1); //此处返回 8,5最近的一个2的n次方就是 8
初始化完成后,日常使用第一步就是 put :
this.putVal(hash(var1), var1, var2, false, true); //实际调用的是putVal 方法。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //32的一半正好是16,高半区和低半区做异或
}
正式进入putVal 方法:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) { //注意onlyIfAbsent参数 是否允许替换。如果当前位置已存在一个值,是否替换,false是替换,true是不替换
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) //判断数组是否为空
n = (tab = resize()).length; //初始化数组
if ((p = tab[i = (n - 1) & hash]) == null) //(n - 1) & hash 计算出数组位置,因为n-1 高位为0 ,所以只考虑低位, 比如 (8-1 = 7) -> 0000 0111 , & 之后只取hash的低三位
tab[i] = newNode(hash, key, value, null);
else {
//如果数组位置已经有值,p是当前数组位置的数据值
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode) //TreeNode,红黑树节点 -> 继承LinkedHashMap.Entry -> 继承HashMap.Node。在链表转成树的时候会数组位置上的元素或从Node变成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)
treeifyBin(tab, hash); // 链表长度达到 8,还会进一步判断数组长度是否达到64,否则则转换成红黑树
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) //key已经存在
break;
p = e;
}
}
if (e != null) { // 此处针对已经存在的key进行处理->值替换、返回旧值
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null) //判断是否执行值替换
e.value = value;
afterNodeAccess(e); //给子类LinkedHashMap使用
return oldValue;
}
}
++modCount;
if (++size > threshold) //数据总数超过阈值, 则开始进行扩容 比如,容量16*0.75 = 12,超过12则开始扩容。
resize();
afterNodeInsertion(evict); //给子类LinkedHashMap使用
return null;
}
resize方法,用于初始化或者扩容:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE; //已经达到最大容量,不能继续扩容啦
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY) //新容量 = 老容量*2,依然小于最大容量且老容量大于16
newThr = oldThr << 1; // 设置新容量为老容量*2
}
else if (oldThr > 0) // 使用tableSizeFor之后的自定义大小为初始化容量
newCap = oldThr;
else { // 使用默认值来初始化
newCap = DEFAULT_INITIAL_CAPACITY; //容量16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //阈值 16 * 0.75
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//创建一个新的数组
table = newTab;
//进入扩容逻辑,初始化则直接返回
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//循环老数组,判断数组位置上是否有数据,e是老数组位置上的元素
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
//下面分别按照数组元素,链表和树来进行处理
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;// 比如,(8-1 = 7)-> 0000 0111 ,扩充后 (16 -1 = 15) -> 0000 1111。 比较 高位多了个1。因为newCap - 1 高位为0 ,所以只考虑低位,所以如果hash(比如......0111 或者 ......1111)的低位第四位为0 那么还在原来数组位置,如果为1,则移动8位。所以最终的结果要么在原位,要么移动 newCap/2。
else if (e instanceof TreeNode) //TreeNode,红黑树节点, 继承LinkedHashMap.Entry 继承HashMap.Node
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);//和链表类似,本身树结构保有双向链表的结构,先按高低位进行拆分,得到新链表后在判断,如果长度小于8,则转成Node的链表,否则重新进行树的构建。
else { //处理链表
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) { //oldCap假设为8 -> 0000 1000,这里判断 hash低位的第四位 是否为0。如果为0,则保持原位,否者移动 oldCap = newCap/2 个位置。然后将原链表拆成 高位和低位 两个链表。
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
//循环之后,可能得到两个新的链表,原位和新的位置
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;//低位,在原来的位置
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;//高位,移动oldCap
}
}
}
}
}
return newTab;
}
红黑树的插入:
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
TreeNode<K,V> root = (parent != null) ? root() : this; //获取根节点,root方法自行看源代码
for (TreeNode<K,V> p = root;;) {
int dir, ph; K pk;
//开始比较
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk))) //已经存在则直接返回
return p;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
//通过dir来判断 左右子节点,一直循环到左或右子节点不存在
if ((p = (dir <= 0) ? p.left : p.right) == null) {
Node<K,V> xpn = xp.next;
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
moveRootToFront(tab, balanceInsertion(root, x));//平衡之后在进行数组上的元素替换
return null;
}
}
}
链表转红黑树方法:treeifyBin仅仅完成了Node单向链表转成TreeNode的双向链表
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize(); //数组长度未达到64,先进行扩容
else if ((e = tab[index = (n - 1) & hash]) != null) { //获取数组上的链表结构第一个Node -> e
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null); //将Node转换成TreeNode
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p; //构成了双向链表
}
tl = p;
} while ((e = e.next) != null); //目前循环目的,只是将Node的单向链表,转成了TreeNode的双向链表
if ((tab[index] = hd) != null)
hd.treeify(tab); //hd,双向链表第一个元素
}
}
TreeNode的treeify方法正式将链表转成树结构:
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null; //清空x的左右子节点
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
else {
K k = x.key;
int h = x.hash; //x是链表的元素
Class<?> kc = null;
for (TreeNode<K,V> p = root;;) { //root,开始循环树插入
int dir, ph;
K pk = p.key;
//计算dir,判断是作为左子节点(<=0)还是右子节点(>0)
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) { //当前树节点的左或右子节点不存在,则开始进行插入处理
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x; //完成节点赋值
root = balanceInsertion(root, x); //开始处理插入平衡
break;
}
}
}
}
moveRootToFront(tab, root); //主要将数组位置设置为树的根节点,同时将根节点设置为链表的首个元素
}
红黑树的平衡-balanceInsertion方法,此处暂不分析。可以先了解一下红黑树的原理:可参考 https://www.jianshu.com/p/e136ec79235c
从以上代码来看,并没有锁机制,所以HashMap并不是线程安全的。
下面再来看看怎么来get:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // 不管 结构是链表、树还是数组元素,最开始总从第一个开始判断。
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)//红黑树
return ((TreeNode<K,V>)first).getTreeNode(hash, key);//基本就是树的二分查找,难度不大
do {//链表,直接循环链表
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
到这里,相信大家对HahsMap的实现和数据结构已经有了一个比较直接的概念。