Hashmap你所应该了解的知识点,面试必备!

hashmap绝对是面试中的高频面试点了!整理以下hashmap你所应该掌握的知识!

1. 面试中有些人喜欢从底层结构开始问起

你了解hashmap的底层结构吗?
hashmap的底层结构在jdk1.7和jdk1.8是不同的。
jdk1.7是数组加链表的结构,采用头插法;jdk1.8是数组加链表加红黑树的结构,采用尾插法

为什么jdk1.7是数组加链表的结构呢?
hashmap采用数组加链表的方式解决哈希冲突,即两个不同的元素,通过哈希函数得出的实际存储地址相同,第二个元素要插入的时候发现位置已经被占用了,hashmap采用链地址法,也就是将两个元素分成前后关系,形成一个链表存放。

那为什么jdk1.8要改成数组加链表加红黑树呢?
为了提升查询效率。因为链表长度过长的时候,查询效率就变得很低,链表就会转化成红黑树。

为什么不直接用红黑树?
因为树节点所占空间是普通节点的两倍,所以只有当节点足够多的时候,才会使用树节点。也就是说,节点少的时候,尽管时间复杂度上,红黑树比链表好一点,但是红黑树所占空间比较大,综合考虑,认为只能在节点太多的时候,红黑树占空间大这一劣势不太明显的时候,才会舍弃链表,使用红黑树。

链表什么时候会转化成红黑树?
两个条件

  1. 数组长度大于MIN_TREEIFY_CAPACITY即64,如果小于64则会扩容
  2. 链表长度大于等于8

为什么链表的长度要大于8,为什么不用2或者20呢?
为了配合使用分布良好的hashCode,树节点很少使用。并且在理想状态下,受随机分布的hashCode影响,链表中的节点遵循泊松分布,而且根据统计,链表中节点数是8的概率已经接近千分之一,而且此时链表的性能已经很差了。所以在这种比较罕见和极端的情况下,才会把链表转变为红黑树。因为链表转换为红黑树也是需要消耗性能的,特殊情况特殊处理,为了挽回性能,权衡之下,才使用红黑树,提高性能。也就是大部分情况下,hashmap还是使用的链表,如果是理想的均匀分布,节点数不到8,hashmap就自动扩容了。
所以可以这样说:通常情况下,链表长度很难达到8,但是特殊情况下链表长度为8,哈希表容量又很大,造成链表性能很差的时候,只能采用红黑树提高性能,这是一种应对策略。

如果原本是红黑树,但是节点个数变为7了,会有什么变化?
没有变化,因为无论链表转化红黑树还是红黑树转化为链表都是很消耗性能的,所以只有当红黑树节点小于等于6个的时候,才会从红黑树转化为链表。

2.有的人会问构造方法问起

hashmap有几种构造方法
这是为了看你有没有看过源码,四种

// 1 无参
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

// 2 指定容量  负载因子为默认的0.75
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

// 3 指定容量和负载因子
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
// 4 传入一个旧的map  创建的时候table就初始化
public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

然后会问new hashmap(20)和new hashmap(32)使用时有什么区别?
答案是没有区别:因为初始化时 会调用如下方法,该方法的作用是将一个int类型的整数,转换成不小于该整数的最小的2的n次幂

static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

然后会问hashmap什么情况下会扩容
两种情况:

  1. 初始化后第一次put时扩容
  2. map的size大于 容量*负载因子时扩容

扩容的过程是怎样的?

  1. 数组的长度变为原来的2倍
  2. 重新计算元素的hash值,然后根据hash值插入到新的数组中

为什么数组的长度会变为原来的2倍,3倍不配嘛?
扩大2倍可以使得元素经过rehash之后,元素的位置要么是在原位置,要么是在原位置加原数组长度的位置。
至于为什么新位置要么是原位置,要么是原位置+原数组长度的位置是由于每次扩容相当于数组长度的高位多了一个1,新的hash运算取决于hashCode在这一位上的值是0还是1,如果是0则无需变化位置,如果是1则位置为原位置+原数组长度的位置

3. 有人会从线程安全问起

hashmap线程安全吗?
不安全

为什么hashmap线程不安全?

  1. 多线程情况下,put的时候数据覆盖,导致的数据不一致
    比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的桶索引和线程B要插入的记录计算出来的桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。
  2. jdk1.7扩容时,resize可能会形成循环列表,在调用get方法时形成死循环,导致cpu飙升,从而宕机

如何实现线程安全的map呢

  1. 使用Hashtable(使用synchronized,锁粒度过大,效率低)
  2. 使用ConcurrentHashMap (推荐使用,采用分段锁,效率高)
  3. 使用Collections.synchronizedMap() 获取map
4. 有人会问相关的方法和变种map

get方法

  1. 通过key获取hash值,然后通过hash值获取最终数组索引
  2. 根据数组索引找到链表,然后遍历链表,通过equals方法比对找出对应记录并返回

set方法

  1. 如果table数组为空数组[],进行数组填充(即扩容)
  2. 判断key是否为null,是则存储位置为table[0]或table[0]的冲突链上
  3. 计算key的hashcode,确保散列均匀,然后获取在table中的实际位置
  4. 判断key是否存在,如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧value
  5. 如果key不存在,还要判断是链表是红黑树,如果是链表,还要判断插入后链表长度以决定是否要转换成红黑树。
  6. HashMap内部结构发生变化次数+1

什么时候需要重写 equals方法和hashcode方法?

  1. 当key是自定义的类型时,如key是自己自定义的类,带有自身属性

hashmap推荐使用什么作为key?

  1. String 原因是不可变~balabala

hashmap是有序的吗?

  1. 不是

我想使用有序的map怎么办?

  1. 使用LinkedHashMap:用链表维护了顺序,是按插入顺序排序的
  2. 使用TreeMap:使用了树维护了顺序,默认按升序排序,也可自己重写排序方法

如果我已知map里要放1000个数据,那我如何创建map比较好?

  1. new hashmap(2048) 避免扩容!
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值