jdk7和jdk8HashMap主要的区别

1.在上篇HashMap博客中,已经提到jdk7使用的是数组+链表来实现,而jdk8使用数组+链表+红黑树实现,那为什么要引入红黑树呢,原因就是哪怕将hash的碰撞降到最低,也不能避免链表会越来越长。

[1].链表属于插入快,遍历慢的数据结构

[2].完全二叉树插入慢,遍历快

[3].红黑树插入和查询都较快

[4].jdk7中的put方法

public V put(K key, V value) {

    // HashMap允许存放null键和null值。

    // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。

    if (key == null)

        return putForNullKey(value);

    // 根据key的keyCode重新计算hash值。

    int hash = hash(key.hashCode());

    // 搜索指定hash值在对应table中的索引。

    int i = indexFor(hash, table.length);

    // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。

    for (Entry<K,V> e = table[i]; e != null; e = e.next) {

        Object k;

        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

            V oldValue = e.value;

            e.value = value;

            e.recordAccess(this);

            return oldValue;

        }

    }

    // 如果i索引处的Entry为null,表明此处还没有Entry。

    modCount++;

    // 将key、value添加到i索引处。

    addEntry(hash, key, value, i);

    return null;

}

[5].jdk8中的put方法

     public V put(K key, V value) {

          return putVal(hash(key), key, value, false,  true);

     }

     /**

      * Implements Map.put and related methods

      *

      * @param hash

      *            hash for key

      * @param key

      *            the key

      * @param value

      *            the value to put

      * @param onlyIfAbsent

      *            if true, don't change existing value

      * @param evict

      *            if false, the table is in creation  mode.

      * @return previous value, or null if none

      */

     final V putVal(int hash, K key, V value, boolean  onlyIfAbsent, boolean evict) {

          Node<K, V>[] tab;

          Node<K, V> p;

          int n, i;

          // 如果当前map中无数据,执行resize方法。并且返回n

          if ((tab = table) == null || (n = tab.length) ==  0)

              n = (tab = resize()).length;

          // 如果要插入的键值对要存放的这个位置刚好没有元素,那么把他封装成Node对象,放在这个位置上就完事了

          if ((p = tab[i = (n - 1) & hash]) == null)

              tab[i] = newNode(hash, key, value, null);

          // 否则的话,说明这上面有元素

          else {

              Node<K, V> e;

              K k;

              // 如果这个元素的key与要插入的一样,那么就替换一下,也完事。

              if (p.hash == hash && ((k = p.key) == key  || (key != null && key.equals(k))))

                   e = p;

              // 1.如果当前节点是TreeNode类型的数据,执行putTreeVal方法

              else if (p instanceof TreeNode)

                   e = ((TreeNode<K, V>)  p).putTreeVal(this, tab, hash, key, value);

              else {

                   // 还是遍历这条链子上的数据,跟jdk7没什么区别

                   for (int binCount = 0;; ++binCount) {

                        if ((e = p.next) == null) {

                             p.next = newNode(hash, key,  value, null);

                             // 2.完成了操作后多做了一件事情,判断,并且可能执行treeifyBin方法

                             if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st

                                  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)  // true || --

                        e.value = value;

                   afterNodeAccess(e);

                   return oldValue;

              }

          }

          ++modCount;

          // 判断阈值,决定是否扩容

          if (++size > threshold)

              resize();

          afterNodeInsertion(evict);

          return null;

     }

2.hash算法简化

[1].jdk7的hash算法

    final int hash(Object k) {

        int h = hashSeed;

        if (0 != h && k instanceof String) {

            return sun.misc.Hashing.stringHash32((String)  k);

        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that  differ only by

        // constant multiples at each bit position have a  bounded

        // number of collisions (approximately 8 at  default load factor).

        h ^= (h >>> 20) ^ (h >>> 12);

        return h ^ (h >>> 7) ^ (h >>> 4);

    }

[2].jdk8中的hash算法

    static final int hash(Object key) {

        int h;

        return (key == null) ? 0 : (h =  key.hashCode()) ^ (h >>> 16);

    }

[3].为什么在jdk8中要简化hash算法:jdk8之前之所以hash方法写的比较复杂,主要是为了提高散列行,进而提高遍历速度,但是jdk8以后引入红黑树后大大提高了遍历速度,继续采用复杂的hash算法也就没太大意义,反而还要消耗性能,因为不管是put()还是get()都需要调用hash()

3.新节点插入顺序:jdk7在头部插入,jdk8在尾部插入

[1].这里需要提一下两个jdk8新增的属性TREEIFY_THRESHOLD是树化阀值,TREEIFY_THRESHOLD是链表化阀值,当链表上的数据大于等于树化阀值,链表转化为红黑树,反之小于等于链表化阀值时转为链表

    static final int TREEIFY_THRESHOLD = 8;

    /**

     * The bin count threshold for untreeifying  a (split) bin during a

     * resize operation. Should be less than  TREEIFY_THRESHOLD, and at

     * most 6 to mesh with shrinkage detection  under removal.

     */

    static final int UNTREEIFY_THRESHOLD= 6;

[2].因为并没有对每个链表(红黑树)进行记录元素个数,所以每次都是通过遍历得来的元素个数,所以在遍历完后就顺带插入到尾部

4.扩容机制

[1].jdk7中的扩容

    void resize(int newCapacity) {

        Entry[] oldTable = table;

        int oldCapacity = oldTable.length;

        if (oldCapacity == MAXIMUM_CAPACITY) {

            threshold = Integer.MAX_VALUE;

            return;

        }

        Entry[] newTable = new Entry[newCapacity];

        transfer(newTable,  initHashSeedAsNeeded(newCapacity));

        table = newTable;

        threshold = (int)Math.min(newCapacity *  loadFactor, MAXIMUM_CAPACITY + 1);

    }

    /**

     * Transfers all entries from current table to  newTable.

     */

    void transfer(Entry[] newTable, boolean rehash) {

        int newCapacity = newTable.length;

        for (Entry<K,V> e : table) {

            while(null != e) {

                Entry<K,V> next = e.next;

                if (rehash) {

                    e.hash = null == e.key ? 0 :  hash(e.key);

                }

                int i = indexFor(e.hash, newCapacity);

                e.next = newTable[i];

                newTable[i] = e;

                e = next;

            }

        }

    }

[2].核心就是transfer()方法,2个循环

①.对索引数组中的元素遍历

②.对链表上的每一个节点遍历:用 next 取得要转移那个元素的下一个,将 e 转移到新 Hash 表的头部,使用头插法插入节点(所以元素的位置在链表中的物理体现是跟之前反着的)。

③.循环2,直到链表节点全部转移

④.循环1,直到所有索引数组全部转移

[3].问题:在多线程下当2个线程操作统一条链表就有可能出现链表闭合

①.当两个线程调用put()方法,且都满足扩容的条件(new了两个新数组),其中一条链表是3→5

②.线程一进到transfer(),把 Entry<K,V> next = e.next;执行完就挂起了,当前e记录的是3,next是5

③.线程二进到这个方法将扩容完成,此时由于jdk7的扩容后链表会反转,所以是5→3

④.线程一被唤醒,将e(3)插到线程一的某个链表的表头,把next(5)值赋给e

⑤.由于5的下个节点是3,故把3赋给next,将e(5)插到线程一对应链表的表头,现在的指向是3→5,把next(3)赋给e

⑥.e的next为null(不重要),把e(3)再指放到链表的表头,把3指向5,此时,闭环就形成了,3的next是5,5的next也是3

[4].由于jdk8是顺序拷贝,所以就不会产生jdk7的死锁

[5].jdk7扩容后hash值会发生变化,因为hashSeed值随着容量增长,,而jdk8根据该扩容长度调整存元素放位置

    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;

    }

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值