HashMap复习

一、构造函数:

//默认的构造函数
   public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    //指定容量大小
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
     //指定容量大小和负载因子大小
    public HashMap(int initialCapacity, float loadFactor) {
        //指定的容量大小不可以小于0,否则将抛出IllegalArgumentException异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
         //判定指定的容量大小是否大于HashMap的容量极限
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
         //指定的负载因子不可以小于0或为Null,若判定成立则抛出IllegalArgumentException异常
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
         
        this.loadFactor = loadFactor;
        // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
        this.threshold = tableSizeFor(initialCapacity);
    }

二、哈希的实现方式

JDK版本实现方式节点数>=8节点数<=6
1.8以前 数组+单向链表 数组+单向链表 数组+单向链表
1.8以后 数组+单向链表+红黑树 数组+红黑树 数组+单向链表

  HashMap由 数组+链表+红黑树实现,主要的目的是提高查找效率。

三、HashMap的负载因子为什么是2的n次幂?

  负载因子默认是0.75, 2^n是为了让散列更加均匀,例如出现极端情况都散列在数组中的一个下标,那么hashmap会由O(1)复杂退化为O(n)的。

四、Hashmap的容量为什么是2的n次幂

  一个哈希表,为了减少哈希碰撞,我们首先想到的方法是取余运算,因为取余就可以使得整数均匀地分布到哈希表的每一个位置上。比如现在的哈希表的长度是16,那么11如果要放到哈希表上,那就用11%16,得到了11,那么11就放在索引为11的位置。但是呢,计算机的取余操作效率不高,而位运算的效率是很高的,所以为了提高效率,java的工程师在原码里面就用了x&(length-1)的操作,这里的长度16-1 = 15,11和15做位运算,就是11&(1111),最后得到的结果也是11,也就是说,x&(length-1)的结果是和x%length的结果是一样的,因为当length为2的整数次幂时,length-1的二进制位数上都为1,这样就保证了取余和位运算的结果相等,所以原码为了提高效率就要求长度为2的整数次幂。

五、为什么HashMap线程不安全?

1、put的时候导致的多线程数据不一致。
  这个问题比较好想象,比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的桶索引和线程B要插入的记录计算出来的桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。
  

2、另外一个比较明显的线程不安全的问题是HashMap的get操作可能因为resize而引起死循环(cpu100%)。

  这种情况发生在HashMap自动扩容时,当2个线程同时检测到元素个数超过 数组大小 × 负载因子。此时2个线程会在put()方法中调用了resize(),两个线程同时修改一个链表结构会产生一个循环链表,接下来再想通过get()获取某一个元素,就会出现死循环。

posted on 2020-03-18 20:14  方块鲍勃  阅读(177)  评论(0编辑  收藏  举报

导航