【备战秋招冲击大厂(3),大厂Java研发岗面试复盘

java.util.Map:它有四个实现类,分别是HashMap、Hashtable、LinkedHashMap 和TreeMap.

  • Hashmap 是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。HashMap最多只允许一条记录的键为Null,允许多条记录的值为 Null;HashMap不支持线程的同步,即任一时刻如果有多个线程同时写HashMap,可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。

  • Hashtable与 HashMap类似,它继承自Dictionary类,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。

  • LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

  • TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。

    小结:一般情况下,我们用的最多的是HashMap,在Map中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列.

8. HashMap

  • 哈希表:根据关键码值(key value)而直接进行访问的数据结构。也就是说,它通过关键码值映射到表中一个位置来访问记录,以加快查找速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

  • 哈希:把任意长度的输入通过哈希算法映射成固定长度的输出。

  • 哈希冲突(无法避免):计算得到的哈希值相同。

    解决方法:

    1)开放定址法

    2)再哈希法:双哈希法计算

    3)链址法:HashMap实现方式,next指针连接Node

    4)建立公共溢出区:建立基本表和溢出表,哈希值相同的直接放到溢出表

    哈希算法要求:

    1)高效,能够处理长文本

    2)不能逆推原文

    3)尽量分散,减少哈希冲突

  • HashMap

    • 每个数据单元为一个Node结构,包含key,value,hash,next四个字段

    • 采用懒加载机制,即在进行put操作时才真正构建table数组。

    • 初始长度为16,默认负载因子为0.75,当HashMap的长度达到16*0.75=12时,就会触发扩容流程,每次扩容为原来的2倍(碰撞的概率低)。

    • 允许第一个位置的key为空,允许value为空

    • 线程不安全,导致cpu100%:jdk7链表成环,jdk8红黑树父子节点成环

    • JDK1.7:数组+链表;JDK1.8:数组+链表+红黑树(链表长度大于8且table大于64转为红黑树,红黑树节点个数小于6转为链表)

  • 红黑树(自平衡二叉查找树)特性:

    1)每个结点是黑色或者红色。

    2)根结点是黑色。

    3)每个叶子结点(NIL)是黑色。 [注意:这里叶子结点,是指为空(NIL或NULL)的叶子结点!]

    4)如果一个结点是红色的,则它的子结点必须是黑色的。

    5)每个结点到叶子结点NIL所经过的黑色结点的个数一样的。

  • HashMap的get流程:

    1)首先会判断数组是否不等于null,或者数组的长度是否大于0,如果不满足,就说明HashMap里没有数据,直接返回null。

    2)通过 hash & (table.length - 1)获取该key对应的数据节点的hash槽;

    3)判断首节点是否为空,为空则直接返回空;

    4)再判断首节点.key是否和目标值相同,相同则直接返回(首节点不用区分链表还是红黑树);首节点.next为空,则直接返回空;

    5)首节点是树形节点,则进入红黑树数的取值流程,并返回结果;

    6)否则就会进入一个do while循环进行查询链表。并返回结果;

  • HashMap的put流程:

    1)如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold

    2)如果key为null时被放在了tab下标为0的位置.

    3)根据hash值来确认存放的位置。如果当前位置是空直接添加到table中

    4)如果在首结点与我们待插入的元素有相同的hash和key值,则先记录。

    5)如果首结点的类型是红黑树类型,则按照红黑树方法添加该元素

    6)如果首结点类型为链表类型,遍历到末尾时,先在尾部追加该元素结点。当遍历的结点数目大于8时,则采取树化结构。

    7)modCount++;如果集合在被遍历期间如果内容发生变化则++modCount,只能检测并发修改的bug,不能保证线程安全(ABA,祥见CAS)

    8)当结点数+1大于threshold时,则进行扩容

9. Hashmap扩容原理

e.hash & oldCap,就是用于计算位置b到底是0还是1用的,只要其结果是0,则新散列下标就等于原散列下标,否则新散列坐标要在原散列坐标的基础上加上原table长度。

  • 触发扩容时机:

    1)当new完HashMap之后,第一次往HashMap进行put操作的时候,首先会进行扩容。

    2)当HashMap的使用的桶数达到总桶数*加载因子的时候会触发扩容;

    3)当某个桶中的链表长度达到8进行链表扭转为红黑树的时候,会检查总桶数是否小于64,如果总桶数小于64也会进行扩容;

