襄阳市网站建设_网站建设公司_域名注册_seo优化
2025/12/31 20:26:53 网站建设 项目流程

📌 Java后端篇(15题)

1. 说说JVM的内存结构?

答案框架(记忆口诀:堆栈方本程)

JVM内存分为5个区域:

  • 堆(Heap):存放对象实例,是GC的主要区域,分为新生代(Eden、S0、S1)和老年代
  • 栈(Stack):每个线程私有,存局部变量、方法调用,栈帧包含局部变量表、操作数栈、动态链接、返回地址
  • 方法区(Method Area):存类信息、常量、静态变量,JDK8后叫元空间(Metaspace)使用直接内存
  • 本地方法栈:为Native方法服务
  • 程序计数器:线程私有,记录当前执行的字节码行号

面试加分项:堆是线程共享的,栈是线程私有的;新生代和老年代比例默认1:2


2. Java的垃圾回收机制(GC)?

答案框架(记忆:识别、算法、收集器)

如何识别垃圾

  • 引用计数法(循环引用问题)
  • 可达性分析算法:从GC Roots出发,不可达的对象是垃圾

GC算法

  • 标记-清除:产生碎片
  • 标记-整理:老年代用,整理内存
  • 复制算法:新生代用,Eden和Survivor区8:1:1
  • 分代收集:新生代复制,老年代标记整理

常见收集器

  • Serial:单线程,适合客户端
  • Parallel:多线程,吞吐量优先
  • CMS:并发标记清除,停顿时间短
  • G1:JDK9默认,面向服务端,可预测停顿
  • ZGC:JDK11,超低延迟

3. HashMap底层原理?JDK1.7和1.8的区别?

底层结构

  • JDK1.7:数组+链表
  • JDK1.8:数组+链表+红黑树(链表长度>8且数组长度>=64时转红黑树)

put流程

  1. 计算key的hash值
  2. 根据hash值找到数组下标
  3. 如果位置为空,直接插入
  4. 如果不为空,判断key是否相同,相同则覆盖value
  5. 不同则遍历链表/红黑树插入

关键参数

  • 初始容量:16
  • 负载因子:0.75
  • 扩容:容量翻倍,重新hash(JDK1.8优化了rehash)

1.7和1.8区别

  • 1.7:头插法(并发可能死循环),先扩容后插入
  • 1.8:尾插法,先插入后扩容,引入红黑树

4. ConcurrentHashMap实现原理?

JDK1.7:分段锁(Segment数组),每个Segment是ReentrantLock

JDK1.8

  • 取消Segment,使用Node数组+CAS+synchronized
  • 锁粒度更细:锁住数组的某个位置(链表或红黑树头节点)
  • put流程:CAS尝试插入,失败则synchronized锁住头节点

为什么线程安全

  • 初始化用CAS
  • 添加元素用synchronized锁住桶
  • 扩容时协助扩容(多线程)

5. Spring Bean的生命周期?

记忆口诀:实例化、填充、初始化、使用、销毁

  1. 实例化:通过反射创建Bean实例
  2. 属性填充:依赖注入(@Autowired)
  3. Aware接口回调:BeanNameAware、BeanFactoryAware等
  4. BeanPostProcessor前置处理:postProcessBeforeInitialization
  5. 初始化:@PostConstruct、InitializingBean、init-method
  6. BeanPostProcessor后置处理:postProcessAfterInitialization(AOP在这里)
  7. 使用Bean
  8. 销毁:@PreDestroy、DisposableBean、destroy-method

面试加分项:循环依赖用三级缓存解决(单例、非构造器注入)


6. Spring的事务传播机制?

7种传播行为(记重点3个)

必记

  • REQUIRED(默认):有事务加入,没有创建新的
  • REQUIRES_NEW:总是创建新事务,挂起当前事务
  • NESTED:嵌套事务,外层回滚内层也回滚,内层回滚外层可以不回滚

其他:

  • SUPPORTS:有就用,没有就非事务
  • NOT_SUPPORTED:非事务执行,挂起当前事务
  • MANDATORY:必须有事务,否则抛异常
  • NEVER:必须非事务,否则抛异常

失效场景

  1. 方法不是public
  2. 同一类内部调用(没走代理)
  3. 异常被catch没抛出
  4. 抛出的不是RuntimeException或Error

7. Spring Boot自动配置原理?

核心注解@SpringBootApplication=@Configuration+@EnableAutoConfiguration+@ComponentScan

自动配置流程

  1. @EnableAutoConfiguration导入AutoConfigurationImportSelector
  2. 读取spring.factories文件的配置类全限定名
  3. 通过@Conditional条件注解判断是否生效
  4. 满足条件则加载配置类到容器

常见@Conditional

  • @ConditionalOnClass:类存在
  • @ConditionalOnMissingBean:Bean不存在
  • @ConditionalOnProperty:配置属性匹配

8. MySQL索引原理?为什么用B+树?

