logo头像

生而无畏,战至终章

面试知识点汇总(持续整理中)

本文于394天之前发表,文中内容可能已经过时。

以下为我整理的面试相关知识点,若有错误,还请指正

1. Java基础

1.1. List 和 Set 的区别

  1. List,Set都是继承自Collection接口;
  2. List特点:元素有放入顺序,元素可重复;
    Set特点:元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)    

1.2. List 和 Map 区别

1.3. Arraylist 与 LinkedList 区别

  1. ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构;
  2. 对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针;
  3. 对于添加和删除操作add和remove,一般大家都会说LinkedList要比ArrayList快,因为ArrayList要移动数据。但是实际情况并非这样,对于添加或删除,LinkedList和ArrayList并不能明确说明谁快谁慢
    点这里

ArrayList和LinkedList在性能上各有优缺点,都有各自所适用的地方,总的说来可以描述如下:
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
3.LinkedList不支持高效的随机元素访问。
4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。


LinkedList,因为本质是个链表,所以通过Iterator来插入和移除操作的耗时,都是个恒量,但如果要获取某个位置的元素,则要做指针遍历。因此,get操作的耗时会跟List长度有关

对于ArrayList来说,得益于快速随机访问的特性,获取任意位置元素的耗时,是常量的。但是,如果是add或者remove操作,要分两种情况,如果是在尾部做add,也就是执行add方法(没有index参数),此时不需要移动其他元素,耗时是O(1),但如果不是在尾部做add,也就是执行add(int
index, E element),这时候在插入新元素的同时,也要移动该位置后面的所有元素,以为新元素腾出位置,此时耗时是O(n-index)。另外,当List长度超过初始化容量时,会自动生成一个新的array(长度是之前的1.5倍),此时会将旧的array移动到新的array上,这种情况下的耗时是O(n)。

总之,get操作,ArrayList快一些。而add操作,两者差不多。(除非是你希望在List中间插入节点,且维护了一个Iterator指向指定位置,这时候linkedList能快一些,但是,我们更多时候是直接在尾部插入节点,这种特例的情况并不多)

1.4. ArrayList 与 Vector 区别

1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
2.Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。
3.LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。

  1. 查看Java源代码,发现当数组的大小不够的时候,需要重新建立数组,然后将元素拷贝到新的数组内,ArrayList和Vector的扩展数组的大小不同,两者区别如下:
    • ArrayList在内存不够时默认是扩展50% + 1个,Vector是默认扩展1倍。
    • Vector提供indexOf(obj, start)接口,ArrayList没有。
    • Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销。

1.5. HashSet 是如何保证不重复的

点这里
1,如果hash码值不相同,说明是一个新元素,存;
如果没有元素和传入对象(也就是add的元素)的hash值相等,那么就认为这个元素在table中不存在,将其添加进table;
2(1),如果hash码值相同,且equles判断相等,说明元素已经存在,不存;
2(2),如果hash码值相同,且equles判断不相等,说明元素不存在,存;

如果有元素和传入对象的hash值相等,那么,继续进行equles()判断,如果仍然相等,那么就认为传入元素已经存在,不再添加,结束,否则仍然添加;

1.6. HashSet与Treeset

1.TreeSet 是二差树(红黑树的树据结构)实现的,Treeset中的数据是自动排好序的,不允许放入null值
2.HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束
3.HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例。
适用场景分析:HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。为快速查找而设计的Set,我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。

源码分析:
HashSet源码分析点这里
Treeset源码分析点这里

1.7. HashMap 是线程安全的吗,为什么不是线程安全的(最好画图说明多线程环境下不安全)?

点这里
HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。

1.8. HashMap 的扩容过程 、HashMap如何解决hash冲突、TreeMap

点这里

