宁波银行总行科技 技术面面经
- 轮次
- 技术面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 自我介绍
- Java 中重载和重写分别是什么?
- 创建线程有哪些常见方式?
- 内部类有什么作用?
- 构造二叉搜索树需要满足哪些条件?
- 请简述快速排序的基本过程
- 数据库查询优化一般有哪些方法?
- 父任务等所有子任务完成后,如何同步状态、聚合结果?
- 消息队列的消息怎么避免重复消费?
- 手撕:单词拆分
《参考解析》
-
重载与重写:重载(Overload)发生在同一个类里,方法名相同、参数列表不同,与返回值无关,编译期就确定调用哪个版本,本质是静态分派。重写(Override)发生在父子类之间,子类重新实现父类的方法,要求方法签名相同、访问权限不能收窄、抛出的检查异常不能更宽,运行期按对象的实际类型分派。重写建议加
@Override让编译器兜底。 -
创建线程的方式:继承
Thread重写run;实现Runnable交给Thread;实现Callable配合FutureTask拿到返回值;用线程池(ThreadPoolExecutor/Executors)提交任务,这也是生产上唯一推荐的方式;Java 8 起还可以用CompletableFuture做异步编排,Java 21 有虚拟线程。前两种本质一样,区别只是能不能再继承别的类。 -
内部类的作用:一是封装,把只服务于外部类的辅助实现藏起来,比如迭代器的具体实现;二是能访问外部类的私有成员,写回调和事件监听时很方便;三是静态内部类不持有外部类引用,做单例、Builder 都很合适,还能避免内存泄漏;四是匿名内部类让一次性的接口实现写起来更紧凑。代价是编译后会生成独立的
Outer$Inner.class,非静态内部类持有外部引用,序列化和内存占用要留意。 -
二叉搜索树的条件:对树中任意节点,其左子树上所有节点的值都小于该节点,右子树上所有节点的值都大于该节点,且左右子树本身也都是二叉搜索树。注意是「子树里所有节点」而不是「左右孩子」,这是最常见的答错点。中序遍历能得到严格递增序列,查找、插入、删除的平均复杂度是 O(log n),但退化成链表时会变成 O(n),所以工程上用平衡树(红黑树、AVL)或 B+树。
-
快速排序过程:从待排序列里选一个基准值(pivot),通常取首元素、随机元素或三数取中;用双指针从两端向中间扫描,把小于基准的放到左边、大于基准的放到右边,这一趟叫分区(partition),基准落到了最终位置;然后对左右两个子区间递归做同样的事。平均时间复杂度 O(n log n),最坏 O(n²)(每次选的基准都是极值,比如对已排序数组取首元素),原地排序、不稳定。工程实现里会优化成小区间用插入排序、三路分区处理大量重复元素、递归太深转迭代。
-
数据库查询优化:先定位问题,开慢查询日志、用
EXPLAIN看执行计划里有没有全表扫描和 filesort。然后是加对索引——联合索引遵守最左前缀、把区分度高的列放前面、尽量走覆盖索引避免回表;SQL 层面避免SELECT *、避免在索引列上做函数运算和隐式类型转换(会让索引失效)、深分页改成基于游标或延迟关联;再往上还有架构层面:读写分离、热点数据缓存、合理的分库分表、大事务拆小。索引不是越多越好,写放大和维护成本要算进去。 -
父任务等子任务完成后聚合结果:最简单的是
CountDownLatch,主线程await,每个子任务结束后countDown;需要拿返回值就用CompletableFuture.allOf(...)再对每个 future 调join聚合,或者用invokeAll提交一批任务。如果要顺序处理结果可以用ExecutorCompletionService,谁先完成先拿谁。分布式场景下本地锁不管用,得靠状态表 + 乐观锁/版本号,或者用消息中间件把子任务的完成事件汇总起来再判断「全部到齐」。 -
消息队列避免重复消费:重复投递在 MQ 里基本无法根除(网络抖动、消费成功但 ack 丢失、重平衡都会导致重投),所以正确做法是让消费端幂等,而不是指望消息只来一次。常见手段:用业务唯一键建唯一索引,重复插入直接冲突;用 Redis 的
SETNX或去重表记录已处理的消息 ID,处理前先查、处理完写入,注意两步要在一起做原子性;用状态机做幂等(已支付的单子收到重复回调直接返回成功);更新类操作尽量设计成幂等语义,比如用UPDATE ... SET status = 'PAID' WHERE id = ? AND status = 'UNPAID'靠受影响行数判断;另外消费成功后及时 ack、避免超时重投,也是减少重复的一部分。 -
单词拆分:典型动态规划。设
dp[i]表示s的前i个字符能否被字典拼出,初始dp[0] = true;枚举分割点j < i,只要dp[j]为真且s[j..i)在字典里,就有dp[i] = true。字符串集合用哈希表存,判断子串用substring即可,时间复杂度 O(n²),空间 O(n)。也可以用记忆化 DFS 从前往后尝试匹配。