10. Hashmap拓展问题

  • 为什么JDK1.8采用红黑树存储Hash冲突的元素?

    红黑树本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。能够加快检索速率。

  • 为什么在长度小于8时使用链表,不一直使用红黑树?

    桶中元素的插入只会在hash冲突时发生,而hash冲突发生的概率较小,一直维护一个红黑树比链表耗费资源更多,在桶中元素量较小时没有这个必要。

  • 为什么要使用红黑树而不使用AVL树?

    红黑树与AVL树,在检索的时候效率差不多,都是通过平衡来二分查找。但红黑树不像AVL树一样追求绝对的平衡,红黑树允许局部很少的不完全平衡,这样对于效率影响不大,但省去了很多没有必要的调平衡操作,AVL树调平衡有时候代价较大,所以效率不如红黑树。

  • 为什么数组容量必须是2次幂?

    索引计算公式为i = (n - 1) & hash,如果n为2次幂,那么n-1的低位就全是1,而扩容后只有一位差异,也就是多出了最左位的1,这样在通过 (length-1) &hash的时候,只要hash对应的最左边的那一个差异位为0,就能保证得到的新的数组索引和老数组索引一致(高效的数据迁移,大大减少了之前已经散列良好的老数组的数据位置重新调换),哈希值进行与操作时可以保证低位的值不变,如果低位值为1,则表示该位置可以插入值,从而保证分布均匀,效果等同于hash%n,但是位运算比取余运算要高效的多。

  • 为什么单链表转为红黑树要求桶内的元素个数大于8?

    当hashCode离散性很好的时候,树型bin用到的概率非常小,因为数据均匀分布在每个bin中,几乎不会有bin中链表长度会达到阈值。但是在随机hashCode下,离散性可能会变差,然而JDK又不能阻止用户实现这种不好的hash算法,因此就可能导致不均匀的数据分布。不过理想情况下随机hashCode算法下所有bin中节点的分布频率会遵循泊松分布,而一个bin中链表长度达到8个元素的概率为0.00000006,几乎是不可能事件。

    同理,少于6就从红黑树转回单链表是为了节省维护一个树的资源消耗,而选择6作为临界值,是因理想情况下一个bin中元素个数达到6的概率是0.00001316,达到7的概率为0.00000094,二者跨度较大,可以减小树和链表之间频繁转化的可能性。

  • 为什么jdk1.8将头插法改成尾插法?

    JDK1.7中扩容时,每个元素的rehash之后,都会插入到新数组对应索引的链表头,所以这就导致原链表顺序为A->B->C,扩容之后,rehash之后的链表可能为C->B->A,元素的顺序发生了变化。在并发场景下,扩容时可能会出现循环链表的情况。而JDK1.8从头插入改成尾插入元素的顺序不变,避免出现循环链表的情况。

11. ConcurrentHashMap

  • JDK1.7:Segment数组+HashEntry

1)HashEntry中value,以及next(链表)都是 volatile 修饰的,保证了获取时的可见性。

2)原理上来说:ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像HashTable那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量,默认为16)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。

  • JDK1.8:Node +CAS+Synchorized+volatile

  • 对比Java7 和Java8 的异同和优缺点

    1)数据结构不同

    • Java 7采用数组+链表来实现,而 Java 8 中的 ConcurrentHashMap 使用数组 + 链表 + 红黑树

      2)并发度

    • Java 7 中,每个 Segment 独立加锁,最大并发个数就是 Segment 的个数,默认是 16。

    • Java 8 中,锁粒度更细,理想情况下 table 数组元素的个数(也就是数组长度)就是其支持并发的最大个数,并发度比之前有提高。

      3)保证并发安全的原理

    • Java 7 采用 Segment 分段锁来保证安全,而 Segment 是继承自 ReentrantLock。

    • Java 8 中放弃了 Segment 的设计,采用 Node + CAS + synchronized+volatile 保证线程安全。

      4)遇到 Hash 碰撞

    • Java 7 在 Hash 冲突时,会使用拉链法,也就是链表的形式。

    • Java 8 先使用拉链法,在链表长度超过一定阈值时,将链表转换为红黑树,来提高查找效率。

      5)查询时间复杂度

    • Java 7 遍历链表的时间复杂度是 O(n),n 为链表长度。

    • Java 8 如果变成遍历红黑树,那么时间复杂度降低为 O(logn),n 为树的节点个数。

12. 线程安全的Map

  • HashTable

  • SynchronizedMap:加了一个对象锁,每次操作hashmap都需要先获取这个对象锁

  • ConcurrentHashMap:线程安全是通过cas+synchronized+volatile来实现的

  • ConcurrentSkipListMap: 通过跳表来实现的高并发容器并且这个Map是有序排序的,根据key来排序

13. HashMap和Hashtable 的区别

