反射
定义:运行时获取类信息+操作成员(Class/Method/Field)核心用途:框架(Spring IOC/MyBatis)、动态加载、操作私有成员关键类:
Class.forName()、
getDeclaredMethod()、
setAccessible(true)
泛型
核心:参数化类型(
List<T>)好处:类型安全(编译期校验)、代码复用、避免强转注意:类型擦除(编译后泛型消失,转为Object)
接口 vs 抽象类
接口:多实现、默认抽象方法、仅常量抽象类:单继承、可含非抽象方法、有构造器场景:接口定规范、抽象类做复用String不可继承
原因:被
final修饰,保证不可变性、安全性、缓存优化
char类型
大小:2字节(UTF-16编码),存单个字符(含中文)内存结构
线程共享:堆(年轻代Eden+Survivor/老年代)、方法区(元空间)线程私有:虚拟机栈、本地方法栈、程序计数器直接内存:非JVM内存,NIO使用GC机制
核心算法:标记-清除(碎片)、标记-复制(年轻代)、标记-整理(老年代)分代回收:年轻代(Minor GC)、老年代(Full GC)存活判断:可达性分析(GC Roots:局部变量/静态变量)类加载
流程:加载→验证→准备→解析→初始化双亲委派:启动类→扩展类→应用类加载器(避免类重复加载)对象创建:内存泄漏避免
关键点:关闭资源(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→TERMINATEDArrayList 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框架(#{})、输入校验核心数据结构
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.lang.reflect包下的Class、Method、Field、Constructor等类。
// 获取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, "张三");
为什么会用反射?什么时候会用到反射
答:使用反射的核心原因是动态性需求,编译期无法确定具体类或成员,需运行时灵活处理。
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)。
Java创建线程有几种方式?
答:核心有4种,推荐使用线程池(第4种):
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对象模型是JVM中对象的存储结构,核心分为三部分(以HotSpot虚拟机为例):
接口与抽象类的区别
答:核心区别在于“设计目的+使用场景”,具体对比如下:
| 特性 | 抽象类(abstract class) | 接口(interface) |
|---|---|---|
| 关键字 | abstract | interface |
| 继承/实现方式 | 子类用extends继承(单继承) | 类用implements实现(多实现) |
| 方法类型 | 可包含抽象方法、非抽象方法、静态方法 | 默认为抽象方法,Java8+支持default方法、静态方法 |
| 成员变量 | 可自定义访问控制(private/protected等) | 仅public static final常量(必须初始化) |
| 构造方法 | 有(供子类调用) | 无 |
| 设计目的 | 代码复用(子类共享父类实现) | 行为约束(类需实现指定方法) |
| 关系表达 | is-a(如Dog is a Animal) | has-a(如Dog has a Runnable行为) |
讲一讲垃圾回收机制?
答:JVM的垃圾回收(GC)是自动回收堆和方法区中“无用对象”内存的机制,核心目标是避免内存泄漏、释放空闲内存。
垃圾回收算法有哪些?
答:核心有4种基础算法,各有优劣:
分代回收算垃圾回收算法吗?
答:算,但它是“复合算法”而非基础算法。
怎么避免内存泄漏?
答:内存泄漏是“无用对象无法被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)采用“可达性分析”而非“引用计数法”。
JVM内存结构(内存区域划分)
答:JVM运行时数据区分为6大区域,其中堆和方法区为线程共享,其余为线程私有:
直接内存是什么?方法区在哪?
答:
Unsafe类或
NIO的
DirectByteBuffer分配;
特点:读写速度比堆内存快(避免JVM与操作系统之间的内存拷贝),但不受JVM堆大小限制,需手动管理(否则可能内存泄漏);用途:NIO、Netty等框架用于提升IO性能。
方法区的位置:
JDK7及之前:在JVM堆中,称为“永久代”;JDK8及之后:移至本地内存,称为“元空间”(MetaSpace);核心变化:元空间大小默认无上限(依赖系统内存),避免了永久代的OutOfMemoryError(如常量池过大导致)。
类加载器及双亲委派模型
答:
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个步骤,顺序固定:
static int a = 1,准备阶段设为0,初始化阶段设为1);解析:将类中的符号引用(如类名、方法名)转为直接引用(内存地址);初始化:执行类的静态代码块(static{})和静态变量赋值语句,是类加载的最后一步,只有在类被首次使用时触发(如创建对象、调用静态方法)。
JMM(Java内存模型)是什么?
答:JMM是定义线程与内存之间交互规则的规范,核心解决多线程并发时的可见性、原子性、有序性问题。
JVM堆的分代模型
答:JVM堆按“对象生命周期”分为年轻代和老年代,目的是优化GC效率:
Java中线程安全的方式有哪些?
答:线程安全的核心是保证“原子性、可见性、有序性”,常用方案:
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 int a = 0; a++仍存在线程安全问题,需用原子类或锁);适用场景:状态标记(如
volatile boolean flag = false)、双重检查锁定(DCL)。
Java的偏向锁介绍,以及描述锁升级过程
答:偏向锁是JVM对synchronized的优化,目的是“减少无竞争场景下的锁开销”。
锁可以降级吗?
答:不可以。Java中synchronized的锁升级是“不可逆”的,只能从偏向锁→轻量级锁→重量级锁,无法反向降级。
线程的状态有哪些?
答:Java线程的状态定义在
Thread.State枚举中,共6种(官方定义,无“阻塞态”单独分类):
死锁的四个必要条件
答:死锁是“两个或多个线程互相持有对方需要的锁,无限等待”,必须同时满足以下4个条件:
电脑单核要不要弄多线程?
答:需要,核心原因是“利用IO等待时间提升效率”。
线程和进程的区别?线程共享哪些数据?
答:
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 独立资源(内存空间、PID、文件描述符) | 共享进程资源 |
| 调度单位 | 操作系统调度,切换开销大 | 操作系统调度,切换开销小 |
| 独立性 | 高(进程间地址空间独立) | 低(一个线程崩溃可能导致进程崩溃) |
| 通信方式 | 复杂(管道、消息队列、Socket) | 简单(共享内存、线程变量) |
ArrayList和LinkedList的区别
答:核心区别在于底层数据结构,进而影响操作性能:
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组(基于数组) | 双向链表(基于节点引用) |
| 访问元素(get/set) | O(1)(直接通过索引) | O(n)(需遍历链表) |
| 增删元素(中间) | O(n)(需移动元素) | O(1)(仅修改节点引用) |
| 内存占用 | 连续内存,可能有扩容冗余 | 非连续内存,每个节点含前后引用 |
| 线程安全 | 非线程安全 | 非线程安全 |
哈希表是什么?有什么解决哈希冲突的方法?(拉链法、线性探测法)
答:哈希表是“基于哈希函数实现的键值对存储结构”,核心是“通过键快速定位值”(平均时间复杂度O(1))。
mysql索引简单介绍一下,实现原理是什么?为什么这样实现?
答:MySQL索引是“提升查询效率的数据结构”,核心作用是“减少数据扫描范围”。
事务是什么?ACID特性分别是什么?
答:事务是数据库中“一组不可分割的操作集合”,要么全部执行成功,要么全部执行失败。
数据库的隔离级别有哪些?默认级别是什么?
答:MySQL支持4种隔离级别(从低到高),用于解决并发事务的三大问题:
| 隔离级别 | 脏读(读未提交数据) | 不可重复读(同一事务多次读结果不同) | 幻读(同一事务多次查行数不同) |
|---|---|---|---|
| 读未提交(Read Uncommitted) | 允许 | 允许 | 允许 |
| 读已提交(Read Committed) | 禁止 | 允许 | 允许 |
| 可重复读(Repeatable Read) | 禁止 | 禁止 | 禁止(InnoDB优化) |
| 串行化(Serializable) | 禁止 | 禁止 | 禁止 |
什么是MVCC?
答:MVCC(多版本并发控制)是InnoDB实现隔离级别的核心技术,通过“保存数据的多个版本”实现并发控制,避免锁竞争。
怎样防止SQL注入?
答:SQL注入是“攻击者通过拼接恶意SQL语句注入到查询中,篡改查询逻辑”,防止思路是“避免直接拼接用户输入”:
// 正确示例(参数化)
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:如必须使用,需严格校验动态参数。
索引的功能有哪些?有哪些类型的索引?
答:
Redis缓存淘汰策略有哪些?
答:Redis缓存淘汰策略是“缓存满时,删除旧数据的规则”,核心分为两类:
介绍一下缓存击穿、缓存穿透的系统原因及解决方式
答:
缓存雪崩的原因及解决方式
答:
Redis的持久化机制有哪些?区别是什么?
答:Redis持久化用于“将内存数据保存到磁盘,避免重启后数据丢失”,核心有两种方式:
Redis五种基本数据结构是什么?
答:Redis核心有5种基本数据结构,各有特定用途:
Redis为什么快?
答:Redis是高性能键值数据库,核心原因的是“纯内存操作+高效设计”:
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分布式锁的粒度是“锁的范围大小”,核心原则是“最小粒度锁”,避免锁竞争加剧。
lock:order:123),仅锁定特定资源;避免过度细化:锁粒度太细会导致锁数量过多,增加Redis负担,需平衡并发和复杂度;
示例:秒杀场景中,锁定“商品ID+用户ID”(
lock:seckill:goods100:user200),避免同一用户重复秒杀,同时不影响其他用户。
Redis的集群有了解吗?为什么要用集群?
答:Redis集群是“将数据分散到多个Redis节点的部署方式”,核心分为主从复制、哨兵模式、Redis Cluster。
数据库和Redis的一致性问题怎么解决?
答:核心原则是“保证缓存与数据库的数据同步”,常用方案:
Spring的AOP是怎么实现的?
答:AOP(面向切面编程)是“在不修改源码的情况下,给程序动态添加功能(如日志、事务)”,Spring AOP的核心实现是“动态代理”。
Proxy类和
InvocationHandler实现;CGLIB动态代理:基于继承,目标类无需实现接口,生成目标类的子类,通过拦截方法调用实现;
Spring策略:目标类有接口时用JDK动态代理,无接口时用CGLIB(Spring Boot 2.x后默认使用CGLIB)。
Spring的IOC有什么作用?什么是解耦?高耦合有什么不好?
答:
mybatis是做什么的?
答:MyBatis是“持久层框架”,用于简化Java程序操作数据库的代码(替代JDBC),核心是“将SQL语句与Java代码分离”。
@Select("SELECT * FROM user WHERE id = #{id}"));参数映射:自动将Java方法参数映射为SQL语句的参数(支持#{}占位符,防止SQL注入);结果映射:自动将SQL查询结果映射为Java对象(如User、List);事务管理:可集成Spring的事务管理,简化事务控制。
核心优势:比JDBC简洁(无需手动处理连接、Statement、结果集),比Hibernate灵活(可手动优化SQL),适合SQL优化需求高的场景。
SpringBoot与Tomcat的区别?
答:两者属于不同层面的技术,无直接对比性,核心区别:
| 特性 | SpringBoot | Tomcat |
|---|---|---|
| 定位 | Java后端开发框架(Spring的简化版) | Web容器(Servlet容器) |
| 核心功能 | 自动配置、 starters依赖、嵌入式容器支持 | 运行Servlet/JSP、处理HTTP请求 |
| 关系 | SpringBoot可内置Tomcat(默认),无需单独部署 | 可作为独立Web容器,部署SpringBoot应用(如打包为war包) |
| 使用场景 | 快速开发Java后端应用(微服务、单体应用) | 运行Java Web应用(支持SpringBoot、SSM等) |
java -jar xxx.jar),无需单独安装配置Tomcat。
项目中用到了RocketMQ,为什么用?
答:RocketMQ是分布式消息队列,核心用途是“解耦、削峰、异步通信”,选择原因:
项目中用到了Kafka,主要用来做什么?用Kafka有什么好处?
答:
RabbitMQ分发什么消息?在哪保证可靠性?
答:
publisher-confirm-type: correlated,RabbitMQ接收消息后向生产者返回确认,确保消息发送成功;消息持久化:队列和消息设置为持久化(durable=true),避免RabbitMQ宕机导致消息丢失;消费者确认:消费者开启手动确认(acknowledge-mode: manual),处理完消息后手动发送ack,确保消息被正确处理;死信队列:无法处理的消息(如重试多次失败)转入死信队列,避免阻塞正常队列;集群部署:主从复制,避免单点故障。
ES的查找流程是什么?为什么要用ES?
答:
OSI七层模型,分别描述它们的工作是什么
答:OSI七层模型从下到上依次为:
TCP三次握手的过程,双方的状态变化
答:三次握手用于建立TCP连接,过程及状态变化如下:
为什么TCP不是两次握手?
答:两次握手无法保证“连接建立的可靠性”,会导致以下问题:
TCP可靠传输的实现机制
答:TCP通过以下机制保证可靠传输:
TCP四次挥手的具体过程,TIME_WAIT状态有什么作用?
答:
TCP拥塞控制的原理
答:TCP拥塞控制是“避免发送方发送速率超过网络承载能力”的机制,核心通过“拥塞窗口(cwnd)”动态调整发送速率:
TCP拥塞控制的原理
答:TCP拥塞控制是“避免发送方发送速率超过网络承载能力”的机制,核心通过“拥塞窗口(cwnd)”动态调整发送速率,分为4个阶段:
HTTP长短连接如何实现?
答:HTTP长短连接的核心区别是“连接是否复用”,通过请求头的
Connection字段控制:
Connection: keep-alive,连接建立后不立即关闭,后续请求复用该TCP连接;流程:建立TCP连接→发送多个HTTP请求→接收多个响应→超时/主动关闭连接;超时控制:服务器端设置长连接超时时间(如60秒),超时无请求则关闭连接;优点:减少连接建立/关闭开销,提升高并发场景性能(如浏览器加载网页时复用连接请求多个资源)。
网络IO相关知识
答:网络IO是“进程与网络设备之间的数据传输”,核心分为“阻塞IO”和“非阻塞IO”,结合IO多路复用实现高效处理:
Socket/
ServerSocket)、NIO(
java.nio包,Selector+Channel+Buffer)、AIO(
java.nio.channels.AsynchronousSocketChannel)。
Socket相关知识
答:Socket是“网络通信的端点”,是TCP/UDP协议的编程接口,用于实现进程间的网络通信(跨主机/本地)。
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,互联网控制消息协议)是网络层协议,核心作用是“传递网络控制信息和错误报告”,不传输应用数据。
OSPF和RIP的区别?这两个协议的底层算法是什么?
答:OSPF和RIP都是“内部网关协议(IGP)”,用于自治系统(AS)内的路由选择,核心区别如下:
| 特性 | RIP(路由信息协议) | OSPF(开放式最短路径优先) |
|---|---|---|
| 底层算法 | 距离矢量算法(Distance Vector) | 链路状态算法(Link State) |
| 度量值(cost) | 跳数(最多15跳,16跳不可达) | 链路带宽、延迟等(加权求和) |
| 收敛速度 | 慢(路由更新周期30秒,易产生环路) | 快(触发更新+洪泛链路状态,无环路) |
| 适用网络规模 | 小型网络(如局域网) | 中大型网络(如企业网、互联网骨干网) |
| 路由更新方式 | 定期广播完整路由表 | 触发更新链路状态信息(LSA),仅传变化 |
算法稳定性的概念
答:算法稳定性是“排序后,相同值元素的相对顺序保持不变”,核心影响场景是“多字段排序”。
[2, 1, 2],排序后若稳定排序,结果为
[1, 2(原第一个2), 2(原第三个2)];不稳定排序可能为
[1, 2(原第三个2), 2(原第一个2)];应用场景:先按成绩排序,再按姓名排序,稳定排序可保证成绩相同的人姓名排序顺序不变。
原地排序的排序算法有哪些?
答:原地排序是“排序过程中仅需少量额外空间(O(1)),不依赖额外数组存储数据”的算法,常用的有:
二叉树的遍历方法
答:二叉树遍历是“按一定顺序访问所有节点”,核心分为4种,基于递归或迭代实现:
public void preOrder(TreeNode root) {
if (root == null) return;
System.out.println(root.val); // 访问根
preOrder(root.left); // 左子树
preOrder(root.right); // 右子树
}
哈夫曼树是什么?
答:哈夫曼树(最优二叉树)是“带权路径长度(WPL)最短的二叉树”,核心用于哈夫曼编码(数据压缩)。
红黑树是什么?有什么特点?
答:红黑树是“自平衡的二叉搜索树”,核心通过颜色规则保证树的高度平衡(左右子树高度差不超过2倍),避免二叉搜索树退化为链表。
算法题:一个正整数数组,找出最长的不重复的子数组
答:核心思路是“滑动窗口+哈希表”,时间复杂度O(n),空间复杂度O(n)。
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)。
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)空间)。
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)。
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个关键模块设计:
decr stock:goodsId,库存≤0则直接返回秒杀失败(防止超卖);数据库最终一致性:Redis预扣库存后,异步将订单信息写入数据库,定时任务同步库存;
异步处理:用消息队列(RabbitMQ/RocketMQ)接收秒杀请求,异步创建订单、扣减数据库库存,提升响应速度;
防超卖与防作弊:
分布式锁:Redis分布式锁防止并发扣减库存;唯一标识:用户+商品唯一标识(如
seckill:userId:goodsId),防止重复秒杀;
核心流程:用户请求→网关限流→Redis预扣库存→消息队列异步处理→数据库扣减库存→返回结果。
两个人在一堆硬币里拿硬币,谁后手拿到输,有没有必胜策略?
答:有必胜策略,核心取决于“硬币总数是否为2ⁿ - 1”(如1、3、7、15…)。
估算一下一个公交车里能放下多少个乒乓球?
答:核心是“估算公交车容积和乒乓球体积,忽略座椅、过道等空隙(或按比例扣除)”。
23枚硬币,10枚正面朝上,蒙眼状态下如何将硬币分为两堆,使每堆正面朝上的硬币数量一样?
答:核心是“利用硬币翻面的对称性”。
20个球,一个天平,有一个球比较轻,如何找出轻球?
答:核心是“二分法称重”,最少2次,最多3次。
25匹马赛跑,求出前三名?
答:核心是“分组赛跑+精英赛,避免冗余比赛”,最少7场。
1000瓶酒中有1瓶毒酒,用最少的测试次数找出毒酒?
答:核心是“二进制编码”,最少10次。
用rand5生成rand7(基于5随机数生成7随机数)
答:核心是“拒绝采样法”,利用rand5生成均匀分布的[1,21]区间,再取模得到rand7。
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;
}
业务中若下游请求多个接口,但都是同一个表的数据只是字段组合不同,怎么处理?
答:核心思路是“接口聚合+缓存优化”,减少数据库查询次数。
SELECT id, name, age FROM user WHERE id IN (1,2,3));
方案2:缓存公共数据;
将该表的高频查询数据缓存到Redis(如Hash结构存储全量字段),下游接口直接从Redis查询所需字段,无需访问数据库;优点:响应速度快,减轻数据库压力,适合字段组合不固定的场景;
方案3:视图/存储过程;
若字段组合固定,创建数据库视图(View)或存储过程,封装查询逻辑,下游接口调用视图/存储过程获取数据;优点:简化应用层代码,数据库层面优化查询;
注意:聚合接口需考虑参数校验、权限控制,缓存需保证数据一致性(如数据库更新时同步更新Redis)。
项目中是否基于开源项目开发?自己做了哪些优化工作?
答:(结合实际项目回答,示例如下)
五年职业规划是什么?
答:核心是“技术深耕+能力升级”,分阶段明确目标:
愿不愿意转嵌入式?
答:(结合个人意愿,示例如下)
技术栈打算深耕哪些?
答:核心是“聚焦后端核心技术+贴合公司业务”,示例如下:
Linux的中断处理
答:Linux中断是“硬件设备(如键盘、网卡)向CPU发送的信号,请求CPU处理紧急事件”,核心流程分为“上半部”和“下半部”。
池化思想是什么?为什么要用线程池?还有什么地方有池化思想?
答:
线程池的corePoolSize和maximumPoolSize的关系?
答:corePoolSize是“核心线程数”,maximumPoolSize是“最大线程数”,两者共同控制线程池的线程数量。