深入解读HashMap

讲讲HashMap?

源码解析

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
  			//辅助变量
        Node<K,V>[] tab; Node<K,V> p; int n, i;
 				//如果当前tabe数组是null,数量是0的话
        if ((tab = table) == null || (n = tab.length) == 0)
        		//执行扩容方法resize()
       			//初始化数组默认大小是16
            n = (tab = resize()).length;
  			//通过hash与运算获得数组索引位置,该位置是null
        if ((p = tab[i = (n - 1) & hash]) == null)
          	//创建新Node对象
            tab[i] = newNode(hash, key, value, null);
        else {//该索引位置不为null
            Node<K,V> e; K k;
          	//当前table索引hash和新的索引hash相同 && 
            //(当前table节点的key和新key是同一对象 || equals为真)
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
          	//如果当前table的node已是红黑树就按红黑树处理
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //如果节点是后面是链表就遍历比较
                for (int binCount = 0; ; ++binCount) {
                 	  //遍历整个链表结束,也没有和新的key相同的就添加链表后面新创建node
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                      //加入后判断当前的链表个数是否已经达到8个,如果达到就进行红黑树转换
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                          //提示:
                          //treeifyBin里如果table为null或者大小小于64,暂时不会转化为红黑树
                          //而是进行扩容。
                          //if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                            treeifyBin(tab, hash);
                        break;
                    }
                  	//发现相同就break结束遍历
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;  //替换原有的值,直接结束
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;//每增加node,Size++
  			//如果数量大于临界值,就进行扩容
  			//threshold=table大小(默认16)*负载因子(默认0.75)
  			//12>24>>48
        if (++size > threshold)
          	//扩容成2倍增长16->32->64
            resize();
        afterNodeInsertion(evict);
        return null;
    }

简述

Jdk1.7 数组+链表

Jdk1.8 数组+链表+红黑树

hashMap 默认数组大小16,负载因子 默认0.75, 临界值= 数组大小*负载因子。

  1. 首先将key进行hash算法,key的hashCode右移16位并进行异或运算。
  2. 如果table是null ,先初始化数组默认大小是16
  3. 通过hash的与运算获得当前table索引位置,如果索引位置内容是null则创建新Node节点对象
  4. 如果当前table索引位置里内容不为null,则寻找相同key直接修改值,分三种情况
    1. 当前table索引的对象hash是否与新hash相同并且当前table位置对象key是否和新key相同
    2. 如果当前table索引的对象已是TreeNode(红黑树)就进行遍历树方式查找
    3. 如果是链表则遍历链表查找,如果遍历完也没找到就往链表尾部加入并创建新Node,在判断当前链表个数是否大于等于8,如果大于就进行红黑树转化。执行红黑树转化方法时有个条件,如果数组大小小于64则还是进行扩容操作。
  5. 当前Size数组大小和threshold(临界值)比较,如果size大于threshold则进行扩容(2倍增长),

问题

JDK 1.8中对hash算法和寻址算法是如何优化的?
//hash算法优化
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//(n - 1) & hash 寻址算法优化   
if ((p = tab[i = (n - 1) & hash]) == null)

配合来说,put键值时,肯定涉及数组长度的取模运算,但是在计算机上面(n - 1) & hash位运算效率高于普通的除法取余。关键这个n(hashmap长度)通常会很小,但是hash值是32位的,因此(n-1)大概率高位补零,由于与运算是只有两个数字都是1结果才为1,其他情况均为0,因此hash值的高16位永远起不到作用,这样就大大的增加了寻址冲突概率,除非数组长度很大,撑满整个32位最好都为1,才不会有冲突,但一般不可能很长,所以通过hash算法的优化(h = key.hashCode()) ^ (h >>> 16) 这个公式完美的让hash值高16位与低16位融合,保留高低16位的特征。再跟(n-1)做与运算,hash冲突的概率就降低了!

HashMap是如何进行扩容的?

hashmap的底层是数组,因此需要在元素个数达到一定的阈值以后进行扩容的操作。 扩容的过程就涉及到rehash的操作,也就是所有元素的坐标再次定位到新的数组上的位置上。 如果该桶单元只有一个数据:直接e.hash & (newCap - 1)重新计算新桶的位置 如果该桶单元是一个链表:(e.hash & oldCap) == 0 根据与旧桶的容量,判断在新桶中的位置是原位置,还是原位置+oldCap,链表顺序不变,分成两部分,一部分放到新桶中原位置,一部分放到新桶中原位置+oldCap,可以看到这里并不是避免了定位的与运算,而是避免了链表数据进入新桶多次的hash冲突。 如果该桶单元是一个红黑树:(e.hash & bit) == 0与桶单元是链表逻辑类似,判断在新桶中的位置是原位置,还是原位置+oldCap,红黑树顺序不变,分成两部分,一部分放到新桶中原位置,一部分放到新桶中原位置+oldCap,同时将根节点放到桶单元中会判断树中数据长度,小于等于6转换成链表。原理一致,多了转换成链表的判断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值