zoukankan      html  css  js  c++  java
  • HashMap源码解析

    hashMap数据结构图:

    HashMap特点:

    1. 允许一个记录的键为null;
    2. 允许多条记录的值为null;

    3. 非线程安全,任意时刻多线程操作hashmap,有可能导致数据不一致,可以通过Collections的synchronizedMap来实现Map的线程安全或者使用concurrentHashMap。

    HashMap是链表+数组结构组成,底层是数组,数组元素是单向链表。当产生hash碰撞事件,意味着一个位置插入多个元素,这个时候数组上面就会产生链表。

    通过hashcode的高16位实现的,能保证数组table的length比较小的时候,保证高低bit都参与到hash计算中,不会有大的开销。

    static final int hash(Object key) {
            int h;
         // h = key.hashCode() 为第一步 取hashCode值
         // h ^ (h >>> 16)  为第二步 高位参与运算
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }

    根据key的hash值进行value内容的查找

     public V get(Object key) {
            Node<K,V> e;
            return (e = getNode(hash(key), key)) == null ? null : e.value;
        }
    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) {
                if (first.hash == hash && // always check first node
                    ((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;
        }

    put实现:

    对key的hashCode()进行hashing,并计算下标( n-1 & hash),判断该位置元素是否存在,不存在,创建Node元素,存在产生碰撞,则利用key.equals()方法去链表或树中去查找对应的节点。

    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) {
        //tab数组,p每个桶
            Node<K,V>[] tab; Node<K,V> p; int n, i;
        //tab为空创建tab
            if ((tab = table) == null || (n = tab.length) == 0)
                //resize进行扩容
                n = (tab = resize()).length;
        //index = (n - 1) & hash 下表位置
            if ((p = tab[i = (n - 1) & hash]) == null)
                //创建一个新的Node元素
                tab[i] = newNode(hash, key, value, null);
            else {
                //指定位置已经有元素,也就是说产生hash碰撞
                Node<K,V> e; K k;
                //判断节点是否存在,覆盖原来原来的value
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof 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);
                            //TREEIFY_THRESHOLD默认为8,大于8,转换为红黑树
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                //如果达到这个阈值转为红黑树
                                treeifyBin(tab, hash);
                            break;
                        }
                        //如果节点key存在,则覆盖原来位置的key
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                //检查e是否存在相应的key,如果存在就更新value,并且返回
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            //判断hashmap是否需要resize扩容
            if (++size > threshold)
                resize();
            //留给子类LinkedHashMap来实现的
            afterNodeInsertion(evict);
            return null;
        }

    resize实现:HashMap扩容实现:使用一个新的数组代替已有的容量小的数组。

    final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;            //创建一个oldTab数组用于保存之前的数组
            int oldCap = (oldTab == null) ? 0 : oldTab.length;    //获取原来数组的长度
            int oldThr = threshold;                //原来数组扩容的临界值
            int newCap, newThr = 0;
            if (oldCap > 0) {
                if (oldCap >= MAXIMUM_CAPACITY) {    //如果原来的数组长度大于最大值(2^30)
                    threshold = Integer.MAX_VALUE;    //扩容临界值提高到正无穷
                    return oldTab;                    //返回原来的数组,也就是系统已经管不了了,随便你怎么玩吧
                }
                //else if((新数组newCap)长度乘2) < 最大值(2^30) && (原来的数组长度)>= 初始长度(2^4))
                //这个else if 中实际上就是咋判断新数组(此时刚创建还为空)和老数组的长度合法性,同时交代了,
                //我们扩容是以2^1为单位扩容的。下面的newThr(新数组的扩容临界值)一样,在原有临界值的基础上扩2^1
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold
            }
            else if (oldThr > 0)
                newCap = oldThr;    //新数组的初始容量设置为老数组扩容的临界值
            else {               // 否则 oldThr == 0,零初始阈值表示使用默认值
                newCap = DEFAULT_INITIAL_CAPACITY;    //新数组初始容量设置为默认值
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);        //计算默认容量下的阈值
            }
            if (newThr == 0) {    //如果newThr == 0,说明为上面 else if (oldThr > 0)
            //的情况(其他两种情况都对newThr的值做了改变),此时newCap = oldThr;
                float ft = (float)newCap * loadFactor;    //ft为临时变量,用于判断阈值的合法性
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                          (int)ft : Integer.MAX_VALUE);        //计算新的阈值
            }
            threshold = newThr; //改变threshold值为新的阈值
            @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;    //改变table全局变量为,扩容后的newTable
            if (oldTab != null) {
                for (int j = 0; j < oldCap; ++j) {    //遍历数组,将老数组(或者原来的桶)迁移到新的数组(新的桶)中
                    Node<K,V> e;
                    if ((e = oldTab[j]) != null) {    //新建一个Node<K,V>类对象,用它来遍历整个数组
                        oldTab[j] = null;
                        if (e.next == null)
                                    //将e也就是oldTab[j]放入newTab中e.hash & (newCap - 1)的位置,
                            newTab[e.hash & (newCap - 1)] = e;    //这个我们之前讲过,是一个取模操作
                        else if (e instanceof TreeNode)        //如果e已经是一个红黑树的元素,这个我们不展开讲
                            ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                        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) {
                                    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;
                            }
                        }
                    }
                }
            }
            return newTab;
        }

    工作原理总结:

    通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。

  • 相关阅读:
    Mvc+三层(批量添加、删除、修改)
    js中判断复选款是否选中
    EF的优缺点
    Git tricks: Unstaging files
    Using Git Submodules
    English Learning
    wix xslt for adding node
    The breakpoint will not currently be hit. No symbols have been loaded for this document."
    Use XSLT in wix
    mfc110ud.dll not found
  • 原文地址:https://www.cnblogs.com/androidsuperman/p/7517795.html
Copyright © 2011-2022 走看看