2025java面试实战记录
1、两个人同时打开了一个表单,A先修改完成并提交成功了,B没有刷新页面,也做了修改数据并提交。
问:如何保证数据一致性,即A的修改不会被B覆盖
解决方案:使用乐观锁。表里增加版本号字段,更新的时候根据版本号来(也可以用时间戳,只是存在精度问题)
2、假设一张表有姓名、分数、课程三个字段,如何查出所有课程分数都大于80分的姓名?
解决方案:1、使用分组 加上 条件:select name from table group by name having min(score) > 80;
2、使用子查询,先查出分数中有小于等于80的姓名,再取反: select distinct name from table where name not in (select distinct name from table where score <=80);
3、springboot中如何校验控制器中请求传参的个数、字段名是否合法?有多余字段则报错拦截等如何实现?
解决方案:1、在接收参数的DTO 上添加注解@JsonIgnoreProperties( ignoreUnknown = false),存在未定义的字段即报错;
2、或者跟1类似的解决方案,全局配置application.properties中配置,遇到未知字段时抛出异常: spring.jackson.deserialization.fail-on-unknown-properties = true
效果就是当请求包含多余字段时,自动抛出未辨认属性异常UnrecognizedPropertyException
3、第三种解决方案就是使用自定义参数校验器。
(1)使用spring validation实现自定义校验逻辑
public class NoExtraFieldsValidator implements ConstraintValidator<NoExtraFields, Object> {
private Set<String> allowedFields;
@Override
public void initialize(NoExtraFields constraint) {
allowedFields = new HashSet<>(Arrays.asList(constraint.allowedFields()));
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);
Set<String> actualFields = Arrays.stream(wrapper.getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
actualFields.removeAll(allowedFields);
if (!actualFields.isEmpty()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("存在非法字段: " + actualFields).addConstraintViolation();
return false;
}
return true;
}
}
(2)定义注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = NoExtraFieldsValidator.class)
public @interface NoExtraFields {
String message() default "存在非法字段";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] allowedFields(); // 允许的字段列表
}
(3)在DTO上使用注解:
@NoExtraFields(allowedFields = {"name", "age"})
public class UserDTO {
private String name;
private Integer age;
}
4、token认证的实现是怎样的?
解答:token认证是web应用中常见的身份验证方式,核心思路是服务端生成一个代表用户身份和权限的令牌token,客户端在后续请求中携带该token,服务端通过验证token的合法性来判断用户身份
流程:(1)用户发送凭证(用户名/密码、手机号/验证码)调用服务端获取token的接口,服务端验证凭证的正确性,成功则生成一个token返回给客户端;
(2)客户端在请求头Authorization中,使用Bearer方式携带token;
(3)服务端验证token:从Authorization中提取token,验证合法性,签名验证、有效期检查
token一般设置较短的有效期,过期后客户端需要使用refresh token去请求新的access token;
常见token类型:JWT(JSON web token)-最流行,结构是Header.payload.signature,header包含算法和token类型,payload包含用户和token本身的元数据,如用户ID,过期时间,signature对header和payload进行签名,使用header指定的算法和密钥来签名;
5、单点登录是怎么实现的?
名词解释:单点登录single sign-on,SSO,核心目标是让用户在多个关联系统间只需登录一次,即可访问所有授权资源。其实现依赖于集中式认证和信任关系的建立
6、spring是怎么让事务注解@transactional生效的?
解答:spring通过面向切面编程AOP和动态代理机制将@Transactional注解转化为事务管理逻辑,结合事务拦截器和事务管理器实现事务的开启、提交、回滚;
(1)启用事务管理:通过@EnableTransactionManagement开启事务管理功能,spring会注册关键组件如BeanPostProcessor、TransactionInterceptor来支持事务;
(2)代理对象的生成:spring容器在初始化bean时,通过BeanPostProcessor检查bean的方法是否包含@Transactional注解。使用jdk动态代理或CGLIB代理生成代理对象,代理对象会拦截所有标记@Transactional的方法调用;
(3)事务拦截器:代理对象调用方法时,会触发TransactionInterceptor拦截器的invoke方法,执行事务逻辑:获取事务属性-传播行为、隔离级别,创建或加入事务,执行业务方法-调用原始目标方法,提交或回滚事务-根据方法执行结果决定提交或回滚。
(4)事务管理器:具体事务操作如开启、提交、回滚由platformTransactionManager实现。dataSourceTransactionManager基于JDBC连接管理事务;JPATransactionManager用于JPA/Hibernate。事务管理器通过dataSource获取数据库连接并控制其提交和回滚。
7、spring如何解决循环依赖?
解答:通过三级缓存机制解决单例Bean的循环依赖;
(1)一级缓存:存放完全初始化好的bean;
(2)二级缓存:存放早期暴露的bean(已实例化但未填充属性);
(3)三级缓存:存放bean工厂,用于生成早期引用;
举例:当创建bean a时,首先实例化a并通过工厂放入三级缓存。接着填充属性时发现依赖bean b,转而创建b。
b在填充属性时又依赖a,此时从三级缓存中获取a的早期引用,放入二级缓存,完成b的初始化后,a最终完成属性填充并移至一级缓存。
注意:仅支持单例且通过setter/字段注入的循环依赖,构造器注入无法解决(因为构造器注入需要在实例化阶段完成依赖注入,此时bean未放入三级缓存,无法提前暴露使用)。
7.1 构造方法出现了循环依赖怎么解决?
@Lazy懒加载注解,延迟bean的创建直到实际需要时。
8、MySQL为什么设计最左匹配原则?
解答:最左匹配原则由B+树索引结构决定:
联合索引按定义列的顺序构建,如索引(a,b,c),数据按a排序,a相同按b排序,以此类推;
若查询条件不包含最左列,B+树无法利用索引的有序性,导致全表扫描;
9、ThreadLocal内存溢出问题如何出现及解决方案?
原因是:ThreadLocalMap的entry使用弱引用持有threadLocal对象作为key,而entry的value是强引用,导致即使threadLocal对象被回收,key变为null,value仍然被线程的threadLocalMap持有,逐渐累积导致内存泄漏;
解决方案:显式调用remove方法,特别是在线程池场景中
10、hashMap的底层实现以及如何判断key重复?
解答:底层结构是数组+链表,链表长度大于等于8时,转红黑树
key重复判断:(1)计算key的hashcode()确定数组下标; (2)若该位置已有元素,遍历链表/树,用equals()逐一比较key,hashcode相同且equals()返回true,则视为重复,覆盖旧值;
注意:需正确重写hashcode()和equals(),确保逻辑一致;
11、Redis如何扩容?
解答:集群模式扩容:(1)新增节点,通过cluster meet加入集群;(2)迁移哈希槽slot:使用cluster setslot将部分槽从旧节点迁移至新节点;(3)数据转移:通过migrate命令将槽对用的键转移;
单实例扩容:(1)垂直扩容:升级更大内存实例;(2)水平分片:客户端或代理分片到多实例;
主从扩展:增加从节点提升读能力,但需配合集群实现写扩展。
12、用户信息表,主键是ID,用户操作记录表跟用户信息表通过ID关联,如何查询每个用户最新的一条操作记录?
解答:方式一使用row_number() : select * from (
select u.*,ol.*,row_number() over(patition by ol.userid order by ol.operate_time desc) as rn from user u join operate_log ol on u.id = ol.userid) as temp
where rn = 1;
方式二使用子查询:子查询:按用户ID分组,取出用户ID及最大操作时间;再跟用户表、用户操作记录表,联表查询
13、java代码实现读取一个TXT文件,按行匹配有无目标内容:
/**
* 在文件中查找匹配目标字符串的行
*
* @param filePath 文件路径
* @param target 要匹配的目标字符串
* @return 匹配结果列表
* @throws IOException 如果文件读取失败
*/
public static List<MatchResult> findMatchesInFile(String filePath, String target) throws IOException {
List<MatchResult> matches = new ArrayList<>();
Pattern pattern = Pattern.compile(Pattern.quote(target)); // 处理特殊字符
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
int lineNumber = 0;
while ((line = reader.readLine()) != null) {
lineNumber++;
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
matches.add(new MatchResult(lineNumber, line));
}
}
}
return matches;
}
/**
* 存储匹配结果的内部类
*/
static class MatchResult {
int lineNumber;
String lineContent;
MatchResult(int lineNumber, String lineContent) {
this.lineNumber = lineNumber;
this.lineContent = lineContent;
}
}
14、rabbitMQ集群模式的区别
解答:普通集群:队列元数据同步,数据仅存于创建节点,可用性低,一旦创建节点宕机即单点故障,要求低延迟网络,适用于非关键业务,需横向扩展吞吐量;
镜像集群:队列数据全节点复制,主节点挂了,会从镜像节点选出一个作为主,自动故障转移,可用性高,要求高带宽网络,适用于金融、订单等高可用场景;
15、MQ消息积压的处理方案
解答:(1)应急处理方案:增加消费者,提高消费速度;
启用线程池;
rabbitmq开启消息预取channel.basicQos(100),避免单个消费者堆积;
kafka创建临时topic分流:kafka-topics.sh --create --topic emergency-topic --patitions 100
(2)根治措施:优化消费逻辑:批量处理,如kafka的max.poll.records;
异步化处理:非核心操作异步执行;
死信队列监控:设置积压阈值告警,将超出的最早消息放入死信队列,另开消费者去处理
16、慢查询优化与explain命令解读
解答:(1)explain select * from a where b = 1;结果有type访问类型字段,理想值是const/ref/range,key实际使用索引字段,理想值是非NULL,rows扫描行数估算,理想值<1000
(2)慢查询优化:避免索引失效;分页优化where id>10000 limit 20 替代limit 10000,20; 冷热分离:将历史数据归档
17、Redis性能优化方案
解答:(1)内存优化:使用ziplist存储小对象;避免bigkey;
(2)持久化调优:aof-use-rdb-preamble yes混合持久化,降低80%写延迟;
(3)网络优化:客户端连接池jedisPool+ pipeline批量操作,QPS提升5-10倍;
(4)集群扩展:分片集群Redis cluster
18、Redis应用场景示例
解答:(1)排行榜:zset--> zadd leaderboard 100 "user1"; zrevrange leaderboard 0 9 withscores 获取top10
(2)秒杀系统:incr原子计数器+lua脚本扣库存
(3)会话缓存:set user:session:123 '{"id":123}' ex 3600
(4)分布式锁:set lockkey unique-id nx px 30000
19、kafka patition拆分有限制吗?
解答:(1)物理限制:单patition文件数上限500,000,由log.segment.bytes控制; 单broker分区数受操作系统文件句柄限制,需调优ulimit -n
(2)性能影响:分区数越大,并行度越高,吞吐量就越大,但是zookeeper元数据压力剧增,不过新版kafka raft模式已经解决这个问题
(3)黄金法则:推荐分区数 = MAX( 业务吞吐量/单个分区吞吐量,消费者组数量)
通常设置不超过1000个集群,否则控制器故障切换延迟显著增加
20、线程池生产环境配置
@Bean
public ThreadPoolTaskExecutor customExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CPU_CORES * 2); // IO密集型建议2N
executor.setMaxPoolSize(CPU_CORES * 8);
executor.setQueueCapacity(1000); // 根据压测调整
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
21、string、stringBuffer、stringBuilder的区别?
解答:string是不可变的,每次修改都会生成新的对象,适合少量字符串操作;
stringBuffer是可变的,线程安全,因为例如append方法是synchronized修饰的,适合多线程环境;
stringBuilder是可变的,线程不安全,性能优于stringBuffer,适合单线程环境;
22、arrayList和linkedList的区别?
解答:arrayList是基于动态数组,支持随机访问(通过索引,查询效率高),插入和删除效率不高,因为涉及到元素移动,复制元素、扩容等;
linkedList是基于双向链表,不支持高效的随机访问,但是插入和删除效率高,只需要改变节点引用;
23、写一个方法判断一个整数是不是回文数,例如121是回文数,123不是
解答:思路是:分别取原数倒序的不同位的数据,重新组装成一个新数,再比较原数与新数是否相等
public boolean checkNum(int x){
if(x < 0){ return false};
int newNum = 0,original = x;
while(x != 0){
newNum = newNum * 10 + x%10;
x /= 10;
}
return newNum == original;
}
24、spring中Bean的作用域有哪些?
解答:singleton:默认,每个spring容器中一个bean只有一个实例;
prototype:每次请求都会创建一个新的实例;
request:每个http请求创建一个实例(仅web应用);
session:每个http会话创建一个实例(仅web应用);
global-session:全局HTTP会话(仅portlet应用);
25、jvm相关:什么情况下会触发Full GC?
解答:老年代空间不足;
方法区元空间不足;
调用system.gc();
CMS GC出现concurrent mode failure;
26、找出一组数字中唯一重复的数字(假设只有一个数字重复)
set<Integer> seen = new HashSet<>();
for(int num : nums){
if(seen.contains(num)){ return num}
seen.add(num);
}
return -1;
27、找出一组数字中第一个重复的数字
Map<Integer,Integer> occur = new HashMap<>();
for(int i=0;i< nums.length;i++){
int num = nums[i];
if(occur.containsKey(num)){ return num;}
occur.put(num,i);
}
return -1;
28、上传数据的安全性怎么控制?
解析:主要考察数据从前端通过网络传输到后端的安全性
解决方案就是对数据加密
(1)对称加密:发送方与接收方都是用一样的密钥对数据加密。
AES、DES、3DES- 传输的数据很大建议使用对称加密,不过不能保存敏感信息
- 传输的数据较小,要求安全性高,建议采用非对称加密
29、开发项目过程中遇到过哪些棘手的问题?
(1)设计模式:工厂模式加策略模式-->业务经常变更,导致频繁修改业务类,例如登录方式的变更、支付方式的变更,例如海关项目中不同国家的港口对应不同的海关政策,一开始都写在service业务类里面做
判断,一旦要加入新的就要修改代码,倾入性比较高;使用策略模式,将新的方式做成配置的形式,添加策略类
(2)慢查询:数据库优化(索引优化、sql优化、表结构优化)
浙公网安备 33010602011771号