随便分享点不那么常规的面试题(二)

1,349 阅读9分钟

1. 观察者模式和访问者模式有什么区别?

这两种设计模式相对来说都偏冷门,本题能够回答好的话说明对设计模式有过深入的了解,在面试偏业务的岗位上有加分

先解释两者概念:

  • 观察者模式定义了一种一对多的依赖关系,当某一对象状态改变时,会通知所有观察者
  • 访问者模式定义了一系列访问接口,负责接收具体的数据对象,被访问者通过这些接口将自己的数据交给访问者来处理

再解释两者核心区别和功能不同点:

  • 观察者模式的核心是触发器,主要用于广播通知和动态触发某些行为
  • 访问者模式的核心是解耦,主要用于将数据对象和数据分离,避免让对象上的操作污染对象所在类

最后解释两者优缺点和各自使用场景:

  • 观察者模式的优点是可以实现动态触发机制,但是如果存在循环依赖会导致系统崩溃,适用于广播通知/发布订阅/链式触发等场景
  • 访问者模式的优点是契合单一职责原则,同时拥有高度的灵活性,但是具体数据对访问者公布了细节,适用于需要使用数据对象进行很多不同操作的场景

2. 数据库的页分裂问题讲一下

如果问页分裂问题,一般会在聚簇索引之后紧接着提出,如果没有问,在回答完聚簇索引相关的概念后,要尽量地给面试官带出页分裂的概念,体现出自己对这一块的熟悉程度

数据库索引底层使用B+树来实现,如果插入不规则的数据(指不按key顺序插入),会导致树结构频繁发生较大改变

在数据库中,聚簇索引和非聚簇索引在频繁插入不规律数据时,都会导致严重的页分裂问题。mysql中一页大小为8k,是固定的:

  • 聚簇索引中,因为索引顺序与物理表顺序一致,所以当插入包含不规律的索引列的数据时,会导致索引树结构发生较大改变,数据页也同时需要分裂,而且会破坏磁盘顺序索引的高效性保证
  • 非聚簇索引中,因为索引是有序的,所以当插入不规律数据时,如果之前的数据页已满,会导致数据页的分裂,同时也会破坏磁盘顺序索引的高效性保证

3. 排序的稳定性意义是什么,如果一定要使用非稳定性排序算法,如何保证稳定性?

本题主要用于考察面试者是否了解排序的概念,而不是仅仅背知识点。第一问不难,基本上知道稳定性的概念就能顺势推出其意义,第二问稍微有点难度,但是只要真正了解稳定性的概念,就能想到一些解决办法

排序的稳定性指拥有相同关键字的记录,在排序后的相对次序保持不变。因为有些数据在排序前的相对顺序是有语义的,非稳定性排序会在排序后丢失这些语义,所以在这种场景下需要保证排序的稳定性

当必须使用非稳定性排序算法,且需要保证稳定性时,有两种解决方案:

  1. 剥夺原数据顺序上的语义,比如可以使用另一个数据结构保存语义,或者从业务需求上解决
  2. 如果数据直接拥有在顺序上的语义属性时(比如插入时间/次序等),则重写数据比较器,当关键词相等时,比较数据的顺序属性

4. HashSet是如何实现的?

实现原理很简单,但前提是你主动去了解过

HashSet内部维护了一个HashMap类型的变量,其key值为Set的元素类型,value为Object类型

对所有添加到Set中的元素,HashSet会添加一个元素 => PRESENT的键值对到HashMap中,这个PRESENT是HashSet中定义的一个普通的Object对象。因为HashMap中key相同的元素会相互覆盖,所以保证了集合中没有重复key值的元素存在

5. 事务的ACID特性各自是如何实现的?

ACID的概念很简单,但是想说清原理不容易,其中隔离性和持久性是重点。隔离性的要点是锁机制,有的面试官会紧接着出一些场景题,需要提前准备;持久性的要点是redo logbinlog,有的面试官会引申到读写分离的数据一致性问题,也需要提前做好准备

A(原子性):原理是undo log,即“撤销日志”。当事务对数据库进行修改时,会生成对应的undo log,如果事务回滚,数据库会使用undo log中的内容将数据修改到之前的状态

C(一致性):通过数据库本身(如外键等)和服务端的代码逻辑层面共同保证

I(隔离性):通过数据库锁机制和MVCC来实现。锁机制保证两个事务的写操作不会相互影响,MVCC保证一个事务的读操作不会被其他事务的写操作影响

