面灵AI

宁波银行总行科技 技术面面经

轮次
技术面
时间
2026-09
来源
牛客网

《面试题目》

  1. 自我介绍
  2. Java 中重载和重写分别是什么?
  3. 创建线程有哪些常见方式?
  4. 内部类有什么作用?
  5. 构造二叉搜索树需要满足哪些条件?
  6. 请简述快速排序的基本过程
  7. 数据库查询优化一般有哪些方法?
  8. 父任务等所有子任务完成后,如何同步状态、聚合结果?
  9. 消息队列的消息怎么避免重复消费?
  10. 手撕:单词拆分

《参考解析》

  1. 重载与重写:重载(Overload)发生在同一个类里,方法名相同、参数列表不同,与返回值无关,编译期就确定调用哪个版本,本质是静态分派。重写(Override)发生在父子类之间,子类重新实现父类的方法,要求方法签名相同、访问权限不能收窄、抛出的检查异常不能更宽,运行期按对象的实际类型分派。重写建议加 @Override 让编译器兜底。

  2. 创建线程的方式:继承 Thread 重写 run;实现 Runnable 交给 Thread;实现 Callable 配合 FutureTask 拿到返回值;用线程池(ThreadPoolExecutor / Executors)提交任务,这也是生产上唯一推荐的方式;Java 8 起还可以用 CompletableFuture 做异步编排,Java 21 有虚拟线程。前两种本质一样,区别只是能不能再继承别的类。

  3. 内部类的作用:一是封装,把只服务于外部类的辅助实现藏起来,比如迭代器的具体实现;二是能访问外部类的私有成员,写回调和事件监听时很方便;三是静态内部类不持有外部类引用,做单例、Builder 都很合适,还能避免内存泄漏;四是匿名内部类让一次性的接口实现写起来更紧凑。代价是编译后会生成独立的 Outer$Inner.class,非静态内部类持有外部引用,序列化和内存占用要留意。

  4. 二叉搜索树的条件:对树中任意节点,其左子树上所有节点的值都小于该节点,右子树上所有节点的值都大于该节点,且左右子树本身也都是二叉搜索树。注意是「子树里所有节点」而不是「左右孩子」,这是最常见的答错点。中序遍历能得到严格递增序列,查找、插入、删除的平均复杂度是 O(log n),但退化成链表时会变成 O(n),所以工程上用平衡树(红黑树、AVL)或 B+树。

  5. 快速排序过程:从待排序列里选一个基准值(pivot),通常取首元素、随机元素或三数取中;用双指针从两端向中间扫描,把小于基准的放到左边、大于基准的放到右边,这一趟叫分区(partition),基准落到了最终位置;然后对左右两个子区间递归做同样的事。平均时间复杂度 O(n log n),最坏 O(n²)(每次选的基准都是极值,比如对已排序数组取首元素),原地排序、不稳定。工程实现里会优化成小区间用插入排序、三路分区处理大量重复元素、递归太深转迭代。

  6. 数据库查询优化:先定位问题,开慢查询日志、用 EXPLAIN 看执行计划里有没有全表扫描和 filesort。然后是加对索引——联合索引遵守最左前缀、把区分度高的列放前面、尽量走覆盖索引避免回表;SQL 层面避免 SELECT *、避免在索引列上做函数运算和隐式类型转换(会让索引失效)、深分页改成基于游标或延迟关联;再往上还有架构层面:读写分离、热点数据缓存、合理的分库分表、大事务拆小。索引不是越多越好,写放大和维护成本要算进去。

  7. 父任务等子任务完成后聚合结果:最简单的是 CountDownLatch,主线程 await,每个子任务结束后 countDown;需要拿返回值就用 CompletableFuture.allOf(...) 再对每个 future 调 join 聚合,或者用 invokeAll 提交一批任务。如果要顺序处理结果可以用 ExecutorCompletionService,谁先完成先拿谁。分布式场景下本地锁不管用,得靠状态表 + 乐观锁/版本号,或者用消息中间件把子任务的完成事件汇总起来再判断「全部到齐」。

  8. 消息队列避免重复消费:重复投递在 MQ 里基本无法根除(网络抖动、消费成功但 ack 丢失、重平衡都会导致重投),所以正确做法是让消费端幂等,而不是指望消息只来一次。常见手段:用业务唯一键建唯一索引,重复插入直接冲突;用 Redis 的 SETNX 或去重表记录已处理的消息 ID,处理前先查、处理完写入,注意两步要在一起做原子性;用状态机做幂等(已支付的单子收到重复回调直接返回成功);更新类操作尽量设计成幂等语义,比如用 UPDATE ... SET status = 'PAID' WHERE id = ? AND status = 'UNPAID' 靠受影响行数判断;另外消费成功后及时 ack、避免超时重投,也是减少重复的一部分。

  9. 单词拆分:典型动态规划。设 dp[i] 表示 s 的前 i 个字符能否被字典拼出,初始 dp[0] = true;枚举分割点 j < i,只要 dp[j] 为真且 s[j..i) 在字典里,就有 dp[i] = true。字符串集合用哈希表存,判断子串用 substring 即可,时间复杂度 O(n²),空间 O(n)。也可以用记忆化 DFS 从前往后尝试匹配。