JDK 1.8 中 HashMap 和 Hashtable 主要区别如下:

  • 父类不同。HashMap继承自AbstractMap;Hashtable继承自Dictionary。

  • 线程安全性不同。HashMap线程不安全;Hashtable 中的方法是Synchronized的。

  • HashMap最多只允许一条记录的键为Null,允许多条记录的值为 Null;Hashtable键和值都不允许为空。

  • 默认初始大小和扩容方式不同。HashMap默认初始大小16,容量必须是2的整数次幂,扩容时将容量变为原来的2倍;Hashtable默认初始大小11,扩容时将容量变为原来的2倍加1。

  • 迭代器不同。HashMap的Iterator是fail-fast迭代器;Hashtable还使用了enumerator迭代器。

  • hash的计算方式不同。HashMap计算了hash值;Hashtable使用了key的hashCode方法。

  • 是否有contains方法。HashMap没有contains方法;Hashtable包含contains方法,类似于containsValue。

14. TreeMap底层实现

  • TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证快速检索指定节点。

  • 红黑树的插入、删除、遍历时间复杂度都为O(lgN),所以性能上低于哈希表。但是哈希表无法提供键值对的有序输出,红黑树因为是排序插入的,可以按照键的值的大小有序输出。

15. HashMap和TreeMap

  • HashMap基于散列桶(数组和链表)实现;TreeMap基于红黑树实现。

  • HashMap不支持排序;TreeMap默认是按照Key值升序排序的,可指定排序的比较器,主要用于存入元素时对元素进行自动排序。

  • HashMap大多数情况下有更好的性能,尤其是读数据。在没有排序要求的情况下,使用HashMap。

  • 都是非线程安全。

最后的内容

在开头跟大家分享的时候我就说,面试我是没有做好准备的,全靠平时的积累,确实有点临时抱佛脚了,以至于我自己还是挺懊恼的。(准备好了或许可以拿个40k,没做准备只有30k+,你们懂那种感觉吗)

如何准备面试?

1、前期铺垫(技术沉积)

程序员面试其实是对于技术的一次摸底考试,你的技术牛逼,那你就是大爷。大厂对于技术的要求主要体现在:基础,原理,深入研究源码,广度,实战五个方面,也只有将原理理论结合实战才能把技术点吃透。

下面是我会看的一些资料笔记,希望能帮助大家由浅入深,由点到面的学习Java,应对大厂面试官的灵魂追问

CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】

这部分内容过多,小编只贴出部分内容展示给大家了,见谅见谅!

  • Java程序员必看《Java开发核心笔记(华山版)》

  • Redis学习笔记

  • Java并发编程学习笔记

四部分,详细拆分并发编程——并发编程+模式篇+应用篇+原理篇

  • Java程序员必看书籍《深入理解 ava虚拟机第3版》(pdf版)

  • 大厂面试必问——数据结构与算法汇集笔记

其他像Spring,SpringBoot,SpringCloud,SpringCloudAlibaba,Dubbo,Zookeeper,Kafka,RocketMQ,RabbitMQ,Netty,MySQL,Docker,K8s等等我都整理好,这里就不一一展示了。

2、狂刷面试题

技术主要是体现在平时的积累实用,面试前准备两个月的时间再好好复习一遍,紧接着就可以刷面试题了,下面这些面试题都是小编精心整理的,贴给大家看看。

①大厂高频45道笔试题(智商题)

②BAT大厂面试总结(部分内容截图)

③面试总结

3、结合实际,修改简历

程序员的简历一定要多下一些功夫,尤其是对一些字眼要再三斟酌,如“精通、熟悉、了解”这三者的区别一定要区分清楚,否则就是在给自己挖坑了。当然不会包装,我可以将我的简历给你参考参考,如果还不够,那下面这些简历模板任你挑选:

以上分享,希望大家可以在金三银四跳槽季找到一份好工作,但千万也记住,技术一定是平时工作种累计或者自学(或报班跟着老师学)通过实战累计的,千万不要临时抱佛脚。

另外,面试中遇到不会的问题不妨尝试讲讲自己的思路,因为有些问题不是考察我们的编程能力,而是逻辑思维表达能力;最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。

g-xbshqamB-1630856369182)]

3、结合实际,修改简历

程序员的简历一定要多下一些功夫,尤其是对一些字眼要再三斟酌,如“精通、熟悉、了解”这三者的区别一定要区分清楚,否则就是在给自己挖坑了。当然不会包装,我可以将我的简历给你参考参考,如果还不够,那下面这些简历模板任你挑选:

[外链图片转存中…(img-1DM4FZOw-1630856369183)]

以上分享,希望大家可以在金三银四跳槽季找到一份好工作,但千万也记住,技术一定是平时工作种累计或者自学(或报班跟着老师学)通过实战累计的,千万不要临时抱佛脚。

另外,面试中遇到不会的问题不妨尝试讲讲自己的思路,因为有些问题不是考察我们的编程能力,而是逻辑思维表达能力;最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。

以上文章中,提及到的所有的笔记内容、面试题等资料,均可以免费分享给大家学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值