HashMap保存数据的过程为:首先判断key是否为null,若为null,则直接调用putForNullKey方法。若不为空则先计算key的hash值,然后根据hash值搜索在table数组中的索引位置,如果table数组在该位置处有元素,则通过比较是否存在相同的key,若存在则覆盖原来key的value,否则将该元素保存在链头(最先保存的元素放在链尾)。若table在该处没有元素,则直接保存

解决哈希冲突常用的两种方法是:开放定址法和链地址法
开放地址法:
当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探查到开放的 地址则表明表中无待查的关键字,即查找失败。

链地址法:
将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。

TreeMap:
基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。

1.9. HashMap与TreeMap、HashTable的区别

HashMap 非线程安全 , 可以使用Collections.synchronizedMap()方法来获取一个线程安全的集合
HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。
TreeMap:非线程安全基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。

适用场景分析:
HashMap和HashTable:HashMap去掉了HashTable的contains方法,但是加上了containsValue()和containsKey()方法。
HashTable同步的,而HashMap是非同步的,效率上比HashTable要高。
HashMap允许空键值null,而HashTable不允许。
HashMap:适用于Map中插入、删除和定位元素。
Treemap:适用于按自然顺序或自定义顺序遍历键(key)。
HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。
HashMap扩容时是当前容量翻倍即:capacity2,Hashtable扩容时是容量翻倍+1即:capacity2+1。
HashMap和Hashtable的底层实现都是数组+链表结构实现。

1.10. HashTable源码分析

点这里

1.11. HashMap 1.7 与 1.8 的 区别,说明 1.8 做了哪些优化,如何优化的?

HashMap 1.8:
在JDK8及以后的版本中,HashMap引入了红黑树结构,其底层的数据结构变成了数组+链表或数组+红黑树。添加元素时,若桶中链表个数超过8,链表会转换成红黑树
Java7 中使用 Entry 来代表每个 HashMap 中的数据节点,Java8 中使用 Node,基本没有区别,都是 key,value,hash 和 next 这四个属性,不过,Node 只能用于链表的情况,红黑树的情况需要使用 TreeNode

HashMap1.7 put方法

1
2
3
4
5
6
1.判断当前数组是否需要初始化。
2.如果 key 为空,则 put 一个空值进去。
3.根据 key 计算出 hashcode。
4.根据计算出的 hashcode 定位出所在桶。
5.如果桶是一个链表则需要遍历判断里面的 hashcode、key 是否和传入 key 相等,如果相等则进行覆盖,并返回原来的值。
6.如果桶是空的,说明当前位置没有数据存入;新增一个 Entry 对象写入当前位置

HashMap1.8 put方法

1
2
3
4
5
6
7
8
9
1.判断当前桶是否为空,空的就需要初始化(resize 中会判断是否进行初始化)。
2.根据当前 key 的 hashcode 定位到具体的桶中并判断是否为空,为空表明没有 Hash 冲突就直接在当前位置创建一个新桶即可。
3.如果当前桶有值( Hash 冲突),那么就要比较当前桶中的 key、key 的 hashcode 与写入的 key 是否相等,相等就赋值给 e,在第 8 步的时候会统一进行赋值及返回。
4.如果当前桶为红黑树,那就要按照红黑树的方式写入数据。
5.如果是个链表,就需要将当前的 key、value 封装成一个新节点写入到当前桶的后面(形成链表)。
6.接着判断当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树。
7.如果在遍历过程中找到 key 相同时直接退出遍历。
8.如果 e != null 就相当于存在相同的 key,那就需要将值覆盖。
9.最后判断是否需要进行扩容。

HashMap1.7 get方法

1
2
3
4
5
1.首先也是根据 key 计算出 hashcode,然后定位到具体的桶中。
2.判断该位置是否为链表。
3.不是链表就根据 key、key 的 hashcode 是否相等来返回值。
4.为链表则需要遍历直到 key 及 hashcode 相等时候就返回值。
5.啥都没取到就直接返回 null 。

HashMap1.8 get方法

