HahMap底层实现

HashMap底层实现

  HashMap是用途非常大的集合结构,它虽然不是线程安全的,但是在单线程下使用足够了,线程安全的结构是ConcurrentHashMap,在juc包下;当然还有一种HashTable也是线程安全的,但是它的效率非常低,因为它是通过在方法上直接加Synchronized实现的,导致有一个线程访问put方法时,别的线程无法访问get方法,因为他们都是同步方法。好了,继续讲解HashMap。
jdk1.7和jdk1.8中HashMap的结构不一样,1.8在1,7的基础上做出了改进:

  • 1.7中底层结构采用的是数组+链表,1.8为了提升查找效率,引入了红黑树结构,即采用数组+链表+红黑树;
  • 1.7中扩容时,因为多线程可能会导致多个死循环(两个线程同时进行扩容,会出现B->A->B环),1.8对此做出了改进,不再是单个元素进行头插入法,而是将一个链表分为两个子链表(根据key的hash值的最高位是0还是1区分),直接将子链表挂到新桶中。

延迟初始化

  为了提高性能,HashMap在实例化的时候,并不直接开辟空间,而是在第一次put元素(put和putAll)时才会对数组进行初始化。
  数组默认初始容量是16,负载因子是0.75,扩容大小为2^n。

put方法

  1. 判断数组是否为空,若是,则先进行初始化;
  2. 根据新元素key的hashcode计算hash值,并将其与数组长度-1相与,得到在数组中的下标,即对新元素进行定位;
  3. 如果数组当前位置为null,则直接将新元素放入桶中,返回;
  4. 否则,判断当前元素的下一个元素是树节点,还是普通链表节点;
  5. 如果是树节点,则将新元素插入到红黑树中,具体插入是:比较当前元素的hashcode,如果新元素小,则放入左子树中,否则放入右子树中,返回;
  6. 如果是普通链表节点,则遍历链表节点,若找到了与新元素相等(hashcode相同,equals返回true)的节点,则使用新的value直接替换旧的(也可以不替换,put方法上有一个参数设置:为true时,不替换旧值;为false时,替换),并返回;
  7. 否则,将新元素插入到链表结尾,并判断,当前链表长度是否大于8;
  8. 如果大于8,再判断当前数组大小是否大于64,如果大于,则将链表转换为红黑树,否则,直接进行扩容;
  9. 插入元素完毕后,判断当前HashMap中元素个数是否超过了数组大小*负载因子;
  10. 如果超过了,则进行扩容;否则,返回。
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)    //定位到的桶是空的,则直接放入新元素
           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))))    //判断桶中第一个元素是否与新元素相同
               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);
                       if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st    //是否需要转换为红黑树
                           treeifyBin(tab, hash);    //转换为树,当前之前需要判断数组大小是否大于64
                       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方法

  1. 判断当前数组是否为空,若是,则返回null;
  2. 否则,根据待查找key的hash值定位桶;
  3. 判断桶中第一个元素是否是待查找元素,如果是,则直接返回;
  4. 否则,判断下一个元素是树节点,还是链表节点;
  5. 如果是树节点,则在红黑树中查找节点;
  6. 否则,遍历链表;
  7. 找到元素,返回;否则,返回null。
/**
    * Implements Map.get and related methods
    *
    * @param hash hash for key
    * @param key the key
    * @return the node, or null if none
    */
   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;
   }

resize方法:扩容

  1. 判断一系列条件,并设置新桶的大小和阈值,这里省略(扩容后的大小为第一个大于原数组长度*负载因子的2的n次幂);
  2. 根据扩容后的大小创建一个新数组,并遍历旧数组,对数组中每个元素rehash到新数组中;
  3. 如果当前桶中只有一个元素,即没有发生hash冲突,则直接将桶中元素经过rehash,放入新桶的对应位置;
  4. 否则,判断当前桶中元素是否是树节点,如果是树节点,则对树进行分解,根据元素hash值的最高位为0,还是1,分为两个子树,判断子树中元素个数,若个数<=6,则将树还原为链表,将子树或链表放入新的数组中;
  5. 如果是链表节点,则根据hash值的最高位是0,还是1,将链表分为两个子链表0和1,并将他们定位新数组中,定位规则:如果是0链表,则将其挂到新数组的[原下标]处;如果是1链表,则将其挂到新数组的[原下标+旧数组大小]处;
  6. 返回新数组;
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 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
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            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];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    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;
                            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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值