美文网首页
57hashmap8 和hashmap7之间区别以及扩容解决死循

57hashmap8 和hashmap7之间区别以及扩容解决死循

作者: 滔滔逐浪 | 来源:发表于2020-09-24 07:36 被阅读0次

    jdk1.8 核心参数:

    HashMap 初始容量

    /**
    * The default initial capacity - MUST be a power of two.
    */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    hashMap 最大容量

    /**
    * The maximum capacity, used if a higher value is implicitly specified
    * by either of the constructors with arguments.
    * MUST be a power of two <= 1<<30.
    */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    hashmap加载因子 提前扩容

    /**
    * The load factor used when none specified in constructor.
    */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    链表长度大于8的时候,将链表转为红黑数

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     */
    static final int TREEIFY_THRESHOLD = 8;
    

    红黑树长度小于6的时候转换为链表

    /**
     * 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;
    

    (数组容量>=64&链表长度大于8)链表转红黑树

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     */
    static final int MIN_TREEIFY_CAPACITY = 64;
    

    底层采用单向链表

    /**
    * Basic hash bin node, used for most entries. (See below for
    * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
    */

      final int hash;
        final K key;
        V value;
        Node<K,V> next;
    

    将key的hash 保存起来是为了下次扩容的时候能够计算该key在新的table中index的值。

    table数组 类型: 单向链表

    /**
    * The table, initialized on first use, and resized as
    * necessary. When allocated, length is always a power of two.
    * (We also tolerate length zero in some operations to allow
    * bootstrapping mechanics that are currently not needed.)
    */
    transient Node<K,V>[] table;

    /**
    * The number of key-value mappings contained in this map.
    */
    transient int size;

    transient  不能被序列化
    

    遍历hashmap集合的时候防止被篡改

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient int modCount;
    

    加载因子

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    final float loadFactor;
    

    hashmap put 方法底层实现;

    n=table数组的长度 index=key 存放在那个index下标位置。tab 和p临时table大小接收。

    1. Node<K,V>[] tab; Node<K,V> p; int n, i;

    2,将全局table=tab 判断是否为空,如果为空的情况下或者长度为0 开始对table实现扩容。
    if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;//懒加载默认扩容大小为16

    3,计算key index的值判断是否有发生index冲突,如果没有发生index冲突
    if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);

    获取原来的table长度为0;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;

    0 hashmap 下一次提前扩容的大小

    int oldThr = threshold;

    记录新的table容量,新下一次扩容的大小。

    int newCap, newThr = 0;

    存放index下标的位置

    4, tab[i] = newNode(hash, key, value, null);
    5,##如果hash值相同且equals也相同就覆盖值
    if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;

    将新的value覆盖给老的value

    if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
    e.value = value;
    afterNodeAccess(e);
    return oldValue;
    }

    遍历链表,如果链表为空的情况下,直接追加在next后面。

    for (int binCount = 0; ; ++binCount) {
    if ((e = p.next) == null) {
    p.next = newNode(hash, key, value, null);
    //如果链表长度大于8且长度>64直接转为红黑树
    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    treeifyBin(tab, hash);
    break;
    }
    //查找链表中是否包含key如果存在key直接修改value
    if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))
    break;
    p = e;
    }

    modcount++ HashMap结果集 集合线程不安全
    A 线程 遍历 hashmap结果集 B线程向hashMap 存放key
    modCount 新增++fastclass机制
    java.util.HashMap 不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。这一策略在源码中的实现是通过 modCount 域,modCount 顾名思义就是修改次数,对HashMap 内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了 Map:

    
    package com.taotao.hashmap001;
    
    import java.util.HashMap;
    
    /**
     *@author tom
     *Date  2020/9/24 0024 8:09
     *
     */
    public class Test11 {
        public static void main(String[] args) {
            HashMap<Object,String> hashmap=new HashMap<>();
            for (int i = 0; i < 12; i++) {
                hashmap.put(i,i+"");
            }
            hashmap.forEach((k,v)->{
                hashmap.put(13,13+"");
                System.out.println(k+","+v);
            });
        }
    }
    
    
    
    api\1.7.30\slf4j-api-1.7.30.jar" com.taotao.hashmap001.Test11
    0,0
    Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.HashMap.forEach(HashMap.java:1291)
        at com.taotao.hashmap001.Test11.main(Test11.java:16)
    
    Process finished with exit code 1
    
    
    

    遍历hashmap的时候改变了hashmap 的长度 所以 报错 modCount

    
    public void forEach(BiConsumer<? super K, ? super V> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.key, e.value);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    
    

    扩容:
    HashMap7扩容产生死循环问题

    
    oid 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;
          }
      }
    }
    

    HashMap1.7 扩容 16*2=32
    hashmap1.8 扩容 16<<1 位移

    扩容的时候原来的tablekey 转移到新的table 数组中
    重新计算时候 has 值不变 node节点中保存计算好hash值。只是改变下标
    误区:没有重新计算hash值

    异或运算^无符号右移>>>&与运算
    1.<<(向左位移) 针对二进制,转换成二进制后向左移动2位,后面用0补齐
    10的二进制1010
    0000 0000 0000 0000 0000 0000 0000 1010 ---32位
    0000 0000 0000 0000 0000 0000 0010 1000
    System.out.println(10 << 2);//1010 =40
    2.>>(向右位移) 针对二进制,转换成二进制后向右移动2位,操作数移除右边界的位被屏蔽 正数高位
    补0 负数补1
    10的二进制1010
    0000 0000 0000 0000 0000 0000 0000 1010
    System.out.println(10 >> 2);//10=2
    3.>>>(不带符号右移) 针对二进制,转换成二进制后向右移动2位,操作数移除右边界的位被屏蔽
    正数高位 补0 负数补0

    异或运算^ 针对二进制,相同的为0,不同的为1
    0010 --2
    0011 --3
    0001 --1
    4.&(与运算) 针对二进制,00的0 11的1 10 的0
    0010--2
    0011--3
    0010--2

    HashMap如何降低Hash冲突概率

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    ((p = tab[i = (n - 1) & hash])
    

    1、保证不会发生数组越界
    首先我们要知道的是,在HashMap,数组的长度按规定一定是2的幂。因此,数组的长度的二进制形式是:10000…000, 1后面有偶数个0。 那么,length - 1 的二进制形式就是01111.111, 0后面有偶数个1。最高位是0, 和hash值相“与”,结果值一定不会比数组的长度值大,因此也就不会发生数组越界。一个哈希值是8,二进制是1000,一个哈希值是9,二进制是1001。和1111“与”运算后,结果分别是1000和1001,它们被分配在了数组的不同位置,这样,哈希的分布非常均匀。

    HashMap8扩容底层原理

    将原来的链表拆分两个链表存放; 低位还是存放原来index位置 高位存放index=j+原来长度
    if ((e.hash & oldCap) == 0) { 由于oldCap原来的容量没有减去1 所以所有的hash&oldCap
    为0或者1;

    HashMap加载因子为什么是0.75而不是1或者0.5
    产生背景:减少Hash冲突index的概率;
    查询效率与空间问题;

    简单推断的情况下,提前做扩容:
    1.如果加载因子越大,空间利用率比较高,有可能冲突概率越大;
    2.如果加载因子越小,有可能冲突概率比较小,空间利用率不高;
    空间和时间上平衡点:0.75
    统计学概率:泊松分布是统计学和概率学常见的离散概率分布

    HashMap如何存放1万条key效率最高

    image.png

    (需要存储的元素个数 / 负载因子) + 1
    10000/0.75+1=13334

    正常如果存放1万个key的情况下 大概扩容10次=16384

    相关文章

      网友评论

          本文标题:57hashmap8 和hashmap7之间区别以及扩容解决死循

          本文链接:https://www.haomeiwen.com/subject/oornyktx.html