D(持久性):原理是redo logbinlog,当数据修改时,会在redo log中记录这次操作(记录的是物理数据,内容基于磁盘page),通常当事务提交时,会调用fsync对redo log进行刷盘(将数据写入磁盘),如果mysql机器宕机,可以使用redo log对数据进行恢复。同时binlog作为二进制逻辑日志也可以用户数据恢复

6. strictfp关键词有了解过吗?

如果面试官问你这个问题,唯一的原因就是他昨天临睡前看到了这个知识点,所以就拿来考你(当然也有可能是工作中确实用到)。本题单纯的就是知识扩展,如果不会也不影响

stricpy,即strict float point(精确浮点),只能用来修饰方法或类。被strictpy修饰的方法或类中所有的float/double表达式都严格遵循FP-strict的限制,所有表达式的结果都必须是IEEE-754对操作数预期的结果

strictpy可以消除因硬件不同而带来的浮点数计算差异,但是并不能避免类似0.05 + 0.01 != 0.06这样的情况,所以在要求高精度浮点计算时,需要使用BigDecimal

7. 看下面这段代码,和fun1相互阻塞的方法有哪几个,为什么?

    public synchronized static void fun1() {
        try {
            Thread.sleep(2000);
        } catch(Exception e) {
            e.printStackTrace();
        }
        System.out.println("[同步-静态方法-1]");
    }

    // -------------------------------------------------

    public synchronized static void fun2() {
        System.out.println("[同步-静态方法-2]");
    }

    public static void fun3() {
        System.out.println("[普通静态方法]");
    }

    public void fun4() {
        System.out.println("[普通方法]");
    }

    public void fun5() {
        synchronized (Demo.class) {
            System.out.println("[类同步-静态方法]");
        }
    }

    public void fun6() {
        synchronized (this) {
            System.out.println("[对象同步-方法]");
        }
    }

本题考察点是对synchronized关键字的掌握程度,有些人理论背的很熟,但是随便丢个场景应用就懵了。所以在学一个知识点的时候,最起码自己要动手写几行代码跑一下看看

与fun1相互阻塞的方法是fun2和fun5

fun2是另一个被synchronized修饰的方法,和fun1同属一类,所以会相互阻塞

fun5中使用了类锁,而synchronized在静态方法上的锁也属于类锁,所以也会相互阻塞

8. 多个客户端要同时修改redis中的一个key怎么办?

一般这种并发修改的场景,都可以用消息中间件来将并行转串行解决,但是面试官通常还想要另一种答案,所以最少要准备两套方案。本题当然不止我下面列出的这两种方案,只要合理即可

  1. 所有的更新操作全部送入消息中间件中(如kafka/rabbitmq/...)串行化处理
  2. 通过redis的MUTLIWATCH实现:在redis中设置一个key,然后WATCH这个key,通过MUTLI开启事务,然后自增这个key的值,接着再执行真正的修改操作,最后使用EXEC提交事务。通过这种方法,如果发生多线程竞争,由于WATCH机制,监听到key值变化的线程会执行redis操作失败

9. 缓存雪崩、缓存击穿、缓存穿透分别是什么,怎么解决?

本题应该算是偏常规的缓存题,但是有的面试官会把这三个场景放一起说来故意迷惑你,所以要注意区分它们之间的不同点,千万不要死记硬背,否则一定会搞混的

  • 缓存雪崩:指缓存服务器宕机或大量key同时过期,导致大量请求直接打到数据库上,致使数据库服务器宕机。
    • 如果是缓存服务器宕机:部署高可用集群,启用熔断限流机制,以及在启动前使用rdb日志进行数据预热
    • 如果是key批量过期:设置缓存时间时乘上一个随机的负载因子
  • 缓存击穿:某个频繁访问的key过期的瞬间,大量流量直接打到数据库服务器上,导致宕机
    • 可以将热点key设置为永不过期
    • 也可以使用双检锁,当在缓存中发现数据不存在时,先获取锁,然后再检查缓存中是否存在数据,如果依然不存在则查询数据库
  • 缓存穿透:用户恶意访问数据库不存在的数据,导致每次查询都会走数据库
    • 如果某次查询没有在数据库中查到数据,则将该key放入缓存中,值设为空,同时设置一个很短的过期时间
    • 限制同一ip短时间的频繁访问

10. 为什么事务注解不加在Controller层或是Dao层?

本题主要考察对mvc分层的理解程度,只要能把每一层的概念理解了,说个大概还是没问题的,即使不知道,举几个反例出来就可以了

  • 不加在controller层的原因:controller的重点是请求逻辑,而不是数据库事务。通常controller会调用多个服务,如果失败只需要一个回滚即可
  • 不加在dao层的原因:当服务发生异常时,需要整体进行回滚,而加在dao层只能回滚一个操作