1
2
3
4
5
6
1.首先将 key hash 之后取得所定位的桶。
2.如果桶为空则直接返回null 。
3.否则判断桶的第一个位置(有可能是链表、红黑树)的 key 是否为查询的 key,是就直接返回 value。
4.如果第一个不匹配,则判断它的下一个是红黑树还是链表。
5.红黑树就按照树的查找方式返回值。
6.不然就按照链表的方式遍历匹配返回值

1.12. final finally finalize

1
2
3
4
final 用于声明属性,方法和类, 分别表示属性不可变, 方法不可覆盖, 类不可继承.
finally 是异常处理语句结构的一部分,表示总是执行.
finalize 是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等. JVM不保证此方法总被调用.
并且一个对象的finalize()方法最多只会被系统自动调用一次

1.13. 强引用 、软引用、 弱引用、虚引用

  • 强引用:是指程序代码之中普遍存在的,只要强引用还存在,垃圾收集器永远不会回收被引用的对象

  • 软引用:是指一类还有用但非必须得对象,对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常,在JDK中提供了SoftReference类来实现软引用

  • 弱引用:也是用来描述非必须对象的,但是它的强度比软引用要弱一些,被弱引用关联的对象只能活到下一次垃圾收集发生之前,当垃圾收集器开始工作时,无法内存是否足够,都会被回收掉,JDK提供了WeakReference类来实现弱引用

  • 虚引用:也称为幽灵引用或幻影引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用唯一的目的就是在被垃圾收集器回收时收到一个系统通知,JDK提供了PhantomReference类来实现虚引用

1.14. Java反射

1.15. Arrays.sort 实现原理和 Collection 实现原理

点这里

1.16. LinkedHashMap的应用

点这里

1.17. cloneable接口实现原理

点这里

1.18. 异常分类以及处理机制

点这里

1.19. 数组在内存中如何分配

1.20. happens-before原则

点这里

1.21. fast-fail机制

点这里

1.22. equals方法和hashcode方法

http://starlin.top/2018/09/21/equals()和hashcode()方法详解/

1.23. 反射的机制

反射最大的作用之一就在于我们可以不在编译时知道某个对象的类型,而在运行时通过提供完整的”包名+类名.class”得到。注意:不是在编译时,而是在运行时。

功能:
•在运行时能判断任意一个对象所属的类。
•在运行时能构造任意一个类的对象。
•在运行时判断任意一个类所具有的成员变量和方法。
•在运行时调用任意一个对象的方法。
说大白话就是,利用Java反射机制我们可以加载一个运行时才得知名称的class,获悉其构造方法,并生成其对象实体,能对其fields设值并唤起其methods。

应用场景:
反射技术常用在各类通用框架开发中。因为为了保证框架的通用性,需要根据配置文件加载不同的对象或类,并调用不同的方法,这个时候就会用到反射——运行时动态加载需要加载的对象。

特点:
由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

1.24. 动态代理

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在两者之间起到中介的作用(可类比房屋中介,房东委托中介销售房屋、签订合同等)。
所谓动态代理,就是实现阶段不用关心代理谁,而是在运行阶段才指定代理哪个一个对象(不确定性)。如果是自己写代理类的方式就是静态代理(确定性)。

组成要素:
(动态)代理模式主要涉及三个要素:
其一:抽象类接口
其二:被代理类(具体实现抽象接口的类)
其三:动态代理类:实际调用被代理类的方法和属性的类

实现方式:
实现动态代理的方式很多,比如 JDK 自身提供的动态代理,就是主要利用了反射机制。还有其他的实现方式,比如利用字节码操作机制,类似 ASM、CGLIB(基于 ASM)、Javassist 等。
举例,常可采用的JDK提供的动态代理接口InvocationHandler来实现动态代理类。其中invoke方法是该接口定义必须实现的,它完成对真实方法的调用。通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。此外,我们常可以在invoke方法实现中增加自定义的逻辑实现,实现对被代理类的业务逻辑无侵入。

