HashMap

目录

数据结构

根据key获取哈希桶数组索引位置?

hash()算法?

put()方法的详细执行?

get()方法的详细执行?

reSize() 扩容?

为什么最大容量是  1 << 30?


参考:


数据结构

数组 + 链表 + 红黑树

Node数组:

Node链表节点的数据结构: 是一个单链表;

红黑树的树节点数据结构:

根据key获取哈希桶数组索引位置?

hash()算法?

这里的Hash算法本质上就是三步:取key的hashCode值、高位运算、取模运算

hash & (n - 1)  : 按位与

put()方法的详细执行?

   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)
            //数组空,初始化一个
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            //数组不为空,在相应的hash桶出无值,直接新建node写入
            tab[i] = newNode(hash, key, value, null);
        else {
            //在相应位置有值
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //判断了Hash桶相应位置是第一个值是否就是想要找的值,如果是,则直接替换即可
                e = p;
            else if (p instanceof TreeNode)
                //说明发生了hash碰撞 ,  先判断是否是一颗树
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //说明发生了hash碰撞 , 且不是红黑树,那就是单链表
                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
                            //追加到链表尾部后, 需要判断是否超过了链表长度最大值8,如果超过,就要转化为红黑树
                            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
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        
        ++modCount;
        //判断是否需要扩容;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

get()方法的详细执行?

get()  、hash(key)

getNode()

 /**
     * Implements Map.get and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final HashMap.Node<K,V> getNode(int hash, Object key) {
        HashMap.Node<K,V>[] tab;  //数组
        HashMap.Node<K,V> first; // 赋值 first = tab[(n - 1) & hash]
        HashMap.Node<K,V> e;
        int n; // n = tab.length  这里n是table数组的长度
        K k;
        // 如果table数组为空,说明没有元素,直接返回null
        // (n - 1) & hash 根据hash值和n-1按位与,计算数组下标位置,获取 元素节点first , 如果first == null, 说明没有元素,直接返回null
        if ((tab = table) != null && (n = tab.length) > 0  && (first = tab[(n - 1) & hash]) != null) {
            // always check first node  先判断第一个节点是否是, 经过hash()函数计算得到的hash值虽然相同,但可能发生碰撞, 当 key 也相同时,说明找到了, 直接返回;
            if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                //如果不是第一个节点, 则需要遍及链表或搜索红黑树
                if (first instanceof HashMap.TreeNode)
                    //红黑树获取
                    return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    //遍历单链表   通过hash()函数计算的hash值相等且 key相同,则返回;
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

如何从红黑树查找节点呢?

getTreeNode(hash, key)

 ((TreeNode<K,V>)first).getTreeNode(hash, key);  使用头节点first获取

parent就是first节点的父节点, 

如果parent ==null, 说明当前节点first就是红黑树的根节点; 不然 通过root()方法查找根节点;

reSize() 扩容?

 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    static final int MAXIMUM_CAPACITY = 1 << 30; //默认最大容量,  int类型的最大值是  1<<31=-2147483648, 1<<32 =1, 因此,左移31位已经是Int类型的数据能位移的最大值了
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    int threshold, loadFactor;

    final HashMap.Node<K,V>[] resize() {
        //老数组
        HashMap.Node<K,V>[] oldTab = table;
        //老数组长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //老数组扩容阈值
        int oldThr = threshold;
        int newCap = 0; //新数组容量
        int newThr = 0; //新数组的扩容阈值   扩容时,不会重新指定loadFactor,使用原加载因子或默认加载因子
        //(1) 有数组元素
        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)HashMap数组中无元素, 需要初始化, 初始化分为,指定了数组长度的初始化和未指定数组长度的初始化
        //注意,此时oldThr,也即threshold, 存的并不是阈值,而是new HashMap指定的容量, oldThr > 0说明指定了容量, 不大于0,说明为指定,是0, 则会使用默认值
        else if (oldThr > 0) // initial capacity was placed in threshold
            // (2.1)指定了数组长度的初始化: HashMap的静态方法中,会将数组长度放入threshold字段上, 如果大于0, 说明初始了这个一个长度的数据map
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            //(2.2) 未指定数组长度的初始化,使用默认值, 初始容量 DEFAULT_INITIAL_CAPACITY=16, 初始阈值 = 初始容量(DEFAULT_INITIAL_CAPACITY) * 默认加载因子(DEFAULT_LOAD_FACTOR)
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            //未给定新数组长度扩容阈值时(2.1步骤没给赋值)
            float ft = (float) newCap * loadFactor; //计算扩容阈值
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr; //赋值扩容阈值
        @SuppressWarnings({"rawtypes","unchecked"})
        //new 了一个的数组, 此处才是数组初始化, 数组是懒加载的, 只有put方法执行后,才会触发这里的new
        HashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new HashMap.Node[newCap];
        table = newTab;
        // 注意:当多线程场景下,获取数组长度、初始化新数组、多处判空等,都不是原子的,都有可能导致丢数据,线程不安全;
        if (oldTab != null) {
            //如果有元素,则进行扩容操作
            for (int j = 0; j < oldCap; ++j) {
                //遍历原数组
                HashMap.Node<K,V> e;
                // e = oldTab[j] 取出数组元素里的头元素
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        //说明次数只有一个元素, 不是链表,没有发生Hash碰撞, 则直接计算新数组下标并赋值,
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof HashMap.TreeNode)
                        //红黑树操作
                        ((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        //链表时
                        HashMap.Node<K,V> loHead = null, loTail = null; // loHead 指向链表头部,loTail 指向链表尾部, loHead 和 loTail 可以确定一个链表;
                        HashMap.Node<K,V> hiHead = null, hiTail = null; // hiHead 指向链表头部,hiTail 指向链表尾部, hiHead 和 hiTail 可以确定一个链表;
                        HashMap.Node<K,V> next; //用于遍历链表时保存next节点
                        do {
                            next = e.next;
                            //(e.hash & oldCap) == 0 说明,该节点e再新数组下标的索引值 与 其在老数组下标的索引值相等
                            // 否则,该节点e再新数组下标的索引值 = 其在老数组下标的索引值 + 老数组长度
                            if ((e.hash & oldCap) == 0) {
                                //下面5行,就是构建链表了
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                // 下面5行,也是构建链表了
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        
                        if (loTail != null) {
                            loTail.next = null; //把链表尾部的元素节点的next值置空,防止还链接着其他节点
                            newTab[j] = loHead; //loHead 为头节点的新链表中的所有节点, 经过e.hash & oldCap == 0运算可知, 在新数组的下标位置还是j, 不需要移动
                        }
                        if (hiTail != null) {
                            hiTail.next = null;// //把链表尾部的元素节点的next值置空,防止还链接着其他节点
                            newTab[j + oldCap] = hiHead; // hiHead 为头节点的新链表中的所有节点, 经过e.hash & oldCap != 0运算可知, 在新数组的下标位置 是j + oldCap, 需要移动oldCap个位置
                        }
                    }
                }
            }
        } else {
            //没有值,说明不是扩容, 只是数组初始化
        }
        return newTab;
    }

为什么最大容量是  1 << 30?

MAXIMUM_CAPACITY = 1 << 30;

首先,数组容量是Int类型, Int占 32位, 即  1 int = 4 byte = 32bit  ,   1byte = 8 bit,  即 1个字节占8位;

int的范围是   [ 2 ^-31 , 2^31 - 1] 即  [-2147483648, 2147483647];

从下图可知, 如果 1<< 31 时 超过了int的最大值, 变为负数, 因此不能继续向上扩容了 ;

 1073741824 + 1073741824 = 2147483648, 已经比int的最大值  2147483647 还大1, 因此占位溢出了,变成了负数, 可见, 1 << 30 是能扩容的最大值;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值