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,核心目标是让用户在多个关联系统间只需登录一次,即可访问所有授权资源。其实现依赖于集中式认证和信任关系的建立

单点登录的解决方案,以JWT为例
I. 用户访问其他系统,会在网关判断token是否有效
II. 如果token无效则会返回401(认证失败)前端跳转到登录页面
III. 用户发送登录请求,返回浏览器一个token,浏览器把token保存到cookie
IV. 再去访问其他服务的时候,都需要携带token,由网关统一验证后路由到目标服务

 

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 构造方法出现了循环依赖怎么解决?

由于构造函数是bean生命周期中最先执行的,Spring框架无法解决构造方法的循环依赖问题。可以使用@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
(2)非对称加密:两个密钥:公开密钥(publickey)和私有密钥,公有密钥加密,私有密钥解密。
同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端
优点: 非对称加密与对称加密相比,其安全性更好;
缺点: 非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。 用途: 一般用于签名和认证。私钥服务器保存, 用来加密, 公钥客户拿着用于对于令牌或者签名的解密或者校验使用.
常见的非对称加密算法有: RSA
  • 传输的数据很大建议使用对称加密,不过不能保存敏感信息
  • 传输的数据较小,要求安全性高,建议采用非对称加密

29、开发项目过程中遇到过哪些棘手的问题?

(1)设计模式:工厂模式加策略模式-->业务经常变更,导致频繁修改业务类,例如登录方式的变更、支付方式的变更,例如海关项目中不同国家的港口对应不同的海关政策,一开始都写在service业务类里面做

判断,一旦要加入新的就要修改代码,倾入性比较高;使用策略模式,将新的方式做成配置的形式,添加策略类

(2)慢查询:数据库优化(索引优化、sql优化、表结构优化)

 

 

 

posted on 2025-05-29 23:08  黑子菜园  阅读(202)  评论(0)    收藏  举报

导航