1.25. 一致性hash算法

点这里

1.26. 常见Set的实现及源码分析

1.27. int和Integer

JDK1.5引入了自动装箱与自动拆箱功能,Java可根据上下文,实现int/Integer,double/Double,boolean/Boolean等基本类型与相应对象之间的自动转换,为开发过程带来极大便利。

最常用的是通过new方法构建Integer对象。但是,基于大部分数据操作都是集中在有限的、较小的数值范围,在JDK1.5 中新增了静态工厂方法 valueOf,其背后实现是将int值为-128 到 127 之间的Integer对象进行缓存,在调用时候直接从缓存中获取,进而提升构建对象的性能,也就是说使用该方法后,如果两个对象的int值相同且落在缓存值范围内,那么这个两个对象就是同一个对象;当值较小且频繁使用时,推荐优先使用整型池方法(时间与空间性能俱佳)。

2 注意事项

[1] 基本类型均具有取值范围,在大数*大数的时候,有可能会出现越界的情况。
[2] 基本类型转换时,使用声明的方式。例:long result= 1234567890 * 24 * 365;结果值一定不会是你所期望的那个值,因为1234567890 * 24已经超过了int的范围,如果修改为:long result= 1234567890L * 24 * 365;就正常了。
[3] 慎用基本类型处理货币存储。如采用double常会带来差距,常采用BigDecimal、整型(如果要精确表示分,可将值扩大100倍转化为整型)解决该问题。
[4] 优先使用基本类型。原则上,建议避免无意中的装箱、拆箱行为,尤其是在性能敏感的场合,
[5] 如果有线程安全的计算需要,建议考虑使用类型AtomicInteger、AtomicLong 这样的线程安全类。部分比较宽的基本数据类型,比如 float、double,甚至不能保证更新操作的原子性,可能出现程序读取到只更新了一半数据位的数值

1.28. Equals()方法与==的区别?重写equals方法的还需要重写哪些方法?为什么?

2. Spring

1、BeanFactory 和 FactoryBean?
2、Spring IOC 的理解,其初始化过程?
3、BeanFactory 和 ApplicationContext?
4、Spring Bean 的生命周期,如何被管理的?
5、Spring Bean 的加载过程是怎样的?
6、如果要你实现Spring AOP,请问怎么实现?
7、如果要你实现Spring IOC,你会注意哪些问题?
8、Spring 是如何管理事务的,事务管理机制?
9、Spring 的不同事务传播行为有哪些,干什么用的?
10、Spring 中用到了那些设计模式?
11、Spring MVC 的工作原理和请求流程?
12、Spring 循环注入的原理?
13、Spring AOP的理解,各个术语,他们是怎么相互工作的?
14、Spring 如何保证 Controller 并发的安全?
15、Spring提供了哪几种标准的事件
16、springsecurity与shiro的区别、以及它们的使用场景?

3. Netty

1、BIO、NIO和AIO
2、Netty 的各大组件
3、Netty的线程模型
4、TCP 粘包/拆包的原因及解决方法
5、了解哪几种序列化协议?包括使用场景和如何去选择
6、Netty的零拷贝实现
7、Netty的高性能表现在哪些方面
8、说下netty,你在实际的工作当中,哪里用到了netty?
9、netty的线程模型是怎么样的?

4. mybatis

${}与#{}的区别?
Statement与PreparedStatement的区别?
mybatis 是否可以映射 Enum 枚举类?

5. 分布式相关

