HashMap源码详解

一、域

1.1HashMap中默认参数化

默认值当我们构造HashMap没有指定参数时就会使用默认的DEFAULT _ INITIAL _ CAPACITY和默认DEFAULT _ LOAD _ FACTOR,以及我们的数据结构在什么时候发生转变的三个变量。

参数名功能
DEFAULT _ INITIAL _ CAPACITYHasMap默认的bin的数量为* 16 ,HashMap采用数组+链表+红黑树的结构,所以该值表示默认的数组*长度;该值必须是2的次幂
MAXIMUM _ CAPACITYHashMap默认情况下能够存放的键值对,一旦超过这个值,就会被设置为Integer.MAX _ VALUE
TREEIFY_THRESHOLD当一个bin中的元素超过了这个值8,此时就会使用链表结构转换为红黑树的数据结构,用于向HashMap中添加key-value时的参数
UNTREEIFY_THRESHOLD当一个bin中的元素小于这个值6的时候,就会将bin的红黑树转换为链表结构的存储方式
MIN_TREEIFY_CAPACITY默认值为64确定一个bin中的数据结构,是否将链表转换为Tree的一个临界条件;如果map中的Entry的数量超过了MIN_TREEIFY_CAPACITY,那么此时一个bin中的元素如果超过了TREEIFY_THRESHOLD,那么此时数据结构会由链表转化为红黑树
1.2.1、table详解

一个table是一个Node类型的数组,而Node是一个链表的数据结构。因此我们HashMap最开始使用的是数组+链表的数据结构。如果我们在构造HashMap时没有指定初始容量,那么这个table数组的长度就会采用DEFAULT_INITIAL_CAPACITY这个值进行数组容量初始化。

1.2.2 、threshold详解

Hashtable resize 的门限值,一旦HashMap的size超过了这个threshold就需要对HashMap进行扩容。threshold = table.length*loadFactor.

1.2.3、loadFactor详解

HashMap的装载因子,我们一般都不会自己设定这个值,而是采用默认的DEFAULT_LOAD_FACTOR=0.75。根据Wikipedia我们可以知道loadFactor=map中存在的Entry的数量 / map中bin的数量。因此我们分析当loadFactor<1时和loadFactor>1时的情况。

  • 当loadFactor<1的时候,这个时候表示map中实际的Entry数量是要小于map中bin的数量的,那么这个时候很少或者说不会出现hash冲突的情况,这个时候我们不管是执行put()方法还是执行get()方法都是O(1).但是我们这个时候需要的空间是要大于我们实际的Entry的数量的,因此这是一种用空间换时间来提高时间性能的方法。
  • 当loadFactor>1的时候,这个时候表示map中实际的Entry数量是要大于map中bin的数量,那么这个时候就必然会出现多个Entry放在同一个bin当中的情况,也就是会出现Hash冲突的情况。那么这个时候我们put()和get()方法可能就会比O(1)的时间稍大。因此这是一种使用时间换空间的方案来提高空间性能的方法。

——————————————————————————————————————————————————————

二、 构造方法

HashMap有四个构造方法,我们这里主要介绍其中的三个。我们可以通过这三个构造方法选择设置loadFactor和initialCapacity

public HashMap(int initialCapacity, float loadFactor)//自己设定初始容量和装载因子

public HashMap(int initialCapacity)//初始容量为传递参数值,负载因子为0.75

public HashMap()// 初始容量为16,负载因子为0.75

三、 添加键值对

3.1、put(K key,V value)方法源码分析
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //步骤一:判断table是否为空或者长度为零,是则初始化Map,不是则转向步骤二
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

        //步骤二:如果key映射到table中对应位置的链表为空,那么我们新添加的键值对就作为头结点
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //步骤三:key-value存在与Map中则直接覆盖当前的value值,如果不成立转到步骤四
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //步骤四:如果当前bin的数据结构是红黑树,则直接插入key-value到Tree中;反之,则转向步骤五
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //步骤五:遍历链表,如果链表中Node.key存在与key相等的Node,那么此时就覆盖value;
                //如果不存在,那么首先创建一个Node,然后将改Node添加在链表的末尾
                //添加完节点后,需要判断滨bin中的Node数量是否超过了对应的,如果超过了则将链表的结构转换成红黑树,否则不执行
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab,hash); //将链表转换成红黑树,但是要考虑到总的HashMap的大小是否超过了Min_Treefy_Capacity 
                        break; 
                    } 
                    if(e.hash == hash && 
                        ((k = e.key)== key ||(key!= null && key.equals(k))))
                        break; 
                    p = e; 
                } 
            } 
            if(e!= NULL){//
                oldValue = e.value; 
                if(!onlyIfAbsent || oldValue == null)
                    e.value = value; 
                afterNodeAccess(E); 
                返回oldValue; 
            } 
        }
        ++modCount;
        //步骤六:如果table中key-value的数量超过了规定的数量,那么此时就应该进行扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
3.2、执行步骤

步骤1、判断table是否为空或者长度为零,是则扩容;不是则跳过该步骤,转向步骤二

步骤2、如果key映射到table中对应位置的链表为空,那么我们新添加的键值对就作为头结点;如果该条件不成立则转向步骤三

步骤3、如果key映射到table中位置的链表(or 红黑树)的头结点的key相等,那么直接覆盖头结点的value值即可,如果不成立转到步骤四

步骤4、如果映射到table的中位值的头结点是TreeNode,那么调用putTreeVal方法插入键值对,如果此条件不成立则转向步骤五

步骤5、遍历链表,如果链表中Node.key存在与key相等的Node,那么此时就覆盖value;如果不存在,那么首先创建一个Node,然后将改Node添加在链表的末尾添加完节点后,需要判断bin中的Node数量是否超过了对应的,如果超过了则将链表的结构转换成红黑树,否则不执行

