重生——第十一次面试之挖财一面2026.8.19已OC
1. ArrayList 与 LinkedListArrayList底层是Object[]数组默认初始容量 10扩容为原容量 1.5 倍通过Arrays.copyOf完成数组拷贝。支持随机访问查询 O (1)中间插入、删除需要移动数组元素效率低线程不安全允许存 null元素有序。LinkedList底层双向链表节点维护 prev、next、value。头尾增删 O (1)随机访问需要遍历链表get (i) 时间复杂度 O (n)线程不安全允许 null元素有序。面试要点遍历优先 ArrayList频繁头尾增删选 LinkedList禁止 for 循环中get(i)遍历 LinkedList大数据量 ArrayList 建议指定初始容量减少扩容。2. HashMapJDK8底层结构数组 链表 红黑树核心参数默认容量 16 扩容阈值 容量 × 负载因子链表长度8且数组长度≥64链表转为红黑树红黑树节点数量小于 6 退化成链表。hash 计算(h key.hashCode()) ^ (h 16)高低 16 位异或减少 hash 冲突。put 流程计算 hash 定位数组下标桶位为空直接放入桶位不为空key 完全相等直接覆盖树节点走红黑树插入链表遍历到尾部追加插入完成判断是否达到扩容阈值触发扩容。扩容容量翻倍JDK8 不再全部重算 hash通过hash oldCap判断元素留在原下标还是迁移到原下标oldCap。线程不安全JDK7 并发 put 会环形链表死循环JDK8 消除死循环但依旧存在数据覆盖丢失。追问负载因子 0.75是泊松分布下冲突概率和内存占用的权衡。3. ConcurrentHashMapJDK8JDK7Segment 分段锁Segment 继承 ReentrantLock分段加锁。 JDK8废弃 Segment采用 CAS synchronized 锁住桶头节点锁粒度降低到链表 / 红黑树根节点并发能力更高。底层结构和 HashMap 一致数组 链表 红黑树。put 逻辑数组未初始化先完成初始化桶位为 null使用 CAS 尝试写入桶位有数据synchronized 锁定桶头执行链表 / 红黑树插入支持多线程协助扩容。计数size()使用baseCount基础计数 counterCells竞争计数求和得到总数不保证强一致性key、value 都不允许为 null。面试重点synchronized 经过锁升级优化性能提升直接复用对象头 markword不需要额外锁对象不能完全替代 Hashtablesize、containsValue 这类聚合操作是弱一致。4. Synchronized 锁升级对象头 MarkWord 存储锁标记锁只能升级不能降级无锁 → 偏向锁 → 轻量级锁 → 重量级锁。偏向锁适用于单线程反复获取锁。MarkWord 记录持有锁的线程 ID获取锁不需要 CAS出现竞争就撤销偏向锁。JDK15 默认关闭偏向锁。轻量级锁多线程交替使用锁无真正并发竞争。线程在栈帧创建锁记录CAS 替换对象 MarkWord自旋抢锁不阻塞消耗 CPU。竞争加剧自旋失败膨胀重量级锁。重量级锁向操作系统申请互斥锁竞争失败的线程阻塞进入内核态。自适应自旋JVM 动态调整自旋次数不是固定数值。5. 线程池原理与参数七大构造参数corePoolSize核心线程数maximumPoolSize最大线程数keepAliveTime非核心线程空闲存活时间unit时间单位workQueue阻塞队列存放等待任务threadFactory线程工厂创建线程handler拒绝策略任务执行流程任务到来线程数小于核心线程数新建核心线程执行核心线程已满任务放入阻塞队列队列满线程数小于最大线程数创建非核心线程线程达到最大线程数执行拒绝策略。四种拒绝策略AbortPolicy抛出异常默认策略DiscardPolicy直接丢弃任务无异常DiscardOldestPolicy丢弃队列队头任务重新提交当前任务CallerRunsPolicy由提交任务的调用者线程执行任务面试必考点禁止直接使用 Executors 工具类。newFixedThreadPool无界队列容易 OOMnewCachedThreadPool最大线程无上限线程暴涨 OOM业务使用ThreadPoolExecutor手动构建。 默认核心线程不会回收开启allowCoreThreadTimeOuttrue核心线程也会超时回收。6. Spring Bean 的生命周期实例化通过构造函数反射创建对象此时仅仅完成对象创建属性未赋值。属性填充 populateBean依赖注入给对象属性赋值。初始化阶段BeanPostProcessor#postProcessBeforeInitialization前置处理PostConstruct注解方法InitializingBean#afterPropertiesSet()xml 配置init‑method指定方法BeanPostProcessor#postProcessAfterInitialization后置处理AOP 代理在这里生成Bean 就绪供业务使用。销毁阶段容器关闭PreDestroy注解方法DisposableBean#destroy()xml 配置destroy‑method指定销毁方法记忆顺序实例化 → 属性注入 → 前置后置处理器、PostConstruct、afterPropertiesSet、init‑method → AOP 代理生成 → 使用 → 销毁。 注意BeanPostProcessor 本身不会被 AOP 代理。7. SpringBoot 启动流程自动装配原理SpringBoot 启动流程调用SpringApplication.run()实例化SpringApplication推断 web 应用类型读取META‑INF/spring.factories加载ApplicationContextInitializer、ApplicationListener。run 方法内部启动计时器获取监听器发布应用启动事件构建 Environment加载 application 配置文件创建 ApplicationContext 容器执行容器初始化器回调执行refresh()刷新 Spring 容器Spring 核心流程执行ApplicationRunner、CommandLineRunner。自动装配原理EnableAutoConfigurationEnableAutoConfiguration导入AutoConfigurationImportSelectorselectImports()读取META‑INF/spring.factories拿到全部自动配置类全限定名通过ConditionalOnClass、ConditionalOnMissingBean条件注解满足条件才创建 Bean。SpringBoot3 变更废弃 spring.factories使用META‑INF/spring/org.springframework.boot.autoconfigure.imports。8. Mysql 索引失效索引失效会触发全表扫描explain 中 type 为 ALL。like %xxx通配符 % 写在开头like xxx%索引有效。索引字段上使用函数、运算例如where date(create_time)2026‑01‑01。隐式类型转换字段和传入参数类型不一致。or 条件一侧字段没有索引整个条件索引失效。联合索引不遵循最左前缀原则。!、not in、is not null不一定完全失效数据占比高优化器放弃索引。MySQL 优化器评估全表扫描比走索引更快主动放弃索引。排查使用 explain 看执行计划。9. 隐式转换MySQL 做、、、in比较时两边字段类型不一致MySQL 自动做类型转换。 示例user_id是 varchar 索引字段where user_id 1001字符串和数字对比字符串会被转为数字等价于where cast(user_id as signed)1001索引字段被函数包裹索引失效。实操规范字符串类型字段条件值加引号数字字段传入数字避免类型错位。10. 索引下推 ICPIndex Condition PushdownMySQL5.6 特性仅支持二级索引。 没有 ICP存储引擎拿到索引记录回表读取完整行数据在 MySQL Server 层过滤 where 条件。 开启 ICP存储引擎层直接利用索引内的字段过滤条件不满足条件直接丢弃减少回表次数。适用场景联合索引where 中部分条件属于索引列但不能用于定位只能用来过滤。 explain 中 Extra 字段输出Using index condition代表索引下推生效。ICP 不能替代最左前缀原则只是减少回表。11. 线上慢 SQL 排查开启慢查询日志slow_query_log设置long_query_time阈值记录执行超时 SQL。show processlist查看正在运行的会话观察耗时较高 SQL。explain分析执行计划重点看 type、key、rows、Extratype 尽量 ref/range 以上ALL 代表全表扫描。show profile观察 SQL 各个执行阶段耗时performance_schema监控数据库运行 SQL。优化方向 建立合适索引规避索引失效禁止 select *大分页做延迟关联主键分页大表分库分表业务层增加缓存减轻 DB 压力。注意explain 不要在线上业务流量下执行复制 SQL 到测试环境分析。12. Redis 常见数据结构类型底层编码业务场景StringSDS 简单动态字符串缓存、计数器、分布式锁Listquicklist简易消息队列、栈队列Hashziplist / hashtable存储对象用户信息Setintset / hashtable去重、交集并集ZSet(SortedSet)ziplist / 跳表 skiplist排行榜、延时队列小数据量使用压缩列表 ziplist 节省内存超过阈值自动切换为哈希、跳表。13. Redisson 分布式锁的数据结构与底层原理数据结构Hash。key 为锁名hash key 是UUID:线程IDvalue 为锁重入计数。核心原理可重入锁同一个线程多次加锁计数 1释放锁计数‑1计数等于 0 才删除锁 key。看门狗 WatchDog 续期没有手动指定 leaseTime 时获取锁成功开启后台定时任务默认每 10s 续期锁过期时间锁默认 30s防止业务未执行完锁过期手动指定过期时间看门狗失效。Lua 脚本保证原子性加锁、释放锁全部使用 Lua 脚本执行保证多条命令原子执行。锁等待拿不到锁订阅锁释放消息阻塞等待不是无限自旋。Redlock 红锁多独立 Redis 节点半数以上获取锁才算拿到锁性能差生产很少使用。分布式锁四大要求互斥、防死锁、可重入、高可用。13. SortedSet 底层实现两种编码ziplist 压缩列表元素少、score 与 member 内容较小时连续存储 member 和 score保持有序。skiplist 跳表数据量超过阈值切换同时维护一张哈希表O (1) 通过 member 获取 score跳表负责排序、范围查询。zset 跳表做有序遍历 hash 映射快速查 score。14. 跳表 skiplist多层有序链表底层是完整有序链表上层是下层的索引插入节点随机生成层数。 查询从最高层索引向右遍历数值大于目标就向下一层时间复杂度 O (logn)。 插入随机生成节点层数更新多层链表指针。 删除逐层删除对应节点。对比红黑树实现简单范围遍历性能优异缺点是索引带来额外内存开销。Redis zset 选用跳表。15. 拷打实习16. 消息推模式和拉模式推模式 PushBroker 主动把消息推送给消费者。 优点实时性高缺点Broker 推送速率不受消费者控制消费能力不足会压垮消费者代表ActiveMQ。拉模式 Pull消费者主动请求 Broker 拉取消息。 优点消费速率由消费者自己控制不会被压垮缺点轮询会带来延迟空轮询消耗 CPU代表Kafka、RocketMQ。Kafka 长轮询优化没有消息的时候Broker hold 住请求有消息到达或者超时才返回兼顾实时性与性能。17. MQ 重复消费根本原因业务处理完成offset 提交失败Broker 重新投递消息。MQ 只保证At‑least‑once 至少一次投递不保证恰好一次。解决方案业务幂等主流方案重复消费业务结果不变。数据库唯一索引约束业务唯一 id 存入 Redis 标记已消费业务处理完成之后再提交 offset禁止先提交 offset本地消息表数据库事务保证。不能依赖 MQ 保证不重复网络抖动不可避免业务层必须做幂等。18. 拷打项目19.手搓1. 环形链表 判断链表是否有环class ListNode{ int val; ListNode next; } //快慢指针 public boolean hasCycle(ListNode head){ ListNode fasthead,slowhead; while(fast!nullfast.next!null){ fastfast.next.next; slowslow.next; if(fastslow) return true; } return false; }2.LRU 缓存双向链表维护访问顺序HashMap 做映射get、put O (1)class LRUCache { static class Node{ int key,val; Node prev,next; Node(int k,int v){keyk;valv;} } private MapInteger,Node mapnew HashMap(); private Node head,tail; private int capacity; public LRUCache(int cap){ capacitycap; headnew Node(-1,-1); tailnew Node(-1,-1); head.nexttail; tail.prevhead; } private void remove(Node node){ node.prev.nextnode.next; node.next.prevnode.prev; } private void addToHead(Node node){ node.nexthead.next; node.prevhead; head.next.prevnode; head.nextnode; } private void moveToHead(Node node){ remove(node); addToHead(node); } private Node removeTail(){ Node deltail.prev; remove(del); return del; } public int get(int key) { if(!map.containsKey(key)) return -1; Node nodemap.get(key); moveToHead(node); return node.val; } public void put(int key, int value) { if(map.containsKey(key)){ Node nodemap.get(key); node.valvalue; moveToHead(node); }else{ Node newNodenew Node(key,value); map.put(key,newNode); addToHead(newNode); if(map.size()capacity){ Node delremoveTail(); map.remove(del.key); } } } }3. 反转链表class ListNode{ int val; ListNode next; } //迭代 public ListNode reverseList(ListNode head) { ListNode prenull; ListNode curhead; while(cur!null){ ListNode nextcur.next; cur.nextpre; precur; curnext; } return pre; } //递归版本 public ListNode reverse(ListNode head){ if(headnull||head.nextnull) return head; ListNode newHeadreverse(head.next); head.next.nexthead; head.nextnull; return newHead; }