2、描述一个服务从发布到被消费的详细过程
3、分布式系统怎么做服务治理
4、接口的幂等性的概念
5、消息中间件如何解决消息丢失问题
6、Dubbo的服务请求失败怎么处理
7、重连机制会不会造成错误
8、对分布式事务的理解
9、如何实现负载均衡,有哪些算法可以实现?
10、Zookeeper的用途,选举的原理是什么?
11、数据的垂直拆分水平拆分。
12、zookeeper原理和适用场景
13、zookeeper watch机制
14、redis/zk节点宕机如何处理
15、分布式集群下如何做到唯一序列号
16、如何做一个分布式锁
17、用过哪些MQ,怎么用的,和其他mq比较有什么优缺点,MQ的连接是线程安全的吗
18、MQ系统的数据如何保证不丢失
19、列举出你能想到的数据库分库分表策略;分库分表后,如何解决全表查询的问题
20、zookeeper的选举策略
21、全局ID
22、对分布式的理解
23、zuul网关Filter处理流程及异常处理
24、eureka与zookeeper注册中心的区别?不用eureka可以吗?eureka已经停止维护了,有哪些替代方案?
25、什么是操作的互斥性,接口幂等性如何保证?
26、异步通知交互补偿机制的目的和设计?
27、tomcat有哪几种 Connector运行模式

6. 数据库

1、mysql分页有什么优化
2、悲观锁、乐观锁
3、组合索引,最左原则
4、mysql 的表锁、行锁
5、mysql 性能优化
6、mysql的索引分类:B+,hash;什么情况用什么索引
7、事务的特性和隔离级别

7. 缓存

1、Redis用过哪些数据数据,以及Redis底层怎么实现

1
2
3
4
5
6
7
String(字符串)、List(链表)、Hash(包含键值对的无需散列表)、Set(无需集合)、ZSet(有序集合)
Redis在互联网公司一般有以下应用:
- String:缓存、限流、计数器、分布式锁、分布式Session
- Hash:存储用户信息、用户主页访问量、组合查询
- List:微博关注人时间轴列表、简单队列
- Set:赞、踩、标签、好友关系
- Zset:排行榜

2、Redis缓存穿透,缓存雪崩
3、如何使用Redis来实现分布式锁
4、Redis的并发竞争问题如何解决
5、Redis持久化的几种方式,优缺点是什么,怎么实现的
6、Redis的缓存失效策略
7、Redis集群,高可用,原理
8、Redis缓存分片
9、Redis的数据淘汰策略

8. JVM

1、详细jvm内存模型
2、讲讲什么情况下回出现内存溢出,内存泄漏?
3、说说Java线程栈
4、JVM 年轻代到年老代的晋升过程的判断条件是什么呢?
5、JVM 出现 fullGC 很频繁,怎么去线上排查问题?
6、类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?
7、类的实例化顺序
8、JVM垃圾回收机制,何时触发MinorGC等操作
9、JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的
10、各种回收器,各自优缺点,重点CMS、G1
11、各种回收算法
12、OOM错误,stackoverflow错误,permgen space错误

9. 消息中间件

RocketMQ的优点

10. 高可用架构设计

一、高可用

文章:《究竟什么是互联网高可用架构设计》

内容:什么是高可用

高可用架构核心准则:冗余+故障转移

互联网分层架构,各层保证高可用的架构实践

二、高并发

文章:《究竟什么是互联网高并发架构设计》

内容:什么是高并发

高并发架构准则:垂直扩展,水平扩展

互联网分层架构,各层保证水平扩展的架构实践

三、反向代理

文章:《究竟什么是互联网四层/七层反向代理》

内容:什么是代理与反向代理

如何实施反向代理

什么是四层/七层,有没有二层/三层呢?

四、负载均衡

文章:《究竟什么是互联网负载均衡架构设计》

内容:什么是负载均衡

高并发架构准则:均匀

互联网分层架构,各层保证负载均衡的架构实践

延伸阅读:《LVS为何不能取代DNS轮询》

内容:什么是LVS,DNS轮询

LVS解决什么问题

DNS轮询解决什么问题

LVS为何不能取代DNS轮询

延伸阅读:《异构服务器负载均衡及过载保护》

内容:

如何动态标识服务的处理能力

如何实施异构服务器负载均衡

什么是过载保护

如何实施过载保护