TPLINK 面试准备

  • 时间:2025-11-07 13:58 作者: 来源: 阅读:0
  • 扫一扫,手机访问
摘要:TPLINK 面试准备 Java String底层原理 底层数据结构:jdk8(包括8)之前是char[],jdk9开始改为byte[],编码分为latin-1(一字节,对应英文字符)和utf-16(两字节,用于中文字符,和char大小一样,寻址时inedx * 2)字符串常量池:早期在方法区,后面移到堆,由jvm管理,底层是HashMap,key是字符串,value是字符串引用,当出现 = "

TPLINK 面试准备


Java

String底层原理

底层数据结构:jdk8(包括8)之前是char[],jdk9开始改为byte[],编码分为latin-1(一字节,对应英文字符)和utf-16(两字节,用于中文字符,和char大小一样,寻址时inedx * 2)字符串常量池:早期在方法区,后面移到堆,由jvm管理,底层是HashMap,key是字符串,value是字符串引用,当出现 = "abc"这种字面量,直接复用常量池里的(new String()这种还是正常创建对象)

JVM

可达性分析(GC Roots:局部变量/静态变量)

局部变量:作为 GC Roots 时,依赖于线程当前执行的栈帧,生命周期短,与方法执行同步;静态变量:作为 GC Roots 时,依赖于类的加载状态,生命周期长(通常与程序运行周期一致);两者共同构成了可达性分析的核心起点,确保 JVM 能准确识别 “必须保留” 的对象,避免误回收。

原子性,可见性(volatile),有序性

JMM将内存抽象为:线程私有的本地内存(主内存的副本,cpu寄存器、cache)、线程共享的主内存(RAM);volatile的作用就是当共享数据更新时(副本被修改),强制刷新到主内存,其他线程也强制丢弃就副本,读取主内存中最新的值,确保了可见性

TPLINK Java后端面试高频题速记

一、Java基础(速记关键点)

反射

定义:运行时获取类信息+操作成员(Class/Method/Field)核心用途:框架(Spring IOC/MyBatis)、动态加载、操作私有成员关键类: Class.forName() getDeclaredMethod() setAccessible(true)

泛型

核心:参数化类型( List<T>)好处:类型安全(编译期校验)、代码复用、避免强转注意:类型擦除(编译后泛型消失,转为Object)

接口 vs 抽象类

接口:多实现、默认抽象方法、仅常量抽象类:单继承、可含非抽象方法、有构造器场景:接口定规范、抽象类做复用

String不可继承

原因:被 final修饰,保证不可变性、安全性、缓存优化

char类型

大小:2字节(UTF-16编码),存单个字符(含中文)

二、JVM(高频核心)

内存结构

线程共享:堆(年轻代Eden+Survivor/老年代)、方法区(元空间)线程私有:虚拟机栈、本地方法栈、程序计数器直接内存:非JVM内存,NIO使用

GC机制

核心算法:标记-清除(碎片)、标记-复制(年轻代)、标记-整理(老年代)分代回收:年轻代(Minor GC)、老年代(Full GC)存活判断:可达性分析(GC Roots:局部变量/静态变量)
ps:

类加载

流程:加载→验证→准备→解析→初始化双亲委派:启动类→扩展类→应用类加载器(避免类重复加载)对象创建:

内存泄漏避免

关键点:关闭资源(IO/Socket)、清空静态集合、释放监听器、ThreadLocal.remove()

三、多线程与并发

synchronized

用法:方法/代码块,锁对象/类对象原理:Monitor锁,锁升级(偏向→轻量→重量)特性:可重入、非公平、自动释放

volatile

作用:可见性(主内存同步)、有序性(禁止重排序)局限:不保证原子性(如 i++)场景:状态标记、DCL单例

线程池

核心参数:corePoolSize(核心线程)、maximumPoolSize(最大线程)、workQueue(队列)、拒绝策略拒绝策略:Abort(抛异常)、CallerRuns(调用方执行)常用池:FixedThreadPool(固定线程)、CachedThreadPool(缓存线程)

线程安全方案

锁:synchronized/Lock关键字:volatile原子类:AtomicInteger(CAS实现)工具:Semaphore(限流)、CountDownLatch(倒计时)

线程状态

6种:NEW→RUNNABLE→BLOCKED/WAITING/TIMED_WAITING→TERMINATED

四、集合

ArrayList vs LinkedList

ArrayList:动态数组,查快(O(1))、增删慢(O(n))LinkedList:双向链表,增删快(O(1))、查慢(O(n))场景:查多用ArrayList,增删多用LinkedList

哈希表

冲突解决:拉链法(HashMap)、线性探测法HashMap:JDK8前数组+链表,后链表>8转红黑树核心:哈希函数( hashCode()^>>>16)、扩容(负载因子0.75)

五、数据库

索引

结构:B+树(叶子节点有序相连,支持范围查询)类型:聚簇索引(主键,存数据)、二级索引(存主键)优化:最左前缀法则、避免函数操作索引字段

事务ACID

原子性(不可拆)、一致性(业务合规)、隔离性(防干扰)、持久性(不丢失)

隔离级别

默认:InnoDB可重复读级别:读未提交→读已提交→可重复读→串行化(解决脏读/不可重复读/幻读)

MVCC

原理:多版本数据+Read View(读视图),实现读写不冲突应用:支持可重复读/读已提交隔离级

SQL注入防护

