jdk 1.8 hashmap源码解读(详细)(上)
2017-10-26 18:04
549 查看
package java.util;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* 默认的初始化大小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 (当通上的节点数大于8转成树)
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 不使用树的阀值 6 (桶节点小于6转化成链表)
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 当为最小的树的时候数组容量为64
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* 内部类实现map.entry<k,v>结构,可以看做基本单元,一个节点
*/
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; }
///节点的哈希值算法,key和value哈希取异或 ,因此节点本身的hashCode和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;
}
//节点是否相等,key相等,value相等
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())) ///多了个判断不为null
return true;
}
return false;
}
}
/* ---------------- Static utilities -------------- */
///hash计算,可以看出16位有效位
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); ///32位的,把高位和低位异或,使高位参与运算,分布更均匀,避免碰撞
}
//获取对象的Comparable<>的泛型的string.class
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) { //判断是否实现 比较接口
Class<?> c;
Type[] ts, as;
Type t;
ParameterizedType p;
if ((c = x.getClass()) == String.class) // bypass checks 说明:这里有两步 1.赋值c,2.判断string.class
return c; //如果是string类型,返回string.class
if ((ts = c.getGenericInterfaces()) != null) { //c对象实现的所有接口,如果是接口,泛型的原型
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) &&//判断是否包含接口
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&//此接口是否是Comparable接口
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // 判断第一个类型是不是String.class类型的
return c;
}
}
}
return null;
}
/**
* x的Class文件为kc,且不为null,k>x 1,等0,小-1
*/
@SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareT
10199
o(x)); //如果k>x 为1 等为0 小-1
}
/**
* 返回比给定整数大且最接近的2的幂次方整数,如10,返回16
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
/* ---------------- Fields -------------- */
///节点的数组,长度2的次数
transient Node<K,V>[] table;
/**
* 存放具体的键值对的set集合
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* 实际存在的键值对的大小.
*/
transient int size;
/**
* 改变的次数
*/
transient int modCount;
/**
*
*临界阀值扩容:容量*加载因子(下次改变的大小)
*
*/
int threshold;
/**
*
* 对于数组的加载因子
*/
final float loadFactor;
/* ---------------- Public operations -------------- */
/**
* 初始化,构造函数:阀值大小 加载因子(浮点级)
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)///初始容量小于0 报错
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY; //大于最大取最大,2的31次方,大约21亿
if (loadFactor <= 0 || Float.isNaN(loadFactor))////加载因子小于0,或者不是数字 报错
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity); //初始化阀值的大小
}
/**
*
*
* 构造函数:加载因子取默认0.75,大小输入
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* 构造函数:加载因子0.75
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
*
* 以map<entry<,>>为初始化的构造函数
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
/**
*将map中的值加入本hashmap中
*
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size(); ///加入map的大小
if (s > 0) {
if (table == null) { // pre-size ///刚开始的时候note[]为null
float ft = ((float)s / loadFactor) + 1.0F; /////////////////////////由加载因子和实际大小的值推出应该赋予的真实容量,
int t = ((ft < (float)MAXIMUM_CAPACITY) ? //////得到值小于最大值,取得到值,大于最大,取最大 ////继上:这里说明+1.0F的作用,是为了在下一步(int)转型的时候小数
(int)ft : MAXIMUM_CAPACITY); ////继上:去掉问题的解决,如果不加,就会算出值小于真实容量,反过来即阀值小于加入的map大小
if (t > threshold) //需要容量大于设置阀值 :意味着放入map,map的值达到了扩容的阀值,即是放入的map大小比设置的map阀值大(思想:为上步的需求计算的容错)
threshold = tableSizeFor(t);//调大阀值,为可能出现的扩容做准备,即是初始化阀值
}
else if (s > threshold) ///如果放入的table不为null,且放入的map实际值大于当前的阀值
resize(); ///扩容,把已有的分布到新表并扩容 设置阀值
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { ///
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);//循环放入 存在数组的首链替换,链中不替换
}
}
}
/**
*返回大小
*
*/
public int size() {
return size;
}
/**
* 是否为null长度为0
*
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 由key获得值,先获得节点,在获得值
*/
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
*
*查找所需要的节点
* 第一步:判断数组的首连接
* 第二步:如果首连接符合,返回首连接,不符合,判断下个连接是否为null,
*为null返回null,不为null,判断是否首连接是否是树连接,
*是:查找树,不是:查找从首连接的下一个开始遍历查找
*/
final 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) { ////在现有数组不为null和长度大于0前提下,得到坐标的首连接不为null
if (first.hash == hash && ///查看首连接是否是符合要求的的节点(hash值和key值)
((k = first.key) == key || (key != null && key.equals(k))))
return first; //符合返回
if ((e = first.next) != null) { ///首连接下一个不符合 包含把首连接下一个赋予e
if (first instanceof TreeNode) //判断首连接是否是树连接
return ((TreeNode<K,V>)first).getTreeNode(hash, key); //从树中hash和key获取值
do {
if (e.hash == hash && //如果不是tree遍历连接的每个节点
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
/**
*
*
* 是否包含:查找,返回null,为true包含,返回值,为false,不包含
*/
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
/**
*
* 放入key,和value
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);///放入,false的时候,在onlyifabsent取fasle的时候默认数组首位置替换,支持linkedhashmap的回调
}
/**
*
* 加入值
*嗯,代码有点小长,总结下:
*第一步:判断hashmap数组是否为null,是:初始化。
*第二步:找到数组下标的首节点,为null加入,
*第三步,不为null,符合hash和key相等条件,取出当前节点,在最后判断当输入的条件为true和不为null时,不替换,其他替换,在最后设置linkedhashmap回调
*第四步:不为null,不符合条件,判断是否是树节点,是加入
*第五步:不为null,首节点不符合条件,不是树节点,只有是链节点,for循环查找,下节点为null,加入下节点,当加入长度达到7,转换成树,如果已经存在不加入
*第六部:改变变量加一,判断是否达到阀值和回调
*注意,当加入的值在数组的链的首位置的时候当输入值true和不为空的时候不进行替换,但是当为链的其他位置的时候已经存在key值value不替换,value
*
*/ a.为false替换 b.为true当null才替换
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; ////节点数组
Node<K,V> p;//一个节点
int n, i;
if ((tab = table) == null || (n = tab.length) == 0)//当前现有的节点为null或者0 这里包含把现有数组赋予tab和数组长度赋予了n
n = (tab = resize()).length; //旧节点为null,对其初始化 容量为16的数组
if ((p = tab[i = (n - 1) & hash]) == null) //如果当前插入位置值为null p=传入当前的值 (第一个note的对象)
tab[i] = newNode(hash, key, value, null);//在当前节点创建node,
else { //如果有值
Node<K,V> e; K k;
if (p.hash == hash && //把传入的hash和hash对比 在相等的前提下key值进行对比相等
((k = p.key) == key || (key != null && key.equals(k))))
e = p;//取出当前值 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
else if (p instanceof TreeNode)//当hash和key比较失败的时候,判断是否为红黑树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//8放入红黑树
else { //链首位的hash和key不服和,也不符合树节点,只有在链的其他位置
for (int binCount = 0; ; ++binCount) { //无限循环查找 ///注意当多线程的情况下,这里容易形成死循环,cpu爆
if ((e = p.next) == null) { //把首节点的下一个节点指向e,如果为null
p.next = newNode(hash, key, value, null); 创建新节点,并把它指向p首节点
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//如果本链添加的长度大于等于TREEIFY_THRESHOLD-1=7
treeifyBin(tab, hash);////********
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) ///如果已经存在,不加入
break;
p = e; //便于下次循环传递
}
}
if (e != null) { // existing mapping for key ///在数组的首节点相等时候,对应上面的XXXXX所在行
V oldValue = e.value; //取出旧值
if (!onlyIfAbsent || oldValue == null)//旧值为null或者输入条件为false时 进行新旧值替换
e.value = value;
afterNodeAccess(e); ///当为linkedhashmap时候许可回调
return oldValue; //返回旧值
}
}
++modCount; //改变加一
if (++size > threshold) ///实际值大小大于阀值
resize(); 扩容
afterNodeInsertion(evict); //当为linkedhashmap时候许可回调
return null;返回null
}
/**
*
* 扩容机制,扩容
*
*嗯,有点长,总结下:
*第一步 A:判断判断是否存在旧值,如果旧结点达最大,就让他大吧!如果没有达到最大,
*容量扩大2倍,阀值扩大2倍。B:判断没有存在旧值,判断那一步初始化阶段,没有内容,有没阀值,
*然后if对阀值补充,将最新阀值赋予当前阀值.
*第二步:A.赋值并释放当前数组.B如果单节点,直接均匀放入C.如果不是单节点是树节点,
*放入树节点D.如果不是单节点也不是树节点,把当前的链分两条分别放入对应新表两部分对应下标(下标之差为旧表的数组大小)
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; //取出现有接点数组
int oldCap = (oldTab == null) ? 0 : oldTab.length;///获取旧节点的长度 刚刚掉用构造函数的时候,没有值,大小取0
int oldThr = threshold; //旧节点的扩容阀值
int newCap, newThr = 0; //新节点的容量,阀值=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)////旧结点大于默认的初始容量
newThr = oldThr << 1; // double threshold //新的阀值取旧的2倍
} ///不存在旧结点
else if (oldThr > 0) // 初始化,没有内容,但是设置阀值的情况 hashmap(inintcap,factoy)和hashmap(initcap)情况
newCap = oldThr; 新的阀值取旧阀值
else { // 初始化,没有内容,也没有阀值的情况 hashmap()和hashmap(map)准备
newCap = DEFAULT_INITIAL_CAPACITY; //默认容量16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //默认阀值16*0.75=12
}
///新阀值为0 (什么时候出现?在上面的初始化,没有内容却又阀值)
if (newThr == 0) {
float ft = (float)newCap * loadFactor; //在上面newCap是旧设置阀值*加载因子 扩容变小了?????? >_<
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE); //
}
threshold = newThr;//总上所述,最后新阀值赋予阀值
@SuppressWarnings({"rawtypes","unchecked"})
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; //新节点
if ((e = oldTab[j]) != null) {
oldTab[j] = null;//释放旧节点数组中的值,便于GC回收,把值赋予e
if (e.next == null)//如果当前就节点下个节点为空,即单节点
newTab[e.hash & (newCap - 1)] = e;///把e放入新数组中,下标以e的hash和容量-1与为下标
///https://www.zhihu.com/question/44460053 ///相当于对值的取膜
///http://blog.csdn.net/zccracker/article/details/54378852 《--大力推荐文章
else if (e instanceof TreeNode)//如果当前下个不为null,并为树节点,
((TreeNode<K,V>)e).split(this, newTab, j, oldCap); ////额......>_< 把当前的节点 在新表中以j分成散列度的oldCap大小数,
else { // preserve order //下个节点不为null,不是树节点
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
///e的hash和就oldCap是否为0与分割成两张链表。理解:oldCap的为16时10000,在以后每次2倍相当于向前移一位,都是100xxxx格式,取&是否为0的概率就变成对半分了
if ((e.hash & oldCap) == 0) {
if (loTail == null) //当前为null,给当前,否则给下一个
loHead = e;//
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)//当前为null,给当前,否则给下一个
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);//知道e的下一个为null为止,单向链表结构停止
if (loTail != null) {//当前节点不为null
loTail.next = null;//下个为null,(意味着放完了)
newTab[j] = loHead; //把当前链放入数组中
}
if (hiTail != null) {//当前节点不为null
hiTail.next = null;下个为null,//(意味着放完了)
newTab[j + oldCap] = hiHead;//把当前链放入数组中 这里把新的链放入之中,正好对应新的表大小为原来的两倍
}
}
}
}
}
return newTab;
}
/**
* 当到链的长度达到7的时候变成树
*/
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)///如果为数组为null,或者数组的长度小于最小树容量64,扩容
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) { ///如果数组的长度达到就转化成节点树结构
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null); ///替换成树节点,从第一个开始
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
/**
* 将map放入hashmap中 许可启用linkedhashmap
*
*/
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
/**
* 由key来删除节点值,
*/
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ? //含有操作,调用元素,取出节点,得到节点的值
null : e.value;
}
/**
*移除节点,有点多,总结下
*第一步:判断当前hash含有链,然后判断首节点,首节点不符合,
*判断下个节点是不是树,是树节点转到树,由树去取出节点,不是树,while检查链组
*第二步:得到要删除的节点,判断输入匹配条件,
*然后判断是否树,是由树去删除,不是,判断是否为首节点,是将要删的节点下个接点方到数组中
*不是首节点,将上接点的下一个指向下节点的上一个,返回删除节点
*/
final Node<K,V> removeNode(int hash, Object key, Object value, ///存在,只要值相等一定删
boolean matchValue, boolean movable) {
//如果false 不管等不等都删 // 树节点的移除标记
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) { //当前数组不为null,长度大于0,找到位置存在元素
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //检查第一个节点,赋予node
node = p;
else if ((e = p.next) != null) {///第一节点不符合,检查下一节点
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);//是否为树,获取
else {
do { ///第一节点不符合,第二节点不是树节点,只有链表结构了
if (e.hash == hash &&
((k = e.key) == key || ///第二节点是否符合
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e; ///标记:BBBBBBBBBB
} while ((e = e.next) != null);
}
}
///在这一步把获取到的节点在node上
if (node != null && (!matchValue || (v = node.value) == value || ///在删除的节点不为null的前提下,当
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); //如果为树,移除树节点
else if (node == p) /////是否是首节点
tab[index] = node.next; ///把要删的节点的下个节点定义为首节点
else
p.next = node.next; //是一般节点 把要删除的节点的上个节点指向要删节点的下一节点 注意:要和上面查找删除节点的判断结合一起看,BBBBBBB行
++modCount; //修改加一
--size; //大小减一
afterNodeRemoval(node); //linkedhashmap的回调
return node; 返回要删节点
}
} //没有找到删除节点,(数组所在index链为null)
return null;
}
/**
* 清除,存在值,把size为0,把数组每个变成null有GC回收
*/
public void clear() {
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}
/**
*
* 遍历节点比较value是否包含
*/
public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) {
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* 默认的初始化大小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 (当通上的节点数大于8转成树)
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 不使用树的阀值 6 (桶节点小于6转化成链表)
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 当为最小的树的时候数组容量为64
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* 内部类实现map.entry<k,v>结构,可以看做基本单元,一个节点
*/
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; }
///节点的哈希值算法,key和value哈希取异或 ,因此节点本身的hashCode和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;
}
//节点是否相等,key相等,value相等
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())) ///多了个判断不为null
return true;
}
return false;
}
}
/* ---------------- Static utilities -------------- */
///hash计算,可以看出16位有效位
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); ///32位的,把高位和低位异或,使高位参与运算,分布更均匀,避免碰撞
}
//获取对象的Comparable<>的泛型的string.class
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) { //判断是否实现 比较接口
Class<?> c;
Type[] ts, as;
Type t;
ParameterizedType p;
if ((c = x.getClass()) == String.class) // bypass checks 说明:这里有两步 1.赋值c,2.判断string.class
return c; //如果是string类型,返回string.class
if ((ts = c.getGenericInterfaces()) != null) { //c对象实现的所有接口,如果是接口,泛型的原型
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) &&//判断是否包含接口
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&//此接口是否是Comparable接口
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // 判断第一个类型是不是String.class类型的
return c;
}
}
}
return null;
}
/**
* x的Class文件为kc,且不为null,k>x 1,等0,小-1
*/
@SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareT
10199
o(x)); //如果k>x 为1 等为0 小-1
}
/**
* 返回比给定整数大且最接近的2的幂次方整数,如10,返回16
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
/* ---------------- Fields -------------- */
///节点的数组,长度2的次数
transient Node<K,V>[] table;
/**
* 存放具体的键值对的set集合
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* 实际存在的键值对的大小.
*/
transient int size;
/**
* 改变的次数
*/
transient int modCount;
/**
*
*临界阀值扩容:容量*加载因子(下次改变的大小)
*
*/
int threshold;
/**
*
* 对于数组的加载因子
*/
final float loadFactor;
/* ---------------- Public operations -------------- */
/**
* 初始化,构造函数:阀值大小 加载因子(浮点级)
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)///初始容量小于0 报错
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY; //大于最大取最大,2的31次方,大约21亿
if (loadFactor <= 0 || Float.isNaN(loadFactor))////加载因子小于0,或者不是数字 报错
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity); //初始化阀值的大小
}
/**
*
*
* 构造函数:加载因子取默认0.75,大小输入
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* 构造函数:加载因子0.75
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
*
* 以map<entry<,>>为初始化的构造函数
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
/**
*将map中的值加入本hashmap中
*
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size(); ///加入map的大小
if (s > 0) {
if (table == null) { // pre-size ///刚开始的时候note[]为null
float ft = ((float)s / loadFactor) + 1.0F; /////////////////////////由加载因子和实际大小的值推出应该赋予的真实容量,
int t = ((ft < (float)MAXIMUM_CAPACITY) ? //////得到值小于最大值,取得到值,大于最大,取最大 ////继上:这里说明+1.0F的作用,是为了在下一步(int)转型的时候小数
(int)ft : MAXIMUM_CAPACITY); ////继上:去掉问题的解决,如果不加,就会算出值小于真实容量,反过来即阀值小于加入的map大小
if (t > threshold) //需要容量大于设置阀值 :意味着放入map,map的值达到了扩容的阀值,即是放入的map大小比设置的map阀值大(思想:为上步的需求计算的容错)
threshold = tableSizeFor(t);//调大阀值,为可能出现的扩容做准备,即是初始化阀值
}
else if (s > threshold) ///如果放入的table不为null,且放入的map实际值大于当前的阀值
resize(); ///扩容,把已有的分布到新表并扩容 设置阀值
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { ///
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);//循环放入 存在数组的首链替换,链中不替换
}
}
}
/**
*返回大小
*
*/
public int size() {
return size;
}
/**
* 是否为null长度为0
*
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 由key获得值,先获得节点,在获得值
*/
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
*
*查找所需要的节点
* 第一步:判断数组的首连接
* 第二步:如果首连接符合,返回首连接,不符合,判断下个连接是否为null,
*为null返回null,不为null,判断是否首连接是否是树连接,
*是:查找树,不是:查找从首连接的下一个开始遍历查找
*/
final 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) { ////在现有数组不为null和长度大于0前提下,得到坐标的首连接不为null
if (first.hash == hash && ///查看首连接是否是符合要求的的节点(hash值和key值)
((k = first.key) == key || (key != null && key.equals(k))))
return first; //符合返回
if ((e = first.next) != null) { ///首连接下一个不符合 包含把首连接下一个赋予e
if (first instanceof TreeNode) //判断首连接是否是树连接
return ((TreeNode<K,V>)first).getTreeNode(hash, key); //从树中hash和key获取值
do {
if (e.hash == hash && //如果不是tree遍历连接的每个节点
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
/**
*
*
* 是否包含:查找,返回null,为true包含,返回值,为false,不包含
*/
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
/**
*
* 放入key,和value
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);///放入,false的时候,在onlyifabsent取fasle的时候默认数组首位置替换,支持linkedhashmap的回调
}
/**
*
* 加入值
*嗯,代码有点小长,总结下:
*第一步:判断hashmap数组是否为null,是:初始化。
*第二步:找到数组下标的首节点,为null加入,
*第三步,不为null,符合hash和key相等条件,取出当前节点,在最后判断当输入的条件为true和不为null时,不替换,其他替换,在最后设置linkedhashmap回调
*第四步:不为null,不符合条件,判断是否是树节点,是加入
*第五步:不为null,首节点不符合条件,不是树节点,只有是链节点,for循环查找,下节点为null,加入下节点,当加入长度达到7,转换成树,如果已经存在不加入
*第六部:改变变量加一,判断是否达到阀值和回调
*注意,当加入的值在数组的链的首位置的时候当输入值true和不为空的时候不进行替换,但是当为链的其他位置的时候已经存在key值value不替换,value
*
*/ a.为false替换 b.为true当null才替换
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; ////节点数组
Node<K,V> p;//一个节点
int n, i;
if ((tab = table) == null || (n = tab.length) == 0)//当前现有的节点为null或者0 这里包含把现有数组赋予tab和数组长度赋予了n
n = (tab = resize()).length; //旧节点为null,对其初始化 容量为16的数组
if ((p = tab[i = (n - 1) & hash]) == null) //如果当前插入位置值为null p=传入当前的值 (第一个note的对象)
tab[i] = newNode(hash, key, value, null);//在当前节点创建node,
else { //如果有值
Node<K,V> e; K k;
if (p.hash == hash && //把传入的hash和hash对比 在相等的前提下key值进行对比相等
((k = p.key) == key || (key != null && key.equals(k))))
e = p;//取出当前值 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
else if (p instanceof TreeNode)//当hash和key比较失败的时候,判断是否为红黑树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//8放入红黑树
else { //链首位的hash和key不服和,也不符合树节点,只有在链的其他位置
for (int binCount = 0; ; ++binCount) { //无限循环查找 ///注意当多线程的情况下,这里容易形成死循环,cpu爆
if ((e = p.next) == null) { //把首节点的下一个节点指向e,如果为null
p.next = newNode(hash, key, value, null); 创建新节点,并把它指向p首节点
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//如果本链添加的长度大于等于TREEIFY_THRESHOLD-1=7
treeifyBin(tab, hash);////********
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) ///如果已经存在,不加入
break;
p = e; //便于下次循环传递
}
}
if (e != null) { // existing mapping for key ///在数组的首节点相等时候,对应上面的XXXXX所在行
V oldValue = e.value; //取出旧值
if (!onlyIfAbsent || oldValue == null)//旧值为null或者输入条件为false时 进行新旧值替换
e.value = value;
afterNodeAccess(e); ///当为linkedhashmap时候许可回调
return oldValue; //返回旧值
}
}
++modCount; //改变加一
if (++size > threshold) ///实际值大小大于阀值
resize(); 扩容
afterNodeInsertion(evict); //当为linkedhashmap时候许可回调
return null;返回null
}
/**
*
* 扩容机制,扩容
*
*嗯,有点长,总结下:
*第一步 A:判断判断是否存在旧值,如果旧结点达最大,就让他大吧!如果没有达到最大,
*容量扩大2倍,阀值扩大2倍。B:判断没有存在旧值,判断那一步初始化阶段,没有内容,有没阀值,
*然后if对阀值补充,将最新阀值赋予当前阀值.
*第二步:A.赋值并释放当前数组.B如果单节点,直接均匀放入C.如果不是单节点是树节点,
*放入树节点D.如果不是单节点也不是树节点,把当前的链分两条分别放入对应新表两部分对应下标(下标之差为旧表的数组大小)
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; //取出现有接点数组
int oldCap = (oldTab == null) ? 0 : oldTab.length;///获取旧节点的长度 刚刚掉用构造函数的时候,没有值,大小取0
int oldThr = threshold; //旧节点的扩容阀值
int newCap, newThr = 0; //新节点的容量,阀值=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)////旧结点大于默认的初始容量
newThr = oldThr << 1; // double threshold //新的阀值取旧的2倍
} ///不存在旧结点
else if (oldThr > 0) // 初始化,没有内容,但是设置阀值的情况 hashmap(inintcap,factoy)和hashmap(initcap)情况
newCap = oldThr; 新的阀值取旧阀值
else { // 初始化,没有内容,也没有阀值的情况 hashmap()和hashmap(map)准备
newCap = DEFAULT_INITIAL_CAPACITY; //默认容量16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //默认阀值16*0.75=12
}
///新阀值为0 (什么时候出现?在上面的初始化,没有内容却又阀值)
if (newThr == 0) {
float ft = (float)newCap * loadFactor; //在上面newCap是旧设置阀值*加载因子 扩容变小了?????? >_<
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE); //
}
threshold = newThr;//总上所述,最后新阀值赋予阀值
@SuppressWarnings({"rawtypes","unchecked"})
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; //新节点
if ((e = oldTab[j]) != null) {
oldTab[j] = null;//释放旧节点数组中的值,便于GC回收,把值赋予e
if (e.next == null)//如果当前就节点下个节点为空,即单节点
newTab[e.hash & (newCap - 1)] = e;///把e放入新数组中,下标以e的hash和容量-1与为下标
///https://www.zhihu.com/question/44460053 ///相当于对值的取膜
///http://blog.csdn.net/zccracker/article/details/54378852 《--大力推荐文章
else if (e instanceof TreeNode)//如果当前下个不为null,并为树节点,
((TreeNode<K,V>)e).split(this, newTab, j, oldCap); ////额......>_< 把当前的节点 在新表中以j分成散列度的oldCap大小数,
else { // preserve order //下个节点不为null,不是树节点
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
///e的hash和就oldCap是否为0与分割成两张链表。理解:oldCap的为16时10000,在以后每次2倍相当于向前移一位,都是100xxxx格式,取&是否为0的概率就变成对半分了
if ((e.hash & oldCap) == 0) {
if (loTail == null) //当前为null,给当前,否则给下一个
loHead = e;//
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)//当前为null,给当前,否则给下一个
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);//知道e的下一个为null为止,单向链表结构停止
if (loTail != null) {//当前节点不为null
loTail.next = null;//下个为null,(意味着放完了)
newTab[j] = loHead; //把当前链放入数组中
}
if (hiTail != null) {//当前节点不为null
hiTail.next = null;下个为null,//(意味着放完了)
newTab[j + oldCap] = hiHead;//把当前链放入数组中 这里把新的链放入之中,正好对应新的表大小为原来的两倍
}
}
}
}
}
return newTab;
}
/**
* 当到链的长度达到7的时候变成树
*/
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)///如果为数组为null,或者数组的长度小于最小树容量64,扩容
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) { ///如果数组的长度达到就转化成节点树结构
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null); ///替换成树节点,从第一个开始
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
/**
* 将map放入hashmap中 许可启用linkedhashmap
*
*/
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
/**
* 由key来删除节点值,
*/
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ? //含有操作,调用元素,取出节点,得到节点的值
null : e.value;
}
/**
*移除节点,有点多,总结下
*第一步:判断当前hash含有链,然后判断首节点,首节点不符合,
*判断下个节点是不是树,是树节点转到树,由树去取出节点,不是树,while检查链组
*第二步:得到要删除的节点,判断输入匹配条件,
*然后判断是否树,是由树去删除,不是,判断是否为首节点,是将要删的节点下个接点方到数组中
*不是首节点,将上接点的下一个指向下节点的上一个,返回删除节点
*/
final Node<K,V> removeNode(int hash, Object key, Object value, ///存在,只要值相等一定删
boolean matchValue, boolean movable) {
//如果false 不管等不等都删 // 树节点的移除标记
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) { //当前数组不为null,长度大于0,找到位置存在元素
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //检查第一个节点,赋予node
node = p;
else if ((e = p.next) != null) {///第一节点不符合,检查下一节点
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);//是否为树,获取
else {
do { ///第一节点不符合,第二节点不是树节点,只有链表结构了
if (e.hash == hash &&
((k = e.key) == key || ///第二节点是否符合
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e; ///标记:BBBBBBBBBB
} while ((e = e.next) != null);
}
}
///在这一步把获取到的节点在node上
if (node != null && (!matchValue || (v = node.value) == value || ///在删除的节点不为null的前提下,当
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); //如果为树,移除树节点
else if (node == p) /////是否是首节点
tab[index] = node.next; ///把要删的节点的下个节点定义为首节点
else
p.next = node.next; //是一般节点 把要删除的节点的上个节点指向要删节点的下一节点 注意:要和上面查找删除节点的判断结合一起看,BBBBBBB行
++modCount; //修改加一
--size; //大小减一
afterNodeRemoval(node); //linkedhashmap的回调
return node; 返回要删节点
}
} //没有找到删除节点,(数组所在index链为null)
return null;
}
/**
* 清除,存在值,把size为0,把数组每个变成null有GC回收
*/
public void clear() {
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}
/**
*
* 遍历节点比较value是否包含
*/
public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) {
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}
相关文章推荐
- HashMap 源码详细分析(JDK1.8)
- HashMap 源码详细分析(JDK1.8)
- HashMap源码之hash()函数分析(JDK 1.8)
- ConcurrentHashMap JDK1.8源码深入剖析(一)
- HashMap源码之hash()函数分析(JDK 1.8)
- Linux源码安装jdk1.8详细步骤
- JDK源码之解读hashMap 的put和get方法的实现原理
- WeakHashMap源码探讨(基于JDK1.8)
- HashMap 源码解析(JDK1.8)
- HashMap源码分析(基于JDK1.8)
- HashMap源码分析-基于JDK1.8
- JDK1.8源码逐字逐句带你理解HashMap底层(1)
- Java之ArrayList源码解读(JDK 1.8)
- JDK源码之解读hashMap 的put和get方法的实现原理
- Java中HashMap底层实现原理(JDK1.8)源码分析
- JDK1.8源码解析之 HashMap
- HashMap源码分析——JDK1.8
- 【图解JDK源码】HashMap的容量大小增长原理(JDK1.6/1.7/1.8)
- JDK8之HashMap源码解读
- jdk源码剖析四:JDK1.7升级1.8 HashMap原理的变化