步骤6、如果map中key-value的数量size超过了threshold,那么此时就应该进行扩容

3.3、对于一个给定的key我们如果在table中找到其对应的位置?

主要使用下面两个步骤:
1、计算给定key的hashCode,将key的hashCode值与其高16位进行或运算得到key的hash值。

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

2、利用(table.length-1)&hash(key)就得到了key在table数组中的索引

(n - 1) & hash                    //在putVal()源码中步骤二中

四、扩容机制

    final Node<K,V>[] resize() {
        //步骤一:下面这段代码部分主要用于确定新的table的capacity和threshold
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;

        if (oldCap > 0) {
            //如果之前的oldTable的容量已经不可扩充了,那么直接返回
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //newTable的容量首先扩容为之前的两倍,如果扩容之后比默认初始容量要大
            //但是小于最大可扩容的容量,此时扩容的门限值也变成之前的两倍
            //根据threshold = capacity*loadfactor,我们的loadfactor是不变的,因此newThr相应翻倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        //如果table的容量已经初始化,那么
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        //如果table未经初始化,那么我们table的capacity和threshold就先初始化
        else {//零初始阈值表示使用默认值
            newCap = DEFAULT_INITIAL_CAPACITY; 
            newThr =(int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 
        } 
        if(newThr == 0){ 
            float ft =(float)newCap * loadFactor; 
            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];         //步骤二:将oldTable的key-value重新散列到表中        table = newTab;
            //遍历table数组
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //如果oldTable的table[j]处只有一个节点
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    //如果oldTable的索引j处存储的是一个red-black tree
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);

                    //如果oldTable的第table[j]条链表有多个节点的情况
                    else { // preserve order
                        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,那么此时(e.hash & oldCap) == 0
                            //也就是说e.hash&(newCap - 1) = e.hash
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //如果e.hash值小于oldCap,那么此时(e.hash & oldCap) == 1
                            //也就是说e.hash&(newCap - 1) = e.hash + oldCap
                            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;
                        }
                    }
                }
            }
        }
        return newTab;
    }

步骤总结:
1、首先判断我们的map的size是否已经达到了最大设定的值2的30次幂,如果是,那么我们调整threshold就可以了,否则调整
例1, oldCap = 16;hash(key1) = 8;newCap = 32

变量名对应二进制&后的结构
hash(key1)0000 0000 0000 0000 0000 0000 0000 1000
oldCap-10000 0000 0000 0000 0000 0000 0000 11110000 0000 0000 0000 0000 0000 0000 1000
oldCap0000 0000 0000 0000 0000 0000 0001 00000000 0000 0000 0000 0000 0000 0000 0000
newCap-10000 0000 0000 0000 0000 0000 0001 11110000 0000 0000 0000 0000 0000 0000 1000

从这个例子我们可以看到,如果我们的oldCap=16,那么我们key1 用

hash(key1)&oldCap + hash(key1) =hash(key1)&(newCap) hash(key1)&oldCap + hash(key1) =hash(key1)&(newCap)

也就是说如果我们的在扩容之前,如果key的hash值小于扩容之前的容量(oldCap),那么不管在扩容之前还是扩容之后key就会映射到与其hash值相同的位置
例1, oldCap = 16;hash(key1) = 17;newCap = 32

变量名对应二进制&后的结构
hash(key1)0000 0000 0000 0000 0000 0000 0001 0001
oldCap-10000 0000 0000 0000 0000 0000 0000 11110000 0000 0000 0000 0000 0000 0000 0001
oldCap0000 0000 0000 0000 0000 0000 0001 00000000 0000 0000 0000 0000 0000 0001 0000
newCap-10000 0000 0000 0000 0000 0000 0001 11110000 0000 0000 0000 0000 0000 0001 0001

从这个例子我们可以看到,如果我们的oldCap=16,那么我们key1 用hash(key1)&(oldCap-1) + hash(key1) = Bin_index;也就是说如果我们的在扩容之前,如果key的hash值小于扩容之前的容量(oldCap),那么不管在扩容之前还是扩容之后key就会映射到与其hash值相同的位置

五、 红黑树

5.1红黑树的规则
  • 根节点必须是黑色的
  • 一个红节点的子节点必须是黑色的
  • 黑节点的子节点可以是红色或者是红色的
  • 一个null节点必须是黑色的
  • 对于每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点
5.2红黑树的插入操作
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
            x.red = true;
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
                if ((xp = x.parent) == null) {//表示如果这个时候为根节点了,那么直接将根节点的颜色置为black(false)
                    x.red = false;
                    return x;
                }
                else if (!xp.red || (xpp = xp.parent) == null)//如果x节点的父节点不是红色(true),那么也就不会违反红黑树的性质
                    return root;
                if (xp == (xppl = xpp.left)) {//如果p[x]是p[p[x]]的左节点,这是第一类大情况
                    if ((xppr = xpp.right) != null && xppr.red) {//如果p[p[x]].right是红色,那么直接将p[x]和p[p[x]].right改变颜色为black(false),那么将p[p[x]]向上递归直到满足红黑树条件
                        xppr.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {//如果p[p[x]].right为null或者颜色为black,那么这个时候我们就需要进行左旋或者右旋然后改变颜色
                        if (x == xp.right) {如果x是p[x].right,那么需要进行两次旋转,第一次左旋,接下来右旋
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {//如果x是p[x].left,那么我们只需要进行一次右旋就可以了
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                else {//如果p[x]是p[p[x]]的右节点,这是第一类大情况
                    if (xppl != null && xppl.red) {
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        if (x == xp.left) {
                            root = rotateRight(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值