核心:参数化查询(PreparedStatement)、ORM框架(#{})、输入校验

六、Redis

核心数据结构

String(缓存/计数器)、Hash(对象)、List(队列)、Set(去重)、ZSet(排行榜)

缓存问题

穿透:缓存空值、布隆过滤器击穿:热点数据永不过期、互斥锁雪崩:过期时间随机化、集群部署

持久化

RDB:快照,快、省空间,丢数据AOF:日志,安全,文件大混合持久化:RDB+AOF(Redis5.0+默认)

分布式锁

实现: SET lock_key val NX EX 10 + Lua脚本删锁优化:看门狗续命(防锁超时)

Redis快的原因

纯内存、单线程(核心)、高效数据结构、IO多路复用

七、框架

Spring IOC

核心:控制反转(容器创建对象+注入依赖)作用:解耦(减少new)、简化开发、便于测试

Spring AOP

原理:动态代理(JDK接口/CDLIB继承)核心:切面(Aspect)、切入点(Pointcut)、通知(Advice)场景:日志、事务、权限

MyBatis

作用:简化JDBC,SQL与Java分离核心:Mapper接口代理、#{}(参数化)、${}(字符串拼接,慎用)

八、网络

TCP三次握手

流程:SYN→SYN+ACK→ACK目的:确认收发能力,协商序列号

TCP四次挥手

流程:FIN→ACK→FIN→ACKTIME_WAIT:等待2MSL,确保报文失效

TCP可靠传输

机制:序列号+确认号、重传、流量控制(滑动窗口)、拥塞控制

OSI七层

从下到上:物理层→数据链路层→网络层→传输层→会话层→表示层→应用层

九、算法(核心思路)

最长无重复子数组

思路:滑动窗口+哈希表(记录元素索引)

环形打家劫舍

思路:拆分为两个线性问题(偷首不偷尾/偷尾不偷首)

回文链表

思路:快慢指针找中点+反转后半段+对比

最长递增子序列

思路:动态规划+二分查找(O(n log n))

秒杀系统设计

核心:限流(前端/网关)、缓存预热、Redis预扣库存、异步处理(消息队列)

十、智力题(速记策略)

23枚硬币分堆

策略:分10和13枚,反转10枚

25匹马找前三

策略:5组预赛+1组精英赛+1组决胜赛(共7场)

rand5生成rand7

策略:拒绝采样(生成1-21,取模7+1)

毒酒问题

策略:二进制编码(10只老鼠对应10位,覆盖1000瓶)

要不要我帮你把这份手册整理成可打印的PDF版,或补充每个高频题的1分钟答题模板,方便面试时快速应答?


汇总问题:

一、Java基础

说说对于Java反射的理解为什么会用反射?什么时候会用到反射说一下泛型,有什么好处?Java中char数据类型是几字节?Java创建线程有几种方式?Java是编译型语言还是解释型语言?Java对象模型是什么?接口与抽象类的区别

二、JVM

讲一讲垃圾回收机制?垃圾回收算法有哪些?分代回收算垃圾回收算法吗?怎么避免内存泄漏?Java中有没有用到引用计数法做垃圾回收?JVM内存结构(内存区域划分)直接内存是什么?方法区在哪?类加载器及双亲委派模型JVM中类加载过程是怎么样的?JMM(Java内存模型)是什么?JVM堆的分代模型

三、多线程与并发

Java中线程安全的方式有哪些?Synchronized的相关知识(原理、用法等)讲一讲volatile关键字Java的偏向锁介绍,以及描述锁升级过程锁可以降级吗?线程的状态有哪些?死锁的四个必要条件电脑单核要不要弄多线程?线程和进程的区别?线程共享哪些数据?

四、集合

ArrayList和LinkedList的区别哈希表是什么?有什么解决哈希冲突的方法?(拉链法、线性探测法(带来的问题,随机探测解决))

五、数据库

mysql索引简单介绍一下,实现原理是什么?为什么这样实现?事务是什么?ACID特性分别是什么?数据库的隔离级别有哪些?默认级别是什么?什么是MVCC?怎样防止SQL注入?索引的功能有哪些?有哪些类型的索引?

六、Redis

Redis缓存淘汰策略有哪些?介绍一下缓存击穿、缓存穿透的系统原因及解决方式缓存雪崩的原因及解决方式Redis的持久化机制有哪些?区别是什么?Redis五种基本数据结构是什么?Redis为什么快?Redis的分布式锁是怎么实现的?Redis分布式锁的粒度控制Redis的集群有了解吗?为什么要用集群?数据库和Redis的一致性问题怎么解决?

七、框架(Spring/MyBatis)

Spring的AOP是怎么实现的?Spring的IOC有什么作用?什么是解耦?高耦合有什么不好?mybatis是做什么的?SpringBoot与Tomcat的区别

八、中间件

项目中用到了RocketMQ,为什么用?项目中用到了Kafka,主要用来做什么?用Kafka有什么好处?RabbitMQ分发什么消息?在哪保证可靠性?ES的查找流程是什么?为什么要用ES?

九、网络

OSI七层模型,分别描述它们的工作是什么TCP三次握手的过程,双方的状态变化为什么TCP不是两次握手?TCP可靠传输的实现机制(三次握手、四次挥手、流量控制、拥塞控制)TCP四次挥手的具体过程,TIME_WAIT状态有什么作用?TCP拥塞控制的原理HTTP长短连接如何实现?网络IO相关知识Socket相关知识ICMP协议相关知识OSPF和RIP的区别?这两个协议的底层算法是什么?

十、数据结构与算法

快速排序的原理,平均/最坏时间复杂度,是否稳定?归并排序的原理,平均/最坏时间复杂度,与快排的区别?排序算法按时间复杂度排序算法稳定性的概念原地排序的排序算法有哪些?二叉树的遍历方法哈夫曼树是什么?红黑树是什么?有什么特点?如何判断一个链表是否有环?(之前已有,补充重复)算法题:一个正整数数组,找出最长的不重复的子数组算法题:环形打家劫舍算法题:最长回文字符子串算法题:最长递增子序列算法题:判断链表是否是回文的算法题:跑步打卡补签(N天内有M天缺勤,给K张补签卡,求最大连续跑步天数)算法题:设计一个秒杀系统

十一、智力题

两个人在一堆硬币里拿硬币,谁后手拿到输,有没有必胜策略?估算一下一个公交车里能放下多少个乒乓球?23枚硬币,10枚正面朝上,蒙眼状态下如何将硬币分为两堆,使每堆正面朝上的硬币数量一样?20个球,一个天平,有一个球比较轻,如何找出轻球?25匹马赛跑,如何求出前三名?1000瓶酒中有1瓶毒酒,用最少的测试次数找出毒酒?用rand5生成rand7(基于5随机数生成7随机数)

十二、项目与实习

介绍一下实习中有技术难度的工作,有什么难住你的地方?怎么解决的?实习的具体工作是什么?拿到模块时的状态、资源,需要在什么基础上开发?项目的设计文档有经过什么样的评审?开发流程是怎么样的?实习几个月的收获是什么?领导同事对你的评价是什么?为什么做这个项目?在原本的代码上有什么新的东西?项目遇到的困难和解决思路项目中是否基于开源项目开发?自己做了哪些优化工作?项目中的断点续传是怎么实现的?Redis缓存了什么?用了什么数据结构?后端哪里使用了多线程?高并发体现在哪?业务中若下游请求多个接口,但都是同一个表的数据只是字段组合不同,怎么处理?选一个项目详细介绍(负责部分、难点、成果)研究生课题有什么成果?有无论文专利产出?

十三、个人与HR相关

自我介绍学习成绩怎么样?绩点、排名、奖学金、竞赛情况你学过哪些课程?讲讲你是怎么学习的?(Java、Redis等技术的学习方法)近两年芯片材料很火,你为什么不选择芯片材料而选择后端?为什么选择来深圳?爱好是什么?父母职业、户籍、是否独生?有没有考研/保研计划?愿不愿意转嵌入式?五年职业规划是什么?技术栈打算深耕哪些?后端或设备代码可以写吗?有无其他offer?美赛M占比?是否有国奖?进过两个实验室,有什么产出?Python和Java哪个更熟悉?怎么学的?反问:部门职能、面试流程、工作内容、是否转岗、福利待遇等

十四、其他

Linux的中断处理池化思想是什么?为什么要用线程池?还有什么地方有池化思想?线程池的核心参数都有什么用?corePoolSize和maximumPoolSize的关系

答案:

一、Java基础

说说对于Java反射的理解
答:Java反射是在程序运行时获取类的信息(属性、方法、构造器等)并操作其成员的机制,核心是 java.lang.reflect包下的Class、Method、Field、Constructor等类。

核心原理:每个类加载后会生成一个唯一的Class对象,反射通过这个Class对象“反向”获取类的结构,突破了编译期的访问限制。核心特性:运行时动态性,无需在编译期知道类的具体信息,可动态加载、调用类的成员。简单示例:

// 获取Class对象的三种方式
Class<?> clazz1 = Class.forName("com.example.User"); // 全类名
Class<?> clazz2 = User.class; // 类名.class
Class<?> clazz3 = new User().getClass(); // 对象.getClass()

// 实例化对象(无参构造)
Object user = clazz1.newInstance();

// 获取并调用方法
Method setName = clazz1.getDeclaredMethod("setName", String.class);
setName.setAccessible(true); // 突破访问权限(如private方法)
setName.invoke(user, "张三");

为什么会用反射?什么时候会用到反射
答:使用反射的核心原因是动态性需求,编译期无法确定具体类或成员,需运行时灵活处理。

核心用途: 框架开发:Spring的IOC(通过配置文件动态创建Bean)、MyBatis的Mapper接口代理(动态生成实现类)、JUnit的@Test注解解析,均依赖反射;动态加载类:如插件化开发(按需加载插件类)、JDBC加载驱动( Class.forName("com.mysql.cj.jdbc.Driver"));操作私有成员:测试场景中调用类的私有方法/属性进行验证;通用工具开发:如Bean拷贝工具(Apache BeanUtils),通过反射遍历对象属性并赋值。 注意:反射会降低性能(跳过编译期检查)、破坏封装性,非必要不使用。

说一下泛型,有什么好处?
答:泛型是参数化类型,允许在定义类、接口、方法时指定“类型占位符”,使用时再传入具体类型(如 List<String> Map<Integer, User>)。

核心好处: 类型安全:编译期检查类型,避免类型转换错误(如 List中存入Integer却取出String);代码复用:定义通用类/方法,适配多种类型(如 ArrayList<T>可存储任意引用类型,无需重复编写 StringList UserList);简化代码:无需手动强制类型转换(非泛型 List取出元素需强转,泛型自动类型匹配)。 示例:

// 泛型类
class Box<T> {
    private T data;
    public T getData() { return data; }
    public void setData(T data) { this.data = data; }
}
Box<String> stringBox = new Box<>();
stringBox.setData("Hello");
String data = stringBox.getData(); // 无需强转

Java中char数据类型是几字节?
答:2字节(16位),基于Unicode编码(UTF-16)。

可存储单个字符(如’a’、‘中’),取值范围是0~65535(包含基本多文种平面的所有字符);注意:对于Unicode扩展平面的字符(如部分emoji),需用两个char组合表示( surrogate pair ),但日常开发中单个char足够覆盖绝大多数常用字符。

Java创建线程有几种方式?
答:核心有4种,推荐使用线程池(第4种):

继承Thread类:重写run()方法,缺点是无法继承其他类;

class MyThread extends Thread { @Override public void run() {} }
new MyThread().start();
实现Runnable接口:重写run()方法,可继承其他类,但无返回值;

class MyRunnable implements Runnable { @Override public void run() {} }
new Thread(new MyRunnable()).start();
实现Callable接口+Future:支持返回结果和异常捕获;

class MyCallable implements Callable<Integer> { @Override public Integer call() { return 1; } }
FutureTask<Integer> future = new FutureTask<>(new MyCallable());
new Thread(future).start();
Integer result = future.get(); // 获取返回值
使用线程池(ExecutorService):推荐,线程复用、控制并发数,如 Executors.newFixedThreadPool(5)

Java是编译型语言还是解释型语言?
答:混合型语言,结合了编译型和解释型的特点:

编译阶段:Java源码(.java)通过javac编译器编译为字节码(.class),不直接编译为机器指令(区别于C++的编译型);运行阶段:JVM中的解释器(如HotSpot的解释器)将字节码逐行解释为机器指令执行,同时JIT(即时编译器)会将高频执行的字节码编译为机器指令缓存,提升性能;总结:“编译为字节码+解释执行(热点编译优化)”,兼顾跨平台性和性能。

Java对象模型是什么?
答:Java对象模型是JVM中对象的存储结构,核心分为三部分(以HotSpot虚拟机为例):

对象头(Header):占8/16字节,存储对象元数据(如Class对象指针、哈希码、锁状态标志、GC分代年龄);实例数据(Instance Data):存储对象的成员变量(包括继承自父类的变量),按类型对齐存储(如int占4字节);对齐填充(Padding):JVM要求对象大小必须是8字节的整数倍,不足时用填充字节补齐(无实际意义)。 示例:一个空对象(无成员变量)的大小是16字节(64位JVM,开启指针压缩),其中对象头8字节+对齐填充8字节。

接口与抽象类的区别
答:核心区别在于“设计目的+使用场景”,具体对比如下:

特性抽象类(abstract class)接口(interface)
关键字abstractinterface
继承/实现方式子类用extends继承(单继承)类用implements实现(多实现)
方法类型可包含抽象方法、非抽象方法、静态方法默认为抽象方法,Java8+支持default方法、静态方法
成员变量可自定义访问控制(private/protected等)仅public static final常量(必须初始化)
构造方法有(供子类调用)
设计目的代码复用(子类共享父类实现)行为约束(类需实现指定方法)
关系表达is-a(如Dog is a Animal)has-a(如Dog has a Runnable行为)

二、JVM

讲一讲垃圾回收机制?
答:JVM的垃圾回收(GC)是自动回收堆和方法区中“无用对象”内存的机制,核心目标是避免内存泄漏、释放空闲内存。

核心流程: 标记:判断对象是否存活(常用“可达性分析”——以GC Roots为起点,遍历对象引用链,不可达对象标记为垃圾);清除:回收标记为垃圾的对象内存(常用“标记-清除”“标记-复制”“标记-整理”算法);内存整理:解决内存碎片问题(如标记-整理算法会移动存活对象,使内存连续); 核心概念: GC Roots:包括虚拟机栈局部变量、方法区静态变量、本地方法栈引用的对象等;回收区域:主要是堆(年轻代、老年代),方法区(元空间)也会回收(如无用类);垃圾收集器:如Serial(串行)、Parallel(并行)、G1(分区并行),负责执行GC操作。

垃圾回收算法有哪些?
答:核心有4种基础算法,各有优劣:

标记-清除算法: 流程:标记垃圾对象→直接清除;优点:简单高效;缺点:产生内存碎片,后续大对象可能无法分配内存。 标记-复制算法: 流程:将内存分为两块,标记存活对象→复制到另一块内存,清除原内存;优点:无内存碎片,实现简单;缺点:内存利用率低(仅用一半内存),适合对象存活率低的区域(如年轻代)。 标记-整理算法: 流程:标记存活对象→移动存活对象到内存一端,清除另一端垃圾;优点:无内存碎片,内存利用率高;缺点:移动对象开销大,适合对象存活率高的区域(如老年代)。 分代回收算法: 本质:结合以上算法的“复合算法”,基于“对象生命周期不同”将堆分为年轻代(Eden+Survivor)和老年代;年轻代:用标记-复制算法(对象存活短,复制开销小);老年代:用标记-清除或标记-整理算法(对象存活长,避免频繁复制)。

分代回收算垃圾回收算法吗?
答:,但它是“复合算法”而非基础算法。

核心逻辑:不直接定义“如何标记、如何清除”,而是根据对象生命周期的特点,将堆划分为不同代(年轻代、老年代),为每代选择最合适的基础算法(如年轻代用标记-复制,老年代用标记-整理);优势:充分利用不同代对象的特性,平衡回收效率和内存利用率,是目前所有商用JVM的默认回收策略。

怎么避免内存泄漏?
答:内存泄漏是“无用对象无法被GC回收,长期占用内存”,避免思路围绕“让无用对象失去可达性”:

避免长生命周期引用短生命周期对象:如静态集合( static List<User>)存储临时对象,使用后未清空;关闭资源:IO流、Socket、数据库连接等资源,使用后需调用close()关闭(推荐用try-with-resources);释放监听器/回调:如注册的事件监听器、ThreadLocal对象,使用后需移除或调用 remove();避免循环引用:如A引用B,B引用A,且均无外部引用(虽现代GC可处理,但仍需避免);合理使用缓存:缓存数据需设置过期时间(如Redis的EXPIRE),避免无限增长;排查工具:用VisualVM、MAT等工具分析堆 Dump,定位未被回收的对象。

Java中有没有用到引用计数法做垃圾回收?
答:没有,Java主流虚拟机(如HotSpot)采用“可达性分析”而非“引用计数法”。

引用计数法原理:为每个对象维护一个引用计数器,被引用时+1,引用失效时-1,计数器为0则标记为垃圾;放弃原因:无法解决“循环引用”问题(如A引用B,B引用A,计数器均为1,实际为垃圾却无法回收);补充:Python、Objective-C等语言曾用引用计数法,需通过额外机制解决循环引用,而Java直接采用更可靠的可达性分析。

JVM内存结构(内存区域划分)
答:JVM运行时数据区分为6大区域,其中堆和方法区为线程共享,其余为线程私有:

程序计数器:线程私有,存储当前线程执行的字节码行号,无内存溢出;虚拟机栈:线程私有,存储方法栈帧(局部变量表、操作数栈等),栈深度过大抛StackOverflowError,内存不足抛OutOfMemoryError;本地方法栈:线程私有,为Native方法提供内存,异常类型同虚拟机栈;堆:线程共享,存储对象实例和数组,是GC主要区域,分为年轻代(Eden+Survivor)和老年代;方法区:线程共享,存储类信息、常量、静态变量、即时编译后的代码,JDK8后用元空间(本地内存)实现,JDK7及之前用永久代;直接内存:非JVM运行时数据区,通过Unsafe类分配,不受堆大小限制,内存不足抛OutOfMemoryError。

直接内存是什么?方法区在哪?
答:

直接内存:是操作系统的本地内存,不属于JVM运行时数据区,通过Java的 Unsafe类或 NIO DirectByteBuffer分配; 特点:读写速度比堆内存快(避免JVM与操作系统之间的内存拷贝),但不受JVM堆大小限制,需手动管理(否则可能内存泄漏);用途:NIO、Netty等框架用于提升IO性能。 方法区的位置: JDK7及之前:在JVM堆中,称为“永久代”;JDK8及之后:移至本地内存,称为“元空间”(MetaSpace);核心变化:元空间大小默认无上限(依赖系统内存),避免了永久代的OutOfMemoryError(如常量池过大导致)。

类加载器及双亲委派模型
答:

类加载器:负责将.class字节码加载为JVM中的Class对象,核心分为3类: 启动类加载器(Bootstrap ClassLoader):C++实现,加载JDK核心类(如 java.lang.String),路径为 JAVA_HOME/lib;扩展类加载器(Extension ClassLoader):Java实现,加载JDK扩展类,路径为 JAVA_HOME/lib/ext;应用类加载器(Application ClassLoader):Java实现,加载应用程序类(classpath下的类)。 双亲委派模型:类加载时先委托父加载器加载,父加载器无法加载时才自己加载: 流程:应用类加载器→扩展类加载器→启动类加载器(顶层),若顶层无法加载,再反向由子加载器尝试;好处:避免类重复加载、保证核心类安全(如无法自定义 java.lang.String类,防止篡改)。

JVM中类加载过程是怎么样的?
答:类加载是“将.class字节码加载为Class对象并初始化”的过程,分为5个步骤,顺序固定:

加载:通过类加载器读取.class文件,生成Class对象(存储在方法区);验证:校验.class文件的合法性(如字节码格式、版本兼容),避免恶意字节码;准备:为类的静态变量分配内存并设置默认值(如 static int a = 1,准备阶段设为0,初始化阶段设为1);解析:将类中的符号引用(如类名、方法名)转为直接引用(内存地址);初始化:执行类的静态代码块(static{})和静态变量赋值语句,是类加载的最后一步,只有在类被首次使用时触发(如创建对象、调用静态方法)。

JMM(Java内存模型)是什么?
答:JMM是定义线程与内存之间交互规则的规范,核心解决多线程并发时的可见性、原子性、有序性问题。

核心抽象:将内存分为主内存(存储共享变量)和工作内存(线程私有,存储共享变量的副本);核心规则: 线程操作共享变量时,需先将主内存的变量加载到工作内存,修改后再写回主内存;禁止线程直接操作主内存的共享变量; 解决的问题: 可见性:一个线程修改共享变量后,其他线程能立即看到(通过volatile、synchronized实现);原子性:一个操作不可拆分(通过synchronized、Lock、原子类实现);有序性:禁止指令重排序(通过volatile、synchronized实现)。

JVM堆的分代模型
答:JVM堆按“对象生命周期”分为年轻代和老年代,目的是优化GC效率:

年轻代(Young Generation):占堆内存的1/3,存储新创建的对象,存活率低; 细分:Eden区(80%)+ 两个Survivor区(From、To,各10%);流程:对象先分配到Eden区→Eden满触发Minor GC,存活对象复制到From Survivor→From满触发Minor GC,存活对象复制到To Survivor(年龄+1)→年龄达到阈值(默认15)晋升到老年代。 老年代(Old Generation):占堆内存的2/3,存储存活时间长的对象(如缓存对象),存活率高; 触发GC:老年代满或年轻代晋升对象超过老年代剩余空间,触发Full GC(Major GC),回收效率远低于Minor GC。 永久代/元空间:不属于堆分代,存储类信息、常量等(JDK8后为元空间)。

三、多线程与并发

Java中线程安全的方式有哪些?
答:线程安全的核心是保证“原子性、可见性、有序性”,常用方案:

锁机制:synchronized(内置锁,自动释放)、Lock接口(ReentrantLock、读写锁,手动释放);关键字:volatile(保证可见性和有序性,不保证原子性);原子类:java.util.concurrent.atomic包下的类(如AtomicInteger、AtomicReference),基于CAS实现原子操作;并发工具:Semaphore(信号量,控制并发数)、CountDownLatch(倒计时锁)、CyclicBarrier(循环栅栏);避免共享资源:ThreadLocal(线程本地存储,每个线程有独立副本)、无状态设计(线程执行不依赖共享变量)。

Synchronized的相关知识(原理、用法等)
答:synchronized是Java内置锁,保证多线程并发安全,核心是“监视器锁(Monitor)”。

用法:修饰方法(实例方法/静态方法)、代码块;

// 修饰实例方法(锁当前对象)
public synchronized void method1() {}
// 修饰静态方法(锁类对象)
public static synchronized void method2() {}
// 修饰代码块(锁指定对象)
public void method3() { synchronized (this) {} }
原理: 同步代码块:通过字节码指令 monitorenter(获取锁)和 monitorexit(释放锁)实现;同步方法:通过方法的access_flags字段标记 ACC_SYNCHRONIZED,调用时自动获取锁;锁优化:JVM对synchronized进行了偏向锁、轻量级锁、重量级锁的升级优化(见问题5)。 特性:可重入(同一线程可多次获取同一锁)、非公平锁(默认)、自动释放锁(异常或方法结束时)。

讲一讲volatile关键字
答:volatile是Java的轻量级同步关键字,核心保证“可见性”和“有序性”,不保证“原子性”。

核心作用: 可见性:一个线程修改volatile变量后,会立即刷新到主内存,其他线程读取时直接从主内存获取,避免缓存不可见问题;有序性:禁止指令重排序(通过内存屏障实现),如单例模式DCL中修饰instance,避免对象未初始化就被引用; 局限性:不能保证原子操作(如 volatile int a = 0; a++仍存在线程安全问题,需用原子类或锁);适用场景:状态标记(如 volatile boolean flag = false)、双重检查锁定(DCL)。

Java的偏向锁介绍,以及描述锁升级过程
答:偏向锁是JVM对synchronized的优化,目的是“减少无竞争场景下的锁开销”。

偏向锁: 核心逻辑:单线程场景下,锁会“偏向”当前线程,首次获取锁时记录线程ID,后续该线程再次获取锁时无需CAS操作,直接进入临界区;优点:几乎无锁开销,适合单线程重复获取锁的场景;缺点:多线程竞争时需撤销偏向锁,产生额外开销。 锁升级过程(从低到高,不可逆): 无锁状态:对象未被任何线程锁定;偏向锁:单线程获取锁,记录线程ID;轻量级锁:多线程交替获取锁,通过CAS尝试获取锁,线程不阻塞;重量级锁:多线程激烈竞争,锁膨胀为操作系统级互斥锁,未获取锁的线程阻塞(通过操作系统内核实现)。

锁可以降级吗?
答:不可以。Java中synchronized的锁升级是“不可逆”的,只能从偏向锁→轻量级锁→重量级锁,无法反向降级。

原因:锁升级的触发条件是“竞争加剧”,一旦升级为重量级锁,说明线程竞争已很激烈,降级回轻量级锁或偏向锁无法解决竞争问题,反而会因频繁切换锁状态增加开销;补充:Lock接口的读写锁(ReentrantReadWriteLock)支持“写锁降级为读锁”(持有写锁时可获取读锁,再释放写锁),但这是“锁类型切换”,并非synchronized的锁等级降级。

线程的状态有哪些?
答:Java线程的状态定义在 Thread.State枚举中,共6种(官方定义,无“阻塞态”单独分类):

NEW(新建):线程已创建但未调用start()方法;RUNNABLE(可运行):线程调用start()后,正在运行或等待CPU时间片;BLOCKED(阻塞):线程等待synchronized锁(如进入synchronized代码块但未获取锁);WAITING(无限等待):线程调用wait()、join()、LockSupport.park(),无超时时间,需其他线程唤醒;TIMED_WAITING(计时等待):线程调用sleep(long)、wait(long)、join(long),有超时时间,超时后自动唤醒;TERMINATED(终止):线程执行完run()方法或异常终止。

死锁的四个必要条件
答:死锁是“两个或多个线程互相持有对方需要的锁,无限等待”,必须同时满足以下4个条件:

互斥条件:锁资源只能被一个线程持有(如synchronized锁);请求与保持条件:线程持有一个锁后,又请求其他线程持有的锁,且不释放已持有的锁;不可剥夺条件:线程持有的锁不能被其他线程强制剥夺,只能主动释放;循环等待条件:多个线程形成循环等待链(如A等B的锁,B等C的锁,C等A的锁)。 避免死锁:破坏任意一个条件即可,常用方式是“按固定顺序获取锁”(破坏循环等待条件)。

电脑单核要不要弄多线程?
答:需要,核心原因是“利用IO等待时间提升效率”。

单核CPU的多线程是“并发”(宏观并行,微观串行),通过时间片轮转实现;关键场景:线程执行过程中存在IO操作(如读取文件、网络请求),此时线程会阻塞,CPU可切换到其他线程执行,避免CPU空闲;示例:一个线程执行网络请求(阻塞1秒),另一个线程执行本地计算,单核多线程可在1秒内完成两个任务,而单线程需要1秒+计算时间。

线程和进程的区别?线程共享哪些数据?
答:

核心区别:
特性进程线程
资源分配独立资源(内存空间、PID、文件描述符)共享进程资源
调度单位操作系统调度,切换开销大操作系统调度,切换开销小
独立性高(进程间地址空间独立)低(一个线程崩溃可能导致进程崩溃)
通信方式复杂(管道、消息队列、Socket)简单(共享内存、线程变量)
线程共享的进程资源: 堆内存(对象实例、数组);方法区(类信息、常量、静态变量);文件描述符、网络连接;进程的环境变量、命令行参数; 线程私有资源:虚拟机栈、本地方法栈、程序计数器。

四、集合

ArrayList和LinkedList的区别
答:核心区别在于底层数据结构,进而影响操作性能:

特性ArrayListLinkedList
底层结构动态数组(基于数组)双向链表(基于节点引用)
访问元素(get/set)O(1)(直接通过索引)O(n)(需遍历链表)
增删元素(中间)O(n)(需移动元素)O(1)(仅修改节点引用)
内存占用连续内存,可能有扩容冗余非连续内存,每个节点含前后引用
线程安全非线程安全非线程安全
使用场景:ArrayList适合“查询频繁、增删少”(如商品列表);LinkedList适合“增删频繁、查询少”(如队列、栈)。

哈希表是什么?有什么解决哈希冲突的方法?(拉链法、线性探测法)
答:哈希表是“基于哈希函数实现的键值对存储结构”,核心是“通过键快速定位值”(平均时间复杂度O(1))。

核心原理:键通过哈希函数计算得到哈希值,哈希值映射为数组下标,值存储在对应下标位置;哈希冲突:不同键计算出相同哈希值,映射到同一数组下标;解决方法: 拉链法(链地址法):数组每个下标存储一个链表,冲突的键值对存入链表,Java的HashMap采用此方式(JDK8后链表长度>8转为红黑树);线性探测法(开放定址法):冲突后按顺序查找数组下一个空闲位置(如下标i冲突,找i+1、i+2…),适合数组空间充足的场景,缺点是易产生“聚集效应”;其他方法:二次探测法(冲突后查找i+1²、i-1²…)、再哈希法(使用多个哈希函数)。

五、数据库

mysql索引简单介绍一下,实现原理是什么?为什么这样实现?
答:MySQL索引是“提升查询效率的数据结构”,核心作用是“减少数据扫描范围”。

核心分类: 主键索引(PRIMARY KEY):唯一标识记录,默认聚簇索引;非主键索引(二级索引):如唯一索引(UNIQUE)、普通索引(INDEX); 实现原理(InnoDB引擎): 聚簇索引:索引与数据存储在一起,叶子节点存储完整数据行,按主键排序;二级索引:叶子节点存储主键值,查询时需先通过二级索引找到主键,再回聚簇索引查询完整数据(回表); 数据结构:B+树(多路平衡查找树); 为什么用B+树? 平衡树结构:查询时间复杂度稳定O(log n),避免二叉搜索树的最坏O(n);叶子节点有序且相连:支持范围查询(如WHERE id BETWEEN 100 AND 200);非叶子节点仅存索引键:一页内存可存储更多索引项,减少IO次数(数据库IO以页为单位)。

事务是什么?ACID特性分别是什么?
答:事务是数据库中“一组不可分割的操作集合”,要么全部执行成功,要么全部执行失败。

ACID特性: 原子性(Atomicity):操作不可拆分,要么全执行,要么全回滚(如转账时A扣款和B到账必须同时成功或失败);一致性(Consistency):事务执行前后数据符合业务规则(如转账前后A和B的总余额不变);隔离性(Isolation):多事务并发时,互不干扰(避免脏读、不可重复读、幻读);持久性(Durability):事务执行成功后,数据永久保存到数据库(即使数据库宕机也不丢失)。

数据库的隔离级别有哪些?默认级别是什么?
答:MySQL支持4种隔离级别(从低到高),用于解决并发事务的三大问题:

隔离级别脏读(读未提交数据)不可重复读(同一事务多次读结果不同)幻读(同一事务多次查行数不同)
读未提交(Read Uncommitted)允许允许允许
读已提交(Read Committed)禁止允许允许
可重复读(Repeatable Read)禁止禁止禁止(InnoDB优化)
串行化(Serializable)禁止禁止禁止
默认级别:InnoDB引擎默认“可重复读(Repeatable Read)”,MyISAM不支持事务。

什么是MVCC?
答:MVCC(多版本并发控制)是InnoDB实现隔离级别的核心技术,通过“保存数据的多个版本”实现并发控制,避免锁竞争。

核心原理: 每行数据包含隐藏列:DB_TRX_ID(事务ID)、DB_ROLL_PTR(回滚指针);事务开始时生成Read View(读视图),记录当前活跃的事务ID;查询时通过Read View判断数据版本是否可见:仅能看到事务ID小于当前Read View中最小活跃事务ID,且未被删除的数据版本; 优点:读不加锁,读写不冲突,提升并发性能;应用:实现“读已提交”和“可重复读”隔离级别(读已提交每次查询生成新Read View,可重复读仅生成一次)。

怎样防止SQL注入?
答:SQL注入是“攻击者通过拼接恶意SQL语句注入到查询中,篡改查询逻辑”,防止思路是“避免直接拼接用户输入”:

使用参数化查询(PreparedStatement):将用户输入作为参数传入,而非拼接SQL语句,MySQL会自动过滤恶意字符;

// 正确示例(参数化)
String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
输入验证:过滤用户输入中的特殊字符(如’、"、OR、AND);使用ORM框架:如MyBatis(#{}占位符)、Hibernate,自动实现参数化查询;最小权限原则:数据库用户仅授予必要权限(如查询权限,无删除、修改权限);避免使用动态SQL:如必须使用,需严格校验动态参数。

索引的功能有哪些?有哪些类型的索引?
答:

索引的核心功能: 加速查询:减少数据扫描范围(如WHERE条件、JOIN关联、ORDER BY排序);保证数据唯一性:如唯一索引(UNIQUE)、主键索引,避免重复数据。 索引的类型: 按功能分: 主键索引(PRIMARY KEY):唯一标识记录,非空;唯一索引(UNIQUE):列值唯一,允许空;普通索引(INDEX):无唯一性约束,加速查询;全文索引(FULLTEXT):用于文本内容检索(如文章标题、内容); 按结构分: 聚簇索引:索引与数据存储在一起(InnoDB主键索引);二级索引:索引与数据分离,叶子节点存主键(InnoDB非主键索引); 按列数分: 单列索引:基于单个列创建;联合索引(复合索引):基于多个列创建(如idx_name_age (name, age))。

六、Redis

Redis缓存淘汰策略有哪些?
答:Redis缓存淘汰策略是“缓存满时,删除旧数据的规则”,核心分为两类:

过期键淘汰策略(仅删除过期数据): volatile-lru:删除过期键中最近最少使用的数据;volatile-ttl:删除过期键中剩余过期时间最短的数据;volatile-random:随机删除过期键中的数据;volatile-lfu:删除过期键中最不经常使用的数据; 全键淘汰策略(删除所有数据,包括未过期): allkeys-lru:删除所有键中最近最少使用的数据;allkeys-random:随机删除所有键中的数据;allkeys-lfu:删除所有键中最不经常使用的数据;noeviction:默认策略,不删除数据,新增数据时返回错误; 常用策略:volatile-lru(优先删除过期且不常用的数据)、allkeys-lru(缓存无过期键时使用)。

介绍一下缓存击穿、缓存穿透的系统原因及解决方式
答:

缓存穿透: 原因:查询“不存在的数据”(如用户ID=-1),缓存和数据库都无数据,请求直接穿透到数据库,高并发下导致数据库压力过大;解决方式: 缓存空值:查询不存在的数据时,缓存空值(设置短期过期时间),避免重复查询数据库;输入校验:过滤非法参数(如用户ID<0、非数字);布隆过滤器:将所有存在的键存入布隆过滤器,查询前先校验,不存在则直接返回。 缓存击穿: 原因:“热点数据”的缓存过期,高并发请求同时访问,缓存未命中,全部穿透到数据库,导致数据库雪崩;解决方式: 热点数据永不过期:核心热点数据(如首页商品)不设置过期时间,通过后台异步更新;互斥锁:缓存过期时,仅允许一个线程查询数据库并更新缓存,其他线程等待;缓存预热:系统启动时,提前将热点数据加载到缓存。

缓存雪崩的原因及解决方式
答:

原因:大量缓存数据同时过期,或Redis服务宕机,导致高并发请求全部穿透到数据库,数据库不堪重负崩溃;解决方式: 过期时间随机化:给缓存数据设置过期时间时加随机值(如10±2分钟),避免同时过期;集群部署:Redis集群化(主从复制+哨兵模式、Redis Cluster),避免单点故障;限流降级:通过网关(如Nginx)限流,或服务降级(返回默认数据),减少数据库压力;缓存预热+熔断:提前加载缓存,数据库压力过大时触发熔断,暂停查询数据库。

Redis的持久化机制有哪些?区别是什么?
答:Redis持久化用于“将内存数据保存到磁盘,避免重启后数据丢失”,核心有两种方式:

RDB(快照持久化): 原理:指定时间间隔内,拍摄内存数据快照,保存为.rdb二进制文件;触发方式:手动(save同步、bgsave异步)、自动(配置文件设置如save 900 1);优点:文件小、恢复快;缺点:数据安全性低(可能丢失两次快照间数据)。 AOF(日志持久化): 原理:记录每一条写命令,追加到.aof文件,重启时重新执行命令恢复数据;同步策略:always(每条命令同步,最安全)、everysec(每秒同步,默认)、no(操作系统控制);优点:数据安全性高;缺点:文件大、恢复慢。 混合持久化(Redis5.0+):AOF文件中包含RDB快照+后续命令日志,兼顾恢复速度和数据安全性。

Redis五种基本数据结构是什么?
答:Redis核心有5种基本数据结构,各有特定用途:

String(字符串):二进制安全,存储字符串、数字、二进制数据,用途:缓存、计数器、分布式锁;Hash(哈希):键值对集合,适合存储对象(如用户信息),用途:缓存对象、购物车;List(列表):有序链表,支持两端插入/删除,用途:消息队列、最新消息列表;Set(集合):无序集合,支持交集、并集、差集,用途:好友关系、去重、抽奖;ZSet(有序集合):按score排序,支持范围查询,用途:排行榜、延迟队列。

Redis为什么快?
答:Redis是高性能键值数据库,核心原因的是“纯内存操作+高效设计”:

纯内存存储:数据全部存在内存中,避免磁盘IO(数据库性能瓶颈);高效数据结构:如String(动态字符串)、Hash(字典)、ZSet(跳表),查询/操作时间复杂度低;单线程模型:避免多线程上下文切换和锁竞争开销(Redis6.0+支持多线程IO,但核心操作仍单线程);非阻塞IO:采用IO多路复用(epoll),处理大量并发连接;简洁的网络模型:基于Reactor模式,事件驱动,减少不必要的开销。

Redis的分布式锁是怎么实现的?
答:Redis分布式锁是“基于Redis实现的跨进程、跨服务器的锁机制”,核心原理是“利用Redis的原子操作保证锁的唯一性”。

核心实现步骤: 获取锁: SET lock_key random_value NX EX 10(NX:仅当锁不存在时设置,EX:过期时间10秒,random_value:唯一值用于释放锁);执行业务:获取锁成功后执行核心业务逻辑;释放锁:通过Lua脚本删除锁(保证原子性),仅删除random_value匹配的锁(避免误删其他线程的锁);

if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end
优化点: 锁超时问题:业务执行时间超过锁过期时间,需通过“续命”(如Redisson的看门狗机制)延长过期时间;集群一致性:Redis Cluster场景下,需使用RedLock算法,在多个主节点获取锁,保证一致性。

Redis分布式锁的粒度控制
答:Redis分布式锁的粒度是“锁的范围大小”,核心原则是“最小粒度锁”,避免锁竞争加剧。

粒度分类: 粗粒度锁:锁定整个资源(如锁定整个订单表),优点是简单,缺点是并发低(所有操作排队);细粒度锁:锁定资源的具体子集(如锁定订单ID=123的订单),优点是并发高,缺点是实现复杂; 控制方式: 锁键精细化:将锁键设计为“资源类型+资源ID”(如 lock:order:123),仅锁定特定资源;避免过度细化:锁粒度太细会导致锁数量过多,增加Redis负担,需平衡并发和复杂度; 示例:秒杀场景中,锁定“商品ID+用户ID”( lock:seckill:goods100:user200),避免同一用户重复秒杀,同时不影响其他用户。

Redis的集群有了解吗?为什么要用集群?
答:Redis集群是“将数据分散到多个Redis节点的部署方式”,核心分为主从复制、哨兵模式、Redis Cluster。

核心集群模式: 主从复制:1主N从,主库写入,从库同步数据并提供读服务,提升读性能和容灾;哨兵模式:监控主从节点,主库故障时自动切换从库为新主库,实现高可用;Redis Cluster:分片集群,将数据分为16384个槽位,分散到多个主节点,支持水平扩展(增加节点提升容量)。 为什么用集群? 解决单点故障:避免Redis单点宕机导致服务不可用;提升性能:主从复制分担读压力,Redis Cluster分担读写压力;扩容存储:突破单节点内存限制(如单节点最大8G,集群可扩容到100G)。

数据库和Redis的一致性问题怎么解决?
答:核心原则是“保证缓存与数据库的数据同步”,常用方案:

Cache-Aside(旁路缓存):推荐,应用直接操作缓存和数据库; 读流程:先查缓存→缓存命中返回→缓存未命中查数据库→更新缓存后返回;写流程:先更数据库→再删缓存(避免缓存脏数据,不推荐更新缓存,因可能有并发问题); 延迟双删:解决写流程中“缓存未删除成功”的问题; 流程:先删缓存→更数据库→延迟1秒再删缓存(确保其他线程已读取到新数据); 最终一致性:高并发场景下,允许短暂不一致,通过定时任务(如每5分钟)同步缓存与数据库数据;分布式事务:强一致性需求(如金融场景),使用2PC/3PC或TCC,但性能较低,非必要不使用。

七、框架(Spring/MyBatis)

Spring的AOP是怎么实现的?
答:AOP(面向切面编程)是“在不修改源码的情况下,给程序动态添加功能(如日志、事务)”,Spring AOP的核心实现是“动态代理”。

核心概念: 切面(Aspect):封装横切逻辑(如日志切面);连接点(JoinPoint):程序执行的某个节点(如方法调用);切入点(Pointcut):指定哪些连接点需要被增强;通知(Advice):切面的具体执行逻辑(如前置通知、后置通知、环绕通知)。 实现方式: JDK动态代理:基于接口,目标类必须实现接口,生成接口的代理对象,通过 Proxy类和 InvocationHandler实现;CGLIB动态代理:基于继承,目标类无需实现接口,生成目标类的子类,通过拦截方法调用实现; Spring策略:目标类有接口时用JDK动态代理,无接口时用CGLIB(Spring Boot 2.x后默认使用CGLIB)。

Spring的IOC有什么作用?什么是解耦?高耦合有什么不好?
答:

IOC(控制反转):是Spring的核心,将对象的创建、依赖注入的控制权从应用程序转移到Spring容器,容器负责管理对象的生命周期。IOC的作用: 解耦:减少类之间的依赖关系(如A类无需手动创建B类对象,由容器注入);简化开发:无需关注对象创建和依赖管理,专注业务逻辑;便于测试:可通过容器注入模拟对象,降低测试难度。 解耦:是“解除类之间的紧密依赖”,如A类依赖B类时,不直接new B(),而是通过接口、容器等方式依赖,使类之间独立变化。高耦合的坏处: 代码复用性低:修改一个类可能导致多个依赖类需要修改;维护成本高:类之间关联紧密,排查问题困难;不便于测试:依赖类无法替换,难以进行单元测试。

mybatis是做什么的?
答:MyBatis是“持久层框架”,用于简化Java程序操作数据库的代码(替代JDBC),核心是“将SQL语句与Java代码分离”。

核心功能: SQL映射:通过XML文件或注解定义SQL语句,将Java方法与SQL关联(如 @Select("SELECT * FROM user WHERE id = #{id}"));参数映射:自动将Java方法参数映射为SQL语句的参数(支持#{}占位符,防止SQL注入);结果映射:自动将SQL查询结果映射为Java对象(如User、List);事务管理:可集成Spring的事务管理,简化事务控制。 核心优势:比JDBC简洁(无需手动处理连接、Statement、结果集),比Hibernate灵活(可手动优化SQL),适合SQL优化需求高的场景。

SpringBoot与Tomcat的区别?
答:两者属于不同层面的技术,无直接对比性,核心区别:

特性SpringBootTomcat
定位Java后端开发框架(Spring的简化版)Web容器(Servlet容器)
核心功能自动配置、 starters依赖、嵌入式容器支持运行Servlet/JSP、处理HTTP请求
关系SpringBoot可内置Tomcat(默认),无需单独部署可作为独立Web容器,部署SpringBoot应用(如打包为war包)
使用场景快速开发Java后端应用(微服务、单体应用)运行Java Web应用(支持SpringBoot、SSM等)
补充:SpringBoot的核心是“简化开发”,通过嵌入式Tomcat(或Jetty、Undertow),使应用可直接通过JAR包运行( java -jar xxx.jar),无需单独安装配置Tomcat。

八、中间件

项目中用到了RocketMQ,为什么用?
答:RocketMQ是分布式消息队列,核心用途是“解耦、削峰、异步通信”,选择原因:

解耦:系统间通过消息通信,无需直接调用(如订单系统下单后发送消息,库存系统消费消息扣减库存,订单系统无需依赖库存系统);削峰填谷:高并发场景下(如秒杀),消息队列缓冲请求,避免数据库直接承受峰值压力(如10万QPS请求通过队列缓冲为1万QPS处理);异步通信:非核心流程异步处理(如下单后发送短信、推送通知),提升主流程响应速度(下单接口从500ms优化至100ms);可靠性:支持消息持久化、重试、死信队列,避免消息丢失;高并发:支持高吞吐量(十万级QPS),适合大规模分布式系统。

项目中用到了Kafka,主要用来做什么?用Kafka有什么好处?
答:

主要用途: 日志收集:集中收集分布式系统的日志(如应用日志、访问日志),传输到ELK栈(Elasticsearch+Logstash+Kibana)分析;数据同步:系统间数据异步同步(如订单数据同步到数据仓库);高并发通信:高吞吐量场景下的消息传递(如电商订单、直播互动消息)。 核心好处: 高吞吐量:支持百万级QPS,远高于其他消息队列(如RabbitMQ);低延迟:消息传递延迟低(毫秒级);高可靠性:支持消息持久化、副本备份,避免消息丢失;可扩展性:集群部署,支持水平扩展(增加节点提升吞吐量);适合大数据场景:与Hadoop、Spark等大数据框架集成友好。

RabbitMQ分发什么消息?在哪保证可靠性?
答:

分发的消息类型:主要是“业务消息”(如订单创建消息、支付结果消息)、“通知消息”(如短信、邮件通知),适合中小吞吐量、可靠性要求高的场景。可靠性保证机制: 生产者确认:生产者开启 publisher-confirm-type: correlated,RabbitMQ接收消息后向生产者返回确认,确保消息发送成功;消息持久化:队列和消息设置为持久化(durable=true),避免RabbitMQ宕机导致消息丢失;消费者确认:消费者开启手动确认(acknowledge-mode: manual),处理完消息后手动发送ack,确保消息被正确处理;死信队列:无法处理的消息(如重试多次失败)转入死信队列,避免阻塞正常队列;集群部署:主从复制,避免单点故障。

ES的查找流程是什么?为什么要用ES?
答:

ES(Elasticsearch)查找流程: 客户端发送查询请求(如HTTP请求)到ES集群的协调节点;协调节点根据查询条件和分片路由规则,确定需要查询的主分片和副本分片;协调节点向目标分片发送查询请求,分片执行查询(基于倒排索引);分片返回查询结果给协调节点,协调节点聚合结果后返回给客户端。 为什么要用ES? 全文检索能力:支持模糊查询、关键词匹配、分词查询(如“Java后端开发”分词为“Java”“后端”“开发”),远超MySQL的LIKE查询;高并发查询:基于倒排索引,查询速度快,支持百万级数据秒级查询;分布式架构:支持集群部署,水平扩展,适合大数据量存储和查询;多维度查询:支持聚合分析(如统计不同分类的商品数量)、过滤查询,满足复杂业务需求;适用场景:日志检索、商品搜索、文档检索、数据分析。

九、网络

OSI七层模型,分别描述它们的工作是什么
答:OSI七层模型从下到上依次为:

物理层:传输比特流(0/1),负责硬件设备的物理连接(如网线、网卡),定义电压、接口标准;数据链路层:将比特流封装为帧,处理MAC地址(物理地址),实现差错检测(如CRC校验);网络层:实现跨网段路由转发,处理IP地址(逻辑地址),选择最佳路由路径;传输层:提供端到端通信,保障可靠性(TCP)或实时性(UDP),定义端口号;会话层:建立、管理、终止会话(如TCP连接),同步通信双方;表示层:数据格式转换(如加密、压缩、编码),确保通信双方能理解数据;应用层:提供应用程序接口,定义通信协议(如HTTP、FTP、DNS)。

TCP三次握手的过程,双方的状态变化
答:三次握手用于建立TCP连接,过程及状态变化如下:

过程: 客户端(CLOSED状态)→ 服务器(LISTEN状态):发送SYN报文(SYN=1,seq=x),客户端进入SYN_SENT状态;服务器→客户端:发送SYN+ACK报文(SYN=1,ACK=1,seq=y,ack=x+1),服务器进入SYN_RCVD状态;客户端→服务器:发送ACK报文(ACK=1,seq=x+1,ack=y+1),客户端进入ESTABLISHED状态,服务器接收后也进入ESTABLISHED状态,连接建立。 核心目的:确保双方收发能力正常,协商初始序列号(ISN),避免历史连接的脏数据。

为什么TCP不是两次握手?
答:两次握手无法保证“连接建立的可靠性”,会导致以下问题:

历史连接无法区分:客户端发送的SYN报文可能因网络延迟滞留,一段时间后到达服务器,服务器若两次握手就建立连接,会误以为是新连接,浪费服务器资源;服务器无法确认客户端已收到自己的响应:两次握手时,服务器发送SYN+ACK后就建立连接,但客户端可能未收到该报文,此时客户端未建立连接,服务器却已分配资源,导致资源浪费;三次握手的第三次报文是客户端对服务器响应的确认,确保双方都知道对方收发正常,避免上述问题。

TCP可靠传输的实现机制
答:TCP通过以下机制保证可靠传输:

三次握手+四次挥手:建立连接时确认双方能力,断开连接时确保数据传输完成;序列号与确认号:每个字节分配唯一序列号,接收方返回确认号(期望接收的下一字节序号),确保数据有序且不丢失;重传机制:超时重传(发送方未按时收到确认则重传)、快速重传(收到3次重复确认则重传);流量控制:通过滑动窗口协议,控制发送方的发送速率,避免接收方缓冲区溢出;拥塞控制:通过慢启动、拥塞避免、快速重传、快速恢复机制,避免网络拥塞(如发送方根据网络状况动态调整发送窗口);校验和:对TCP报文段进行校验,检测数据传输过程中的错误。

TCP四次挥手的具体过程,TIME_WAIT状态有什么作用?
答:

四次挥手过程(客户端先主动关闭): 客户端(ESTABLISHED)→ 服务器:发送FIN报文(FIN=1,seq=x),客户端进入FIN_WAIT_1状态;服务器→客户端:发送ACK报文(ACK=1,seq=y,ack=x+1),服务器进入CLOSE_WAIT状态,客户端进入FIN_WAIT_2状态;服务器→客户端:发送FIN报文(FIN=1,ACK=1,seq=z,ack=x+1),服务器进入LAST_ACK状态;客户端→服务器:发送ACK报文(ACK=1,seq=x+1,ack=z+1),客户端进入TIME_WAIT状态,服务器进入CLOSED状态;客户端TIME_WAIT状态等待2MSL后,进入CLOSED状态,连接完全关闭。 TIME_WAIT状态的作用: 确保服务器收到客户端的最后一个ACK报文:若服务器未收到,会重传FIN报文,客户端在TIME_WAIT期间可重新发送ACK;避免历史连接的脏数据:2MSL(最长报文段寿命)是报文在网络中可能存在的最长时间,等待2MSL可确保网络中所有该连接的报文都失效,避免新连接收到旧报文。

TCP拥塞控制的原理
答:TCP拥塞控制是“避免发送方发送速率超过网络承载能力”的机制,核心通过“拥塞窗口(cwnd)”动态调整发送速率:

慢启动:连接初始时,cwnd从1开始,每经过一个RTT(往返时间)翻倍,快速提升发送速率,直到cwnd达到慢启动阈值(ssthresh)

答案(续):

TCP拥塞控制的原理
答:TCP拥塞控制是“避免发送方发送速率超过网络承载能力”的机制,核心通过“拥塞窗口(cwnd)”动态调整发送速率,分为4个阶段:

慢启动:连接初始时,cwnd(拥塞窗口)从1开始,每经过一个RTT(往返时间)翻倍(如1→2→4→8),快速提升发送速率,直到cwnd达到“慢启动阈值(ssthresh)”(默认65535字节);拥塞避免:cwnd达到ssthresh后,进入拥塞避免阶段,cwnd每经过一个RTT只加1(线性增长),避免网络拥塞;快速重传:当接收方收到3个重复的确认报文(ACK),说明数据丢失,立即重传丢失的报文,无需等待超时,同时将ssthresh设为当前cwnd的一半,cwnd重置为ssthresh;快速恢复:重传后进入快速恢复阶段,cwnd从ssthresh开始线性增长,避免再次触发拥塞,直到恢复正常传输。 核心目标:在网络不拥塞时提升传输速率,网络拥塞时快速调整,平衡吞吐量和稳定性。

HTTP长短连接如何实现?
答:HTTP长短连接的核心区别是“连接是否复用”,通过请求头的 Connection字段控制:

短连接(默认,HTTP/1.0): 实现:每次请求建立TCP连接,请求响应完成后立即关闭连接;流程:建立TCP连接→发送HTTP请求→接收响应→关闭TCP连接;缺点:频繁建立/关闭连接,开销大,适合少量请求场景。 长连接(HTTP/1.1默认): 实现:请求头设置 Connection: keep-alive,连接建立后不立即关闭,后续请求复用该TCP连接;流程:建立TCP连接→发送多个HTTP请求→接收多个响应→超时/主动关闭连接;超时控制:服务器端设置长连接超时时间(如60秒),超时无请求则关闭连接;优点:减少连接建立/关闭开销,提升高并发场景性能(如浏览器加载网页时复用连接请求多个资源)。

网络IO相关知识
答:网络IO是“进程与网络设备之间的数据传输”,核心分为“阻塞IO”和“非阻塞IO”,结合IO多路复用实现高效处理:

核心IO模型(Unix/Linux): 阻塞IO(BIO):进程发起IO请求后,等待数据准备完成并传输,期间进程阻塞(无法做其他事),简单但效率低;非阻塞IO(NIO):进程发起IO请求后,立即返回,若数据未准备好,进程可做其他事,定期轮询数据状态,无阻塞但轮询开销大;IO多路复用(Selector/Poll/Epoll):进程通过一个“监听器”监控多个IO通道,当任一通道数据准备完成,监听器通知进程处理,高效支持大量并发连接(如Redis、Nginx使用Epoll);异步IO(AIO):进程发起IO请求后,无需等待,数据传输完成后操作系统通知进程,完全无阻塞,但实现复杂。 Java中的网络IO:BIO( Socket/ ServerSocket)、NIO( java.nio包,Selector+Channel+Buffer)、AIO( java.nio.channels.AsynchronousSocketChannel)。

Socket相关知识
答:Socket是“网络通信的端点”,是TCP/UDP协议的编程接口,用于实现进程间的网络通信(跨主机/本地)。

核心概念: 套接字地址:由IP地址+端口号组成(如 192.168.1.1:8080),唯一标识网络中的进程;协议类型:TCP Socket(面向连接,可靠)、UDP Socket(无连接,不可靠)。 TCP Socket通信流程(C/S模式): 服务器端:创建ServerSocket→绑定端口→监听连接( listen)→接受连接( accept)→与客户端通信→关闭连接;客户端:创建Socket→连接服务器( connect)→与服务器通信→关闭连接。 示例(Java TCP Socket服务器端):

ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept(); // 阻塞等待客户端连接
InputStream in = socket.getInputStream(); // 读取客户端数据
OutputStream out = socket.getOutputStream(); // 向客户端写数据

ICMP协议相关知识
答:ICMP(Internet Control Message Protocol,互联网控制消息协议)是网络层协议,核心作用是“传递网络控制信息和错误报告”,不传输应用数据。

核心功能: 错误报告:当网络通信出现问题时(如目标主机不可达、端口未开放、超时),路由器或主机发送ICMP错误报文通知源端;网络诊断:支持ping、traceroute等工具,用于测试网络连通性和定位故障; ping:基于ICMP请求/应答报文(Echo Request/Echo Reply),测试主机是否可达;traceroute:通过发送TTL递增的ICMP报文,记录经过的路由器,定位网络路径。 特点:依赖IP协议传输(ICMP报文封装在IP数据报中),无端口号,不保证可靠传输。

OSPF和RIP的区别?这两个协议的底层算法是什么?
答:OSPF和RIP都是“内部网关协议(IGP)”,用于自治系统(AS)内的路由选择,核心区别如下:

特性RIP(路由信息协议)OSPF(开放式最短路径优先)
底层算法距离矢量算法(Distance Vector)链路状态算法(Link State)
度量值(cost)跳数(最多15跳,16跳不可达)链路带宽、延迟等(加权求和)
收敛速度慢(路由更新周期30秒,易产生环路)快(触发更新+洪泛链路状态,无环路)
适用网络规模小型网络(如局域网)中大型网络(如企业网、互联网骨干网)
路由更新方式定期广播完整路由表触发更新链路状态信息(LSA),仅传变化
底层算法细节: RIP的距离矢量算法:路由器仅向邻居发送自己的完整路由表,通过“跳数”选择最短路径,存在“计数到无穷”问题(限制15跳);OSPF的链路状态算法:路由器向全网广播自己的链路状态(如接口带宽、邻居关系),每个路由器构建全网拓扑图,通过Dijkstra算法计算最短路径,无跳数限制,收敛速度快。

十、数据结构与算法

算法稳定性的概念
答:算法稳定性是“排序后,相同值元素的相对顺序保持不变”,核心影响场景是“多字段排序”。

稳定排序算法:冒泡排序、插入排序、归并排序、基数排序;不稳定排序算法:快速排序、选择排序、希尔排序、堆排序;示例:数组 [2, 1, 2],排序后若稳定排序,结果为 [1, 2(原第一个2), 2(原第三个2)];不稳定排序可能为 [1, 2(原第三个2), 2(原第一个2)];应用场景:先按成绩排序,再按姓名排序,稳定排序可保证成绩相同的人姓名排序顺序不变。

原地排序的排序算法有哪些?
答:原地排序是“排序过程中仅需少量额外空间(O(1)),不依赖额外数组存储数据”的算法,常用的有:

冒泡排序:通过相邻元素交换排序,仅需临时变量存储交换数据;插入排序:将元素插入已排序部分,仅需临时变量存储待插入元素;选择排序:选择最小元素交换到当前位置,仅需临时变量;快速排序:分治思想,通过基准元素分区,仅需递归栈空间(最坏O(n),平均O(log n),仍视为原地排序);希尔排序:插入排序的优化,原地排序; 非原地排序:归并排序(需额外数组存储合并结果)、基数排序(需额外桶空间)。

二叉树的遍历方法
答:二叉树遍历是“按一定顺序访问所有节点”,核心分为4种,基于递归或迭代实现:

前序遍历(根→左→右):先访问根节点,再递归遍历左子树,最后递归遍历右子树;中序遍历(左→根→右):先递归遍历左子树,再访问根节点,最后递归遍历右子树(二叉搜索树中序遍历为升序);后序遍历(左→右→根):先递归遍历左子树,再递归遍历右子树,最后访问根节点;层序遍历(按层访问):从上到下、从左到右依次访问每一层节点,需用队列实现; 示例(前序遍历递归实现):

public void preOrder(TreeNode root) {
    if (root == null) return;
    System.out.println(root.val); // 访问根
    preOrder(root.left); // 左子树
    preOrder(root.right); // 右子树
}

哈夫曼树是什么?
答:哈夫曼树(最优二叉树)是“带权路径长度(WPL)最短的二叉树”,核心用于哈夫曼编码(数据压缩)。

核心定义: 带权路径长度(WPL):所有叶子节点的“权重×路径长度”之和(路径长度是从根到叶子的边数);构建方法(哈夫曼算法): 将所有带权节点作为独立的二叉树(仅含根节点);每次选择两个权重最小的二叉树,合并为一个新二叉树(新根节点权重为两者之和);重复上一步,直到合并为一棵二叉树,即为哈夫曼树。 应用场景:哈夫曼编码(频率高的字符编码短,频率低的编码长,减少总数据量)、数据压缩、通信中的编码优化。

红黑树是什么?有什么特点?
答:红黑树是“自平衡的二叉搜索树”,核心通过颜色规则保证树的高度平衡(左右子树高度差不超过2倍),避免二叉搜索树退化为链表。

核心特点(颜色规则): 每个节点要么是红色,要么是黑色;根节点是黑色;所有叶子节点(NIL节点,空节点)是黑色;红色节点的父节点必须是黑色(无连续红色节点);从任意节点到其所有叶子节点的路径,包含的黑色节点数相同(黑高相同)。 优势:插入、删除、查询的时间复杂度均为O(log n),平衡性能优于普通二叉搜索树,且无需像AVL树那样频繁调整(AVL树要求左右子树高度差不超过1);应用场景:Java的TreeMap/TreeSet、Linux内核的进程调度、Redis的有序集合(ZSet)底层(跳表之前用红黑树)。

算法题:一个正整数数组,找出最长的不重复的子数组
答:核心思路是“滑动窗口+哈希表”,时间复杂度O(n),空间复杂度O(n)。

思路: 用哈希表存储元素的最新索引,记录当前窗口的左边界;遍历数组,若当前元素已在哈希表中且索引≥左边界,说明窗口内有重复元素,更新左边界为“该元素索引+1”;否则,更新哈希表中当前元素的索引,计算当前窗口长度(右边界-左边界+1),记录最大值。 代码实现:

public int lengthOfLongestSubstring(int[] nums) {
    Map<Integer, Integer> map = new HashMap<>();
    int maxLen = 0;
    int left = 0; // 滑动窗口左边界
    for (int right = 0; right < nums.length; right++) {
        int num = nums[right];
        // 若元素已存在且在当前窗口内,更新左边界
        if (map.containsKey(num) && map.get(num) >= left) {
            left = map.get(num) + 1;
        }
        map.put(num, right); // 更新元素最新索引
        maxLen = Math.max(maxLen, right - left + 1); // 更新最大长度
    }
    return maxLen;
}

算法题:环形打家劫舍
答:核心思路是“拆分环形为两个线性问题”,时间复杂度O(n),空间复杂度O(1)。

问题分析:环形数组意味着“第一个元素和最后一个元素不能同时偷”,因此拆分为两个线性数组: 偷第一个元素,不偷最后一个元素(数组[0, n-2]);不偷第一个元素,偷最后一个元素(数组[1, n-1]);最终结果为两个线性问题的最大值。 线性打家劫舍解法:用动态规划, dp[i] = max(dp[i-1], dp[i-2] + nums[i]),优化空间为O(1)(用两个变量存储dp[i-1]和dp[i-2])。代码实现:

public int rob(int[] nums) {
    int n = nums.length;
    if (n == 1) return nums[0];
    // 拆分为两个线性问题
    return Math.max(robLinear(nums, 0, n-2), robLinear(nums, 1, n-1));
}
// 线性打家劫舍:从start到end的数组
private int robLinear(int[] nums, int start, int end) {
    int prev = 0, curr = 0;
    for (int i = start; i <= end; i++) {
        int temp = curr;
        curr = Math.max(curr, prev + nums[i]);
        prev = temp;
    }
    return curr;
}

算法题:最长回文字符子串
答:核心思路是“中心扩展法”,时间复杂度O(n²),空间复杂度O(1)(优于动态规划的O(n²)空间)。

思路: 回文子串分为“奇数长度”(中心为单个字符)和“偶数长度”(中心为两个字符);遍历每个字符,以该字符为中心扩展(奇数长度),以该字符和下一个字符为中心扩展(偶数长度);记录每次扩展的最大回文子串长度和起始索引。 代码实现:

public String longestPalindrome(String s) {
    int n = s.length();
    int start = 0, maxLen = 0;
    for (int i = 0; i < n; i++) {
        // 奇数长度回文
        int len1 = expandAroundCenter(s, i, i);
        // 偶数长度回文
        int len2 = expandAroundCenter(s, i, i+1);
        int len = Math.max(len1, len2);
        // 更新最大回文子串
        if (len > maxLen) {
            start = i - (len - 1) / 2;
            maxLen = len;
        }
    }
    return s.substring(start, start + maxLen);
}
// 中心扩展,返回回文子串长度
private int expandAroundCenter(String s, int left, int right) {
    while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
        left--;
        right++;
    }
    return right - left - 1; // 实际长度 = (right-1) - (left+1) + 1 = right - left -1
}

算法题:最长递增子序列
答:核心思路是“动态规划+二分查找”,时间复杂度O(n log n)(优于纯动态规划的O(n²))。

思路: 维护一个数组 tails tails[i]表示长度为 i+1的递增子序列的最小末尾元素;遍历每个元素,用二分查找找到 tails中第一个≥当前元素的位置,替换该位置的元素(保证 tails递增);若当前元素大于 tails所有元素,直接追加到末尾,子序列长度+1;最终 tails的长度即为最长递增子序列的长度( tails本身不是子序列,仅用于计算长度)。 代码实现(求长度):

public int lengthOfLIS(int[] nums) {
    int[] tails = new int[nums.length];
    int len = 0; // tails的有效长度
    for (int num : nums) {
        // 二分查找插入位置
        int left = 0, right = len;
        while (left < right) {
            int mid = (left + right) / 2;
            if (tails[mid] >= num) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        tails[left] = num;
        if (left == len) len++; // 追加到末尾,长度+1
    }
    return len;
}

算法题:判断链表是否是回文的
答:核心思路是“快慢指针找中点+反转后半段+对比”,时间复杂度O(n),空间复杂度O(1)(优于用栈的O(n)空间)。

思路: 快慢指针找中点:快指针每次走2步,慢指针每次走1步,快指针到尾时,慢指针在中点(奇数长度)或中点前一个(偶数长度);反转后半段链表:以慢指针的下一个节点为起点,反转链表;对比两段链表:从表头和反转后的后半段表头开始对比,全部相等则为回文。 代码实现:

public boolean isPalindrome(ListNode head) {
    if (head == null || head.next == null) return true;
    // 1. 快慢指针找中点
    ListNode slow = head, fast = head;
    while (fast.next != null && fast.next.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    // 2. 反转后半段(slow.next为起点)
    ListNode secondHalf = reverse(slow.next);
    // 3. 对比两段
    ListNode p1 = head, p2 = secondHalf;
    boolean res = true;
    while (res && p2 != null) {
        if (p1.val != p2.val) res = false;
        p1 = p1.next;
        p2 = p2.next;
    }
    // (可选)恢复链表
    slow.next = reverse(secondHalf);
    return res;
}
// 反转链表
private ListNode reverse(ListNode head) {
    ListNode prev = null, curr = head;
    while (curr != null) {
        ListNode next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}

算法题:跑步打卡补签(N天内有M天缺勤,给K张补签卡,求最大连续跑步天数)
答:核心思路是“滑动窗口”,时间复杂度O(n),空间复杂度O(1)。

思路: 窗口内允许的最大缺勤次数为K,寻找最长的窗口长度;用左右指针维护窗口,右指针遍历数组,统计窗口内的缺勤次数(0表示缺勤,1表示打卡);若缺勤次数> K,左指针右移,直到缺勤次数≤K;记录每次窗口的最大长度。 代码实现(数组nums中1=打卡,0=缺勤):

public int longestRun(int[] nums, int k) {
    int maxLen = 0;
    int left = 0;
    int absentCount = 0; // 窗口内缺勤次数
    for (int right = 0; right < nums.length; right++) {
        if (nums[right] == 0) {
            absentCount++;
        }
        // 缺勤次数超过k,左指针右移
        while (absentCount > k) {
            if (nums[left] == 0) {
                absentCount--;
            }
            left++;
        }
        // 更新最大长度
        maxLen = Math.max(maxLen, right - left + 1);
    }
    return maxLen;
}

算法题:设计一个秒杀系统
答:核心思路是“限流+缓存+异步+锁”,分6个关键模块设计:

前端限流:按钮置灰(防止重复点击)、验证码(防止恶意请求)、接口限流(如每分钟最多10次请求); 网关限流:Nginx配置限流规则(如按IP限流),过滤无效请求,避免请求直达后端; 缓存预热:秒杀开始前,将商品库存、价格等信息加载到Redis,避免数据库压力; 库存控制: Redis预扣库存: decr stock:goodsId,库存≤0则直接返回秒杀失败(防止超卖);数据库最终一致性:Redis预扣库存后,异步将订单信息写入数据库,定时任务同步库存; 异步处理:用消息队列(RabbitMQ/RocketMQ)接收秒杀请求,异步创建订单、扣减数据库库存,提升响应速度; 防超卖与防作弊: 分布式锁:Redis分布式锁防止并发扣减库存;唯一标识:用户+商品唯一标识(如 seckill:userId:goodsId),防止重复秒杀; 核心流程:用户请求→网关限流→Redis预扣库存→消息队列异步处理→数据库扣减库存→返回结果。

十一、智力题

两个人在一堆硬币里拿硬币,谁后手拿到输,有没有必胜策略?
答:有必胜策略,核心取决于“硬币总数是否为2ⁿ - 1”(如1、3、7、15…)。

前提:假设每次最多拿k枚硬币,最少拿1枚(默认k=总数-1,即每次可拿任意数量,仅留1枚给对方);必胜策略: 若硬币总数不是2ⁿ - 1:先手先拿硬币,使剩余硬币数为2ⁿ - 1(如总数5,先手拿2枚,剩3枚);后续无论后手拿m枚(1≤m≤k),先手都拿(k+1 - m)枚,始终保持剩余硬币数为2ⁿ - 1,最终后手被迫拿到最后1枚,先手胜; 示例:总数7(2³-1),先手必输;总数8,先手拿1枚剩7枚,后续按策略应对,必胜。

估算一下一个公交车里能放下多少个乒乓球?
答:核心是“估算公交车容积和乒乓球体积,忽略座椅、过道等空隙(或按比例扣除)”。

步骤: 公交车尺寸估算:长度8m × 宽度2.5m × 高度2m = 40m³;乒乓球尺寸估算:直径4cm,体积≈(4/2)³×π≈33.5cm³≈3.35×10⁻⁵m³;扣除空隙:公交车内座椅、过道等占30%容积,有效容积=40×(1-30%)=28m³;数量=有效容积÷乒乓球体积≈28÷3.35×10⁻⁵≈83.5万,约80万个; 结论:估算约80-100万个乒乓球(误差允许±20%)。

23枚硬币,10枚正面朝上,蒙眼状态下如何将硬币分为两堆,使每堆正面朝上的硬币数量一样?
答:核心是“利用硬币翻面的对称性”。

步骤: 将23枚硬币分为A堆10枚,B堆13枚;将A堆的10枚硬币全部翻面;此时A堆和B堆正面朝上的硬币数量相同。 原理:设A堆原有x枚正面朝上,则B堆有10-x枚正面朝上;A堆翻面后,正面朝上数量=10 - x,与B堆相等。

20个球,一个天平,有一个球比较轻,如何找出轻球?
答:核心是“二分法称重”,最少2次,最多3次。

步骤(3次称重,最坏情况): 第一次:20球分为7、7、6三堆,称7和7; 若平衡:轻球在6球堆;若不平衡:轻球在较轻的7球堆; 第二次: 6球堆分为2、2、2,称2和2,轻球在较轻的2球堆;7球堆分为2、2、3,称2和2,轻球在较轻的2球堆或3球堆; 第三次: 2球堆称1和1,较轻的是目标球;3球堆称1和1,平衡则剩余1球是目标球,不平衡则较轻的是目标球。

25匹马赛跑,求出前三名?
答:核心是“分组赛跑+精英赛,避免冗余比赛”,最少7场。

步骤: 分组赛跑(5场):25匹马分为5组(A-E),每组5匹,各赛1场,记录每组名次(如A1>A2>A3>A4>A5);精英赛(1场):每组第一名(A1、B1、C1、D1、E1)赛跑,假设名次A1>B1>C1>D1>E1(此时A1是总冠军);决胜赛(1场):争夺二、三名,候选马为A2、A3、B1、B2、C1(A1已确定第一,D1、E1及后续马不可能进前三),赛1场,前两名即为总亚军和季军; 结论:共5+1+1=7场比赛。

1000瓶酒中有1瓶毒酒,用最少的测试次数找出毒酒?
答:核心是“二进制编码”,最少10次。

原理:10位二进制可表示0-1023(覆盖1000瓶),每瓶酒对应一个唯一的10位二进制编码;步骤: 准备10只小白鼠,编号0-9,对应10位二进制的每一位;对每瓶酒,若其二进制编码的第i位为1,就给第i只小白鼠喂该酒;观察小白鼠存活状态:死亡的小白鼠编号对应二进制的1位,组合后的二进制数即为毒酒的编号; 示例:若小白鼠0、2、5死亡,二进制为0000100101,对应编号37,即第37瓶是毒酒。

用rand5生成rand7(基于5随机数生成7随机数)
答:核心是“拒绝采样法”,利用rand5生成均匀分布的[1,21]区间,再取模得到rand7。

原理:rand5生成1-5的随机数,构造rand5*5 + rand5 -5 生成1-21的均匀随机数(21是7的倍数);步骤: 生成随机数x = rand5() * 5 + rand5() - 5;若x > 21,重新生成x(拒绝超出范围的数,保证均匀);返回x % 7 + 1(将1-21映射为1-7,均匀分布); 代码实现:

public int rand7() {
    while (true) {
        int x = rand5() * 5 + rand5() - 5; // 1-21
        if (x <= 21) {
            return x % 7 + 1;
        }
    }
}
// 已知rand5()生成1-5的均匀随机数
public int rand5() {
    return new Random().nextInt(5) + 1;
}

十二、项目与实习

业务中若下游请求多个接口,但都是同一个表的数据只是字段组合不同,怎么处理?
答:核心思路是“接口聚合+缓存优化”,减少数据库查询次数。

方案1:接口聚合(推荐); 开发一个聚合接口,接收多个字段组合的查询需求,一次性从数据库查询所有需要的字段,按需求拆分返回;优点:减少接口调用次数(N次→1次),减少数据库连接开销,支持批量查询优化(如 SELECT id, name, age FROM user WHERE id IN (1,2,3)); 方案2:缓存公共数据; 将该表的高频查询数据缓存到Redis(如Hash结构存储全量字段),下游接口直接从Redis查询所需字段,无需访问数据库;优点:响应速度快,减轻数据库压力,适合字段组合不固定的场景; 方案3:视图/存储过程; 若字段组合固定,创建数据库视图(View)或存储过程,封装查询逻辑,下游接口调用视图/存储过程获取数据;优点:简化应用层代码,数据库层面优化查询; 注意:聚合接口需考虑参数校验、权限控制,缓存需保证数据一致性(如数据库更新时同步更新Redis)。

项目中是否基于开源项目开发?自己做了哪些优化工作?
答:(结合实际项目回答,示例如下)

项目基础:基于开源框架Spring Boot开发电商订单系统,核心依赖MyBatis、Redis、RocketMQ;优化工作: MyBatis优化:自定义分页插件,替换默认分页(减少count查询),添加SQL拦截器打印慢查询日志,优化3个慢查询(索引调整+SQL改写),接口响应时间从300ms降至50ms;Redis优化:引入Redis布隆过滤器解决缓存穿透,优化缓存淘汰策略(热点数据永不过期),添加缓存预热脚本,缓存命中率从85%提升至98%;并发优化:基于RocketMQ实现异步下单,将订单创建、库存扣减、消息推送解耦,支持每秒5000+订单创建,比同步处理提升3倍吞吐量; 优化成果:系统QPS从1000提升至5000,平均响应时间从200ms降至80ms,支持双11高并发场景稳定运行。

十三、个人与HR相关

五年职业规划是什么?
答:核心是“技术深耕+能力升级”,分阶段明确目标:

第1-2年:夯实基础,成为业务骨干,熟练掌握公司技术栈(如微服务、中间件),独立负责核心模块开发,解决常见技术问题;第3-4年:技术深耕,聚焦某一方向(如高并发架构、大数据处理),参与架构设计和技术选型,主导技术优化项目(如性能优化、系统重构);第5年:成为技术专家或技术管理者,具备跨团队协作能力,能带领小团队完成复杂项目,输出技术沉淀(如技术文档、内部培训),推动团队技术进步;补充:保持学习热情,关注行业新技术(如云原生、AI与后端结合),持续提升技术广度和深度。

愿不愿意转嵌入式?
答:(结合个人意愿,示例如下)

若愿意:“我对嵌入式开发有一定了解(如C语言、操作系统底层),也愿意尝试新的技术方向。后端开发培养的逻辑思维和问题解决能力,与嵌入式开发相通,我会快速学习嵌入式相关知识,尽快适应工作需求。”若不愿意:“我长期聚焦Java后端开发,在微服务、中间件、数据库等方面有较多积累,更希望在后端领域深耕。但我愿意了解嵌入式相关知识,若工作中需要跨领域协作,我会积极配合,贡献自己的技术能力。”

技术栈打算深耕哪些?
答:核心是“聚焦后端核心技术+贴合公司业务”,示例如下:

短期:深耕Java核心(JVM、多线程并发)、数据库(MySQL优化、分布式事务)、中间件(Redis、消息队列、Elasticsearch),提升技术落地能力;中期:聚焦高并发架构(分库分表、读写分离、限流熔断)、云原生技术(Docker、K8s、微服务治理),适应分布式系统需求;长期:关注AI与后端的结合(如LLMOps、AI工具集成),探索技术创新,提升系统智能化水平;补充:会结合公司业务需求调整学习重点,确保技术能为业务创造价值。

十四、其他

Linux的中断处理
答:Linux中断是“硬件设备(如键盘、网卡)向CPU发送的信号,请求CPU处理紧急事件”,核心流程分为“上半部”和“下半部”。

核心流程: 硬件触发中断,CPU暂停当前任务,保存上下文(寄存器、程序计数器);上半部(顶半部):执行中断服务程序(ISR),快速处理紧急任务(如读取硬件数据),禁用中断避免嵌套,执行时间短;下半部(底半部):处理非紧急任务(如数据解析、上报),在中断禁用解除后执行,不阻塞其他中断; 下半部实现方式:软中断、tasklet、工作队列; 作用:提高CPU利用率,使CPU在执行常规任务的同时,及时响应硬件设备的请求(如网络数据接收、键盘输入)。

池化思想是什么?为什么要用线程池?还有什么地方有池化思想?
答:

池化思想:是“提前创建资源池,复用资源,避免频繁创建和销毁资源的开销”,核心是“资源复用+控制资源数量”。为什么要用线程池? 减少开销:线程创建和销毁需要操作系统分配资源,复用线程避免频繁开销;控制并发:限制最大线程数,避免线程过多导致CPU切换频繁和内存溢出;便于管理:提供线程调度、任务队列、拒绝策略等功能,方便监控和管理; 其他池化思想应用: 连接池:数据库连接池(Druid、HikariCP)、Redis连接池,复用连接避免频繁建立TCP连接;对象池:线程本地对象池(如StringBuilder池),复用对象避免频繁GC;内存池:JVM的堆内存池(年轻代、老年代),复用内存空间;线程池:Java的ThreadPoolExecutor、Executors工具类。

线程池的corePoolSize和maximumPoolSize的关系?
答:corePoolSize是“核心线程数”,maximumPoolSize是“最大线程数”,两者共同控制线程池的线程数量。

核心关系:corePoolSize ≤ maximumPoolSize;线程池线程创建逻辑: 当任务数≤corePoolSize:创建核心线程,核心线程即使空闲也不会销毁(除非设置allowCoreThreadTimeOut=true);当任务数>corePoolSize:任务进入阻塞队列,队列未满则暂存任务;当队列满且任务数>maximumPoolSize:执行拒绝策略;当队列满且任务数≤maximumPoolSize:创建非核心线程,处理超额任务;非核心线程空闲时间超过keepAliveTime:被销毁,线程数恢复到corePoolSize; 示例:corePoolSize=5,maximumPoolSize=10,队列容量=20;任务数≤5时用核心线程,5<任务数≤25时用核心线程+队列,25<任务数≤35时创建非核心线程(最多5个),任务数>35时执行拒绝策略。
  • 全部评论(0)
最新发布的资讯信息
【系统环境|】AI 换脸冲上 GitHub TOP1,如何一键生成亲妈都不认识的自己?(2025-11-07 15:17)
【系统环境|】蓝牙钥匙 第31次 蓝牙钥匙在企业车队管理中的创新应用与深度实践(2025-11-07 15:17)
【系统环境|】Android高级工程师面试问题与参考答案(2025-11-07 15:16)
【系统环境|】车智赢登录页面算法还原(2025-11-07 15:16)
【系统环境|】进阶之路:设备副总谈从被动维护迈向预测性维护(2025-11-07 15:15)
【系统环境|】智能材料的发展与相关产业投资机会(2025-11-07 15:14)
【系统环境|】企业多品类产品数据分析实战:优化产品组合与增长策略(2025-11-07 15:14)
【系统环境|】自适应推理算法设计中神经编程的创新方法(2025-11-07 15:13)
【系统环境|】springboot图书个性化推荐系统的设计与实现(2025-11-07 15:12)
【系统环境|】特价股票与公司现金流质量的关系(2025-11-07 15:12)
手机二维码手机访问领取大礼包
返回顶部