对线面试官数据结构之HashMap


又到了熟悉的毕业季,室友在宿舍里准备进行线上面试,我躺在床上刷视频。
虽然刷视频很爽,但是以后面试要是被问到类似的问题要是全不会,那不是很尴尬?
先从他的面试里积累点经验,万一以后用得到。有道是: 思则有备,有备无患

在这里插入图片描述

面试开始,面试官先问了一些基础的Java语法,然后就是数据结构的折磨。

在这里插入图片描述

面试官:数据结构熟悉吗?你一般都用哪些数据结构

室友:还可以吧,我们有专门的课程。以前做的项目有用过ArrayListLinkedListArrayDeque还有HashMapHashTableHashSet什么的。

我内心:
在这里插入图片描述
我只用过ArrayList,赶紧听一下,以后面试估计也用得着。

HashMap底层基本结构

面试官:嗯。看来你对数据结构挺熟悉的。对HashMap的底层熟吗?

室友: 还可以吧,平时会看一下用到的知识的源码。

我内心:当初我问你在电脑前干嘛,你可不是这么说的。

在这里插入图片描述
面试官:平时还看源码?不错。能说说HashMap大致的组成吗?

室友:组成的话,大概是一个数组+链表

面试官(眉头一挑):就这样?还有吗?

我:错了,错了!他错了!
在这里插入图片描述

室友(摸下巴):还有?。。。嗷,记起来了。还有可能是数组+红黑树。HashMap里面有树化的操作,当链表存储的节点数量小于等于8时,是以链表形式存储,存储数据后的节点数大于8时,链表就树化为红黑树
比较有意思的是TreeNode所继承的LinkedHashMap.Entry,其实是HashMap.Node的子类,也就是说TreeNodeNode孙子
所以Node数组也可以代表TreeNode

//数组
transient Node<K,V>[] table;
//节点--链表
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        //........
}
//节点--红黑树
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
         //........
}

红黑树转为链表

面试官:你刚刚说了链表转为红黑树,那什么时候红黑树转为链表呢?

室友:好像是移除节点后的数量为6个的时候,就会退化成链表了。

面试官: 嗯,不错。但树化那一块说的有一点不对,就是当你链表的节点数大于8,但是你的数组容量小于64的时候,也不会树化,而是扩容

室友:嗯嗯。

我: 可恶,被他装到了。先掏出小本本记下来,晚上偷偷看。卷死其他室友。
在这里插入图片描述

懒扩容及初始化容量

面试官:那我们继续,HashMap的懒扩容知道吗?

我:懒扩容?睡懒觉我倒是知道,扩容弹夹我也知道,懒扩容是睡懒觉的扩容弹夹?
在这里插入图片描述

室友:懒扩容就是每次扩容的时候,新的容量大小是原来的两倍。比如,默认的初始容量是16,到了要扩容的时候,我新的容量大小就是32了,而不是17或者18什么的。扩容在源码里面是通过移位实现的。
源码里的注释还提到,HashMap的容量大小必须是2的整数次幂,例如你定义的容量为6,那么在HashMap内部其实会给你变成8

容量扩容主要代码

newCap = oldCap << 1; //  左移一位 变成两倍

取大于等于输入容量,最小的二次幂的整数

    /**
     * 给目标容量返回一个2的整数次幂
     */
    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里面有一个属性叫loadFactor,有人数叫负载因子,也有人叫负载系数。其实都是同一个意思,默认大小是0.75。比如当前数组容量大小为100,那么存储到76个元素大于100*0.75)的时候触发扩容条件。

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

我:。。。没听懂,先记着晚点再看。
在这里插入图片描述

扩容的大致步骤

面试官:能简单讲讲扩容的步骤吗?

室友:在1.7中,先是创建一个原容量两倍的数组,然后再重新计算节点在新的数组中的位置。因为在HashMap中是这么计算节点的下标的:index = HashCode(Key) & (Length - 1)。当容量length改变,那么index也会随之变化,以此来使得整体的节点均匀分布。

相对于后一步,在1.8里面就更加简单了,index = hash & oldCap

//计算下标
 static int indexFor(int h, int length) {
 		 // h 为key 的 hash值;length 是数组长度
        return h & (length-1);  
 }

面试官:你说用与(&)运算计算下标,那为什么不用取余呢?

室友:因为&运算是二进制运算效率比取余更高。感觉这也是将容量设计为2的整数次幂的一个好处吧。,通过&运算就可以得到下标,而不需要使用取余。

我:计组乱入。。。当初考试的时候,还是老师让我过的。。。

在这里插入图片描述

HashMap1.7于1.8的区别

面试官:你刚刚说到了1.7和1.8里面HashMap的不同,能简单讲讲大概的不同点吗?

我内心:。。。1.8比1.7高了一个版本。。。
在这里插入图片描述
室友:大概是以下几点:

1、插入方式不同。1.7使用头插法,1.8换成了尾插法来防止扩容时造成死循环。

2、插入时的操作顺序不同。1.7先判断是否扩容,再插入元素,1.8先进行插入元素,插入后再判断是否需要扩容。

3、结构不同。1.7的结构只是数组+链表,而1.8的结构是数组+链表或者红黑树,降低了链表过长时,查找的时间复杂度。

4、扩容后计算下标不同。1.7是通过再hash,再计算进而得到下标,而1.8是直接用原hash&旧容量得到下标。

线程安全及与HashTable、HashSet的区别

面试官:嗯,不错。那HashMap是线程安全的吗?

室友:线程不安全,因为其中的方法都没有加synchronized修饰。

面试官:前面你提到了HashTableHashSet,可以说说HashTableHashSet和HashMap的大致区别吗?

室友:HashTable所有的方法都用synchronized修饰,所以是线程安全的,而HashMap没有用该关键字修饰,所以是线程不安全的,但也正因为如此才导致HashTable效率不高
HashSet的话,它是基于HashMap实现的,但区别在于HashSet中只能存储唯一的值。比如,你存储一个Object对象,在ArrayList中可以用add方法,添加两个,读取的时候也能读取到两个,但在HashSet中,你add两次,但只能读取到一个。这是因为HashSet中add的对象对应的是HashMap中的键,所以是唯一的。

我:。。。可以问点我会的吗。。。
在这里插入图片描述
面试官:说的挺清楚的,你刚刚说HashMap不是线程安全的,那有没有线程安全效率又比较高的?

室友:有的,比如ConcurrentHashMap,其实现原理是降低了锁的的粒度

我:。。。没听说过。。。
在这里插入图片描述
算了 我不装了 我摊牌了
在这里插入图片描述
面试官:不错,今天的面试就到这里,等人事部的人通知吧。

室友:好的,拜拜。

我:好的,再见!
在这里插入图片描述

参考资料

10分钟拿下 HashMap
面试阿里,HashMap 这一篇就够了
阿里面试官没想到一个HashMap,我能跟他扯半小时
一个HashMap跟面试官扯了半个小时

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值