索引类型

  • 主键索引(聚簇索引):叶子节点存数据
  • 普通索引(二级索引):叶子节点存主键值,需要回表查询
  • 唯一索引、全文索引

为什么用B+树

  1. B+树vs二叉树:B+树高度低,磁盘IO次数少(百万数据3-4层)
  2. B+树vs B树
    • B+树非叶子节点不存数据,能存更多索引,扇出更大
    • B+树叶子节点有链表,范围查询效率高
    • B+树更稳定,查询都是从根到叶子

面试加分项

  • 聚簇索引:InnoDB主键索引
  • 非聚簇索引:MyISAM,叶子节点存地址
  • 回表:通过二级索引查到主键,再用主键查完整数据

9. MySQL事务隔离级别?如何解决幻读?

4种隔离级别(记忆:未读提不可串)

  1. 读未提交(READ UNCOMMITTED):脏读、不可重复读、幻读都有
  2. 读已提交(READ COMMITTED):解决脏读,Oracle默认
  3. 可重复读(REPEATABLE READ):解决不可重复读,MySQL默认,通过MVCC+间隙锁解决幻读
  4. 串行化(SERIALIZABLE):全部解决,但性能差

问题定义

  • 脏读:读到未提交的数据
  • 不可重复读:同一事务两次读数据不一致(其他事务UPDATE)
  • 幻读:同一事务两次查询记录数不一致(其他事务INSERT/DELETE)

MySQL如何解决幻读

  • MVCC(多版本并发控制):快照读,读历史版本
  • Next-Key Lock(记录锁+间隙锁):当前读,锁住范围

10. Redis数据类型及应用场景?

5种基本类型

  1. String:缓存、计数器、分布式锁(SETNX)
  2. Hash:存对象,如用户信息
  3. List:消息队列、文章列表、LPUSH+RPOP
  4. Set:去重、共同好友、抽奖(SRANDMEMBER)
  5. ZSet(有序集合):排行榜、延时队列

3种特殊类型

  • HyperLogLog:UV统计(基数统计)
  • Bitmap:签到、布隆过滤器
  • GEO:地理位置

11. Redis持久化机制?

两种方式

RDB(快照)

  • 优点:文件小,恢复快
  • 缺点:可能丢失最后一次快照后的数据
  • 触发:SAVE、BGSAVE、自动触发(save 900 1)

AOF(Append Only File)

  • 优点:数据完整性好
  • 缺点:文件大,恢复慢
  • 策略:always(每次写)、everysec(每秒)、no(操作系统决定)

混合持久化(4.0+)

  • RDB作为基础,AOF记录增量
  • 兼顾恢复速度和数据安全

12. Redis缓存穿透、击穿、雪崩?

缓存穿透:查询不存在的数据,缓存和数据库都没有

  • 解决:布隆过滤器、缓存空值

缓存击穿:热点key过期,大量请求打到数据库

  • 解决:热点数据永不过期、互斥锁(setnx)

缓存雪崩:大量key同时过期或Redis宕机

  • 解决:过期时间加随机值、Redis集群、限流降级

13. 分布式锁的实现方式?

Redis实现

// 加锁:SETNX + 过期时间(原子性) SET lock_key unique_value NX PX 30000 // 解锁:Lua脚本保证原子性 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end

注意点

  • value要唯一(UUID),防止误删
  • 设置过期时间,防止死锁
  • 使用Redisson框架(看门狗机制)

其他实现

  • Zookeeper:临时顺序节点
  • 数据库:乐观锁(version)、悲观锁(for update)

14. 线程池核心参数及执行流程?

7个核心参数

  1. corePoolSize:核心线程数
  2. maximumPoolSize:最大线程数
  3. keepAliveTime:空闲线程存活时间
  4. unit:时间单位
  5. workQueue:阻塞队列
  6. threadFactory:线程工厂
  7. handler:拒绝策略

执行流程

  1. 线程数 < corePoolSize:创建核心线程
  2. 线程数 >= corePoolSize:放入队列
  3. 队列满 && 线程数 < maximumPoolSize:创建非核心线程
  4. 线程数 >= maximumPoolSize:执行拒绝策略

4种拒绝策略

  • AbortPolicy:抛异常(默认)
  • CallerRunsPolicy:调用者线程执行
  • DiscardPolicy:直接丢弃
  • DiscardOldestPolicy:丢弃最老的任务

15. 消息队列如何保证消息不丢失?(以RabbitMQ为例)

三个环节

1. 生产者->MQ:发送确认

  • 开启confirm机制,收到ack才算成功
  • 失败重试或记录日志

2. MQ自身:持久化

  • 交换机、队列、消息都设置持久化
  • durable=true

3. MQ->消费者:手动ack

  • 关闭自动ack
  • 业务处理成功后手动确认
  • 失败后requeue或放入死信队列

额外保障

  • 消费端幂等性处理(防止重复消费)
  • 消息表记录状态,定时任务补偿

🎨 React前端篇(15题)

16. React的虚拟DOM和Diff算法?

虚拟DOM:用JS对象描述真实DOM树,对比差异后批量更新

Diff算法三大策略

  1. Tree Diff:只比较同层节点,不跨层级
  2. Component Diff:同类型组件才对比Virtual DOM,不同类型直接替换
  3. Element Diff:同层元素通过key识别,移动而非重建

key的作用

  • 唯一标识,帮助React识别哪些元素改变
  • 不要用index作为key(顺序变化会导致全部重新渲染)

React 18的并发渲染

  • Fiber架构:可中断的渲染
  • 时间切片:把渲染任务拆分,不阻塞主线程

17. React Hooks为什么不能在条件语句中使用?

原因:Hooks依赖调用顺序来维护状态

React内部用链表存储Hooks,每次渲染按顺序调用:

  • 第一个useState对应链表第一个节点
  • 第二个useEffect对应第二个节点

如果在条件语句中使用:

// ❌ 错误示例 if (condition) { useState(0); // 可能不执行,打乱顺序 }

链表顺序错乱,导致状态错乱

规则

  • 只在顶层调用Hooks
  • 只在React函数中调用Hooks

18. useState和useReducer的区别?

useState

  • 适合简单状态
  • 直接更新值
const [count, setCount] = useState(0); setCount(count + 1);

useReducer

  • 适合复杂状态逻辑
  • 通过dispatch(action)更新
  • 类似Redux,可预测的状态管理
const [state, dispatch] = useReducer(reducer, initialState); dispatch({ type: 'increment' });

使用场景

  • 多个子值组成的复杂对象:useReducer
  • 下一个状态依赖前一个:useReducer
  • 简单的独立值:useState

19. useEffect和useLayoutEffect的区别?

执行时机

  • useEffect:异步执行,DOM更新后、浏览器绘制后
  • useLayoutEffect:同步执行,DOM更新后、浏览器绘制前

使用场景

  • useEffect:99%的场景,数据获取、订阅、副作用
  • useLayoutEffect:需要同步测量DOM、避免闪烁(如动画、滚动位置)

例子

// 会闪烁 useEffect(() => { ref.current.style.top = '100px'; // 先渲染再改样式 }, []); // 不会闪烁 useLayoutEffect(() => { ref.current.style.top = '100px'; // 渲染前就改样式 }, []);

20. React性能优化手段?

1. 避免不必要的渲染

  • React.memo:函数组件浅比较props
  • useMemo:缓存计算结果
  • useCallback:缓存函数引用
  • PureComponent:类组件浅比较props和state

2. 代码分割

  • React.lazy + Suspense:路由懒加载
  • 动态import()

3. 虚拟列表

  • react-window、react-virtualized
  • 只渲染可见区域

4. 合理使用key

  • 列表渲染必须有唯一key
  • 不用index作key

5. 避免内联对象和函数

// ❌ 每次渲染都创建新对象 <Child style={ { color: 'red' }} /> // ✅ 提取到外部 const style = { color: 'red' }; <Child style={style} />

21. React的状态管理方案对比?

1. Redux

  • 优点:可预测、时间旅行、中间件生态
  • 缺点:样板代码多、学习曲线陡
  • 适合:大型应用、复杂状态

2. Zustand

  • 优点:API简洁、无Provider包裹、性能好
  • 适合:中小型应用

3. Mobx

  • 优点:响应式、代码少
  • 缺点:不够明确、调试困难
  • 适合:快速开发

4. Context + useReducer

  • 优点:原生、无需引入库
  • 缺点:性能问题(Provider的value变化所有消费者重渲染)
  • 适合:轻量级状态共享

5. Redux Toolkit(推荐)

  • Redux的现代封装
  • 简化Redux代码
  • 内置immer、thunk

22. 闭包陷阱及解决方案?

问题

function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { setCount(count + 1); // count永远是0,闭包陷阱! }, 1000); return () => clearInterval(timer); }, []); // 依赖数组为空,只执行一次 }

解决方案

方法1:使用函数式更新

setCount(prevCount => prevCount + 1);

方法2:添加依赖

useEffect(() => { const timer = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(timer); }, [count]); // 每次count变化重新创建定时器

方法3:使用useRef

const countRef = useRef(count); countRef.current = count;

23. React Router的原理?

两种模式

1. Hash模式

  • URL:http://example.com/#/home
  • 监听:window.addEventListener('hashchange', fn)
  • 特点:兼容性好,但URL有#号

2. History模式(主流)

  • URL:http://example.com/home
  • API:history.pushState()history.replaceState()
  • 监听:window.addEventListener('popstate', fn)
  • 注意:需要服务端配置,否则刷新404

实现原理

  1. 通过Context传递路由信息
  2. 监听URL变化
  3. 匹配路由组件
  4. 渲染对应组件

24. React事件机制(合成事件)?

特点

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询