java 面试八股集锦
MySQL篇
1.聚集索引的数据和索引放在一起,一个表只能有一个聚集索引,适合范围查询
非聚集索引的数据和索引分离,索引指向数据,一个表可以有多个非聚集索引,适合精确查询
2.B+ tree的优势(和哈希表,二叉树对比):高度低,磁盘io次数少; 查询高效,而且效率稳定,时间复杂度低
3.索引是一种数据结构,可以提高检索效率,降低io成本,减少cpu消耗
4.索引的缺点:执行增删改操作时效率变低,占用额外存储空间
5.索引失效的情况:模糊匹配的时候以%开头; 对列进行函数运算或表达式计算;字符串不加引号; or连接的条件,一边有索引一边无索引;
6.唯一索引:加速查询 + 列值唯一(可以有 NULL)。
联合索引:多列值组成一个索引,用于组合搜索,效率大于索引合并
索引类型(按照物理结构):聚簇索引、非聚簇索引。
索引类型(按照功能分类):主键索引、唯一索引、全局索引、复合索引、普通索引。
7.事务四大特性(acid): 原子性(回滚日志实现),一致性(通过其他三者实现),持久性(重做日志实现,隔离性(锁机制和mvcc实现)
8.事务隔离级别: 读未提交,读已提交,可重复读,串行化; mysql是默认可重复读
9.mysql默认存储引擎:innodb,综合处理能力和性能最好
innodb支持事务,myisam,memory不支持
innodb支持行级锁和表锁,其他两个只有表级锁
innodb支持外键,其他的不支持
innodb有崩溃恢复机制,其他的没有
innoDB适合高并发写操作;myisam适合读多写少的场景 memory用内存存储数据,访问速度快但数据容易丢失
10.EXPLAIN 命令可以分析 SQL 的 执行计划
11.mysql连接分为内连接和外连接,
内连接只返回两张表中都匹配的记录;
外连接分为左外连接和右外连接:
左外连接返回左表所有行,右表没匹配到的记录用null填充,
右外连接返回右表所有行,左表没匹配到的记录用null填充
12.count(1)、count() 与 count(列名) 的区别?:count(1)、count() 都是统计所有行数,COUNT(列名)会忽略值为null的行
13.覆盖索引是什么?: 查询的所有字段都能从索引本身获取,不需要回表。可以减少一次主键查询,性能更高
14.回表是什么: 通过二级索引(除了主键索引之外的索引)找到主键,再通过主键索引(聚簇索引)查找数据的过程。
15.最左前缀原则: 查询条件必须从索引最左边的列开始匹配,并且连续匹配,索引才会生效. 如果中间某个列不在查询条件中,后面的索引会失效
16.drop、delete 与 truncate 的区别?
DROP 用来删除整张表,包括表结构,不能回滚。
TRUNCATE 用于清空表中的所有数据,但会保留表结构,不能回滚。
DELETE 用来删除行,可以带 WHERE 条件,可以回滚。
17.sql查询的执行顺序: 先执行 FROM 确定主表,再执行 JOIN 连接,然后 WHERE 进行过滤,接着 GROUP BY 进行分组,HAVING 过滤聚合结果,SELECT 选择最终列,ORDER BY 排序,最后 LIMIT 限制返回的行数
-
InnoDB 是如何存储数据的?
InnoDB 的数据按行存储在页(16KB)中,页组成段,段存放在表空间中。
数据通过聚集索引存储,二级索引存储主键引用。
InnoDB 支持事务和 MVCC,使用 Redo Log 和 Undo Log 实现崩溃恢复和事务回滚,同时通过缓冲池加速磁盘读写。 -
Hash 索引与 BTree 索引有什么区别?
Hash 索引基于哈希表实现,支持等值查询,查询速度快,不支持范围查询和排序操作;
BTree 索引基于平衡树结构,支持等值查询、范围查询和排序操作,适合大多数查询场景 -
SQL 聚合函数有哪些?
COUNT、SUM、AVG、MIN、MAX等,用于对数据集进行汇总和统计分析。
Redis篇
1.缓存穿透: 请求的数据在缓存和数据库中都不存在,导致所有请求打到数据库; 解决:布隆过滤器,缓存空值,在接口层进行校验,过滤无效请求
缓存击穿: 热点key过期,大量请求直接打到数据库; 解决:互斥锁,逻辑过期
缓存雪崩:同一时间内大量key过期或者redis服务宕机导致所有请求访问数据库; 解决:随机过期时间,分散key过期; 使用多级缓存; 使用redis集群和主从哨兵机制
2.如何实现可重入锁?:可重入锁指的是在一个线程中可以多次获取同一把锁,实现思路是: 线程在获取锁的时候判断是否为自己的锁,如果是,不再重新获取锁, 而是计数加一,释放锁计数减一
3.分布式锁怎么实现?使用setnx命令(建议使用lua脚本,保证解锁操作的原子性)或者redission框架的分布式锁
4.先操作缓存还是先操作数据库?:先操作数据库, 再删除缓存
5.使用redis的哪种数据结构实现点赞/关注/签到功能?:Set, Set能快速添加和删除,并且可以去重
Set还可以用于获取交集,并集,差集,比如共同好友(交集), 好友推荐(差集)
6.排行榜怎么实现?sorted set有序集合,可以按分数进行排序
7.redis为什么快:单线程,没有线程切换和锁竞争; 基于内存,速度快,使用io多路复用模型
8.redis持久化机制: rdb和aof, rdb定期将内存中的数据生成快照并写入文件中,aof每执行一次命令就把命令追加到文件里
9.RDB 创建快照时会阻塞主线程吗?:不会,bgsave命令fork 出一个子进程,不会阻塞 Redis 主线程
10.如何选择 RDB 和 AOF?:对数据安全性要求不高选择rdb,否则选择aof; rdb恢复数据的速度更快,不用像aof一样依次执行命令
11.redis事务:用于保证一组命令的原子性,不支持回滚,使用 MULTI (事务的开始)和EXEC(事务的结束)命令
12.redis的数据过期策略:惰性删除(访问key,检查是否过期,过期就删除), 定期删除(每隔一段时间检查key并删除),内存淘汰(达到阈值删除)
13.Redis 哨兵机制: 是 Redis 实现高可用的方案,负责监控主从节点、自动故障转移和通知客户端。当主节点宕机时,哨兵会通过投票机制选出新的主节点,并让其他从节点跟随它,同时通知客户端更新连接。能够避免单点故障,保证 Redis 集群自动恢复
14.Redis主从复制(同步)是什么? 主从复制是将一台 Redis 服务器的数据实时复制到其他 Redis 服务器的机制。主节点负责写,从节点负责读,复制分为全量复制和增量复制, 主从复制能够避免单节点redis故障,即使有一台服务器宕机,其他服务器依然可以工作
全量复制: 将主节点当前所有的数据完整地复制到从节点,从节点会丢弃自己原有的数据,直接用主节点的数据覆盖
增量复制: 在全量复制完成后,主节点将新增的写命令实时发送给从节点(只请求主节点产生的新数据),从节点按顺序执行,实现持续同步
Java 集合
1.Hashmap和Hashtable的区别?
-
Hashmap线程不安全, Hashtable线程安全,因为Hashtable的方法被synchronized修饰
-
Hashmap的效率更高,因为没加锁.
-
Hashmap允许key和value为null, Hashtable不允许
-
初始容量和每次扩容的大小不同:① 创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充容量变为原来的 2 倍。② 创建时如果给定了容量初始值, Hashtable 会直接使用给定的大小,但 HashMap 会将其扩充为 2 的幂次方大小
-
HashMap 和 HashSet 区别
HashMap基于 哈希表实现,用键值对存储数据
HashSet基于 HashMap 实现,只存储键(key),值(value)用一个固定的常量对象占位
HashMap存储键值对, 键不可重复,值可以重复; HashSet 储存单一元素, 元素不能重复 -
HashMap 的底层实现
底层数据结构: JDK 1.8 之前:数组 + 链表, JDK 1.8 及以后:数组 + 链表 + 红黑树
HashMap通过 key 的 hash 值定位数组索引,将元素存放在对应的桶中。如果桶里没有元素,就直接插入;如果有元素且 key 相同,就覆盖 value;如果 key 不同,则挂到链表尾部或者红黑树中,来解决哈希冲突。当链表长度超过阈值且数组容量达到一定大小时,链表会转为红黑树,以提升查找效率. 当元素数量超过数组长度 × 负载因子(默认 0.75)时触发扩容,会重新计算所有元素索引。 -
HashMap 为什么线程不安全?
原因: 并发操作可能破坏内部数据结构,例如多线程同时执行 put 操作时,会造成数据覆盖问题, 多线程扩容可能导致死循环或环形链表。多线程并发修改时,size计数可能出现错误。解决方式是使用 synchronizedMap 或 ConcurrentHashMap。 -
ConcurrentHashMap是什么
ConcurrentHashMap 是 Java 提供的线程安全哈希表。JDK1.7 使用 分段锁 Segment实现,写操作锁定对应段,读操作无锁;JDK1.8采用 数组 + 链表/红黑树 + CAS + synchronized 机制实现,写操作通过 CAS 或同步锁保证线程安全,读操作无锁且通过 volatile 保证可见性,扩容也使用 CAS 保证多线程安全。它支持高并发读写,性能远高于 Hashtable 和 synchronizedMap,同时在链表长度过长时会转红黑树,以提升最坏情况下的查找效率。
Java并发
-
如何创建线程池?
方式一:通过 ThreadPoolExecutor 构造函数创建, 可以自定义核心线程数、最大线程数、阻塞队列和拒绝策略,更安全、更灵活。
方式二:通过 Executors 工具类创建,非常简便 -
什么是线程和进程?
进程是操作系统资源分配的基本单位,线程是 CPU 调度的基本单位。一个进程可以包含多个线程,线程之间共享进程的内存和资源,但有自己的栈和寄存器。进程切换开销大、通信成本高,线程切换开销小、通信方便,但线程之间需要注意同步问题。 -
如何创建线程?
继承Thread类、实现Runnable接口、实现Callable接口、使用线程池、使用CompletableFuture类 -
线程的生命周期和状态
Java 线程有 6 种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING 和 TERMINATED。
NEW 是新建未启动,RUNNABLE 表示等待 CPU 调度,BLOCKED 等待锁,WAITING 无限期等待唤醒,TIMED_WAITING 限时等待自动唤醒,TERMINATED 表示终止状态。 -
可以直接调用 Thread 类的 run 方法吗?
可以直接调用, 但这样不会以多线程的方式执行,而是在当前线程中执行 run 方法的代码。调用 start() 方法方可启动线程并使线程进入就绪状态 -
什么是线程死锁?
多个线程在执行过程中,由于竞争资源而造成的互相等待的现象,导致它们都无法继续执行下去。
产生的四个必要条件(缺一不可) -
互斥:资源一次只能被一个线程占用。
-
占有且等待:一个线程至少持有一个资源,同时等待其他线程持有的资源。
-
不可抢占:资源在未使用完之前,不能被其他线程强行抢占。
-
循环等待:多个线程形成首尾相接的等待资源关系。
-
怎样避免死锁?
• 1. 按固定顺序申请锁,避免循环等待;
• 2. 一次性申请所有资源;
• 3. 设置锁超时时间
• 4. 减少锁的持有时间
• 5. 使用更高级的并发工具,如 ConcurrentHashMap、BlockingQueue、Semaphore 等 -
volatile 关键字是什么
volatile 是 Java 中的一个关键字,用来保证变量的可见性和有序性,但不保证原子性。
可见性: 当一个线程修改了 volatile 变量的值,新的值会立即刷新到主内存。其他线程在读取该变量时,会直接从主内存读取最新值
有序性: Volatile能阻止指令重排, 确保变量的读写操作按代码顺序执行 -
synchronized 是什么?有什么用?
synchronized 是 Java 内置的锁,用于保证多线程对共享资源的互斥访问 (保证线程安全), 可以修饰方法或代码块, 确保在任意时刻只有一个线程执行
synchronized 的使用方式:
-
修饰实例方法
-
修饰静态方法
-
修饰代码块
-
synchronized 和 volatile 有什么区别?
synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!
• volatile 是轻量级同步,性能肯定比synchronized更好 。但是 volatile只能修饰变量, 而 synchronized 关键字可以修饰方法以及代码块 。
• volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
• volatile用于解决变量在多个线程之间的可见性,而 synchronized解决的是多个线程之间访问资源的同步性。 -
ThreadLocal 有什么用?
线程隔离: 为每个线程创建一个独立的变量副本,使每个线程可以独立地操作自己的副本,而不影响其他线程,也就是线程隔离
用于保存线程上下文信息: 如数据库连接、用户id -
什么是线程池?
一种线程复用机制,在程序启动时创建一定数量的线程,线程执行完任务后返回线程池等待下一个任务
作用:
-
减少线程创建和销毁的开销, 降低资源消耗
-
可以更好的管理线程, 统一管理线程的生命周期
-
提高响应速度: 线程池里会维护一定数量的核心线程,任务来了之后可以直接由这些线程执行,不需要新建线程
-
使用多线程会带来什么问题,怎么解决?:
问题:
线程安全:比如并发修改导致数据不一致。
死锁:多个线程互相持有对方需要的锁。
上下文切换:线程切换开销影响性能。
资源竞争:如锁竞争导致线程阻塞。
解决:
线程安全:用synchronized、Lock(如ReentrantLock)保证同步;使用原子类(AtomicInteger)或并发集合(ConcurrentHashMap)。
死锁:避免嵌套锁、按顺序加锁、使用tryLock设置超时。
减少上下文切换:使用无锁并发(CAS)、线程池复用线程。
- 线程池常见参数有哪些?
ThreadPoolExecutor 的常见参数包括:核心线程数、最大线程数、非核心线程空闲存活时间及单位、任务队列、线程工厂和拒绝策略
15.线程池处理任务的流程:
- 提交任务;
- 如果核心线程空闲,则直接执行;
- 核心线程满了则入队列等待;
- 队列满了且线程数未达最大值,则创建非核心线程执行;
- 队列满了且线程数到达最大值,则触发拒绝策略;
- 非核心线程空闲超过 keepAliveTime 会被回收
计算机网络
1.HTTP/1.0、HTTP/1.1、HTTP/2.0、HTTP/3.0 的区别?
HTTP/1.0 → 是短连接,每次请求都要建立 TCP,效率低下
HTTP/1.1 → 是长连接,一个TCP连接可以发送多次请求,但有队头阻塞问题
HTTP/2.0 → 实现了多路复用,一个TCP连接可以并发处理多个请求, 解决了1.1的请求队头阻塞,但底层仍存在TCP的队头阻塞,引入了二进制传输 + 头部压缩
HTTP/3.0 → 基于 QUIC,使用UDP代替TCP, 彻底解决 TCP 队头阻塞,支持 0-RTT,更快更安全。
2. HTTP 常见状态码有哪些?
• 200 OK:请求成功。
• 201 Created: POST 新建资源成功。
• 301 永久重定向:资源位置永久改变,浏览器会缓存
• 302 临时重定向:临时跳转,常见于登录后跳转
• 304 Not Modified:协商缓存,资源未修改,用于优化性能
• 400 Bad Request:请求参数错误
• 401 Unauthorized:未认证或 token 无效
• 403 Forbidden:认证了但没有权限
• 404 Not Found:资源不存在
• 500 Internal Server Error:服务端程序错误。
• 502 Bad Gateway:网关/代理收到无效响应(常见于 Nginx)
• 503 Service Unavailable:服务不可用,通常是服务过载或维护
-
HTTP 请求头中包含什么?
大体可以分为四类: 通用信息(Host、User-Agent、Accept 等)、缓存与来源控制(Cache-Control、Referer)、认证与安全(Authorization、Cookie)、以及请求体相关(Content-Type、Content-Length)。 -
HTTP 是基于 TCP 还是 UDP?
HTTP/1.x 和主流 HTTP/2 都基于 TCP;而 HTTP/3 基于 UDP -
HTTP 长连接 vs. 短连接的区别是?
短连接:请求一次建立一次连接,用完就断;
长连接:多个请求复用一个连接,性能更好 -
从「敲下一个 URL」到「页面出现在屏幕」整条链路全景
浏览器解析 URL 并查缓存 → DNS 解析域名 → TCP/HTTPS 握手建立连接 → 发送 HTTP 请求 → 服务器处理并返回响应 → 浏览器解析 渲染→ 断开链接 四次挥手 -
GET 与 POST 有什么区别?
GET 用于获取数据,参数在 URL,幂等且可缓存;
POST 用于提交数据,参数在请求体,非幂等,不易缓存。
GET 适合查询,POST 适合创建/更新资源。
幂等(多次请求结果相同,不改变服务器状态)
非幂等(多次提交可能创建多条数据或触发多次操作)
8.HTTP vs. HTTPS 有什么区别?
HTTP以明文形式传输,传输的数据可能会被窃听、篡改、仿造。
HTTPS在 HTTP 上加了 TLS/SSL 加密。更安全, 适合敏感信息传输
-
WebSocket 简介 & 与 HTTP 的核心区别
WebSocket 是基于 TCP 的全双工长连接协议,建立连接后客户端和服务器可以互相发送数据,适合实时高频场景。
HTTP 是单向请求-响应协议,客户端必须先发起请求,服务器才能响应。
WebSocket数据传输开销更小,效率更高。” -
TCP 与 UDP 的区别?
TCP 是面向连接、可靠的传输协议,保证数据完整、顺序、无重复,但开销大,适合需要可靠传输的场景, 比如文件传输, 远程登录
UDP 是无连接、不可靠协议,开销小、传输快,适合对实时性要求高、能容忍少量丢包的场景, 比如视频直播、在线游戏、语音通信 -
TCP 的三次握手
第一次握手:客户端向服务器端发送SYN,确认客户端的发送没问题。
第二次握手:服务器向客户端发送SYN和ACK包,确认服务器的发送与接收没问题
第三次握手:客户端最后回复一个ACK包,确认客户端的接收没问题。 -
数据包在网络中传输的过程
-
发送方将应用数据封装成数据包,添加源地址,发送方法,传输协议类型,目的地址等
-
选择路由转发数据包,发送到目标主机
-
接收端解密数据包,恢复成原始数据
-
ip地址的划分
IP地址主要分为 IPv4 和 IPv6,日常使用更多的是 IPv4。IPv4 通过“网络位”和“主机位”来区分,用于实现网络分层管理和设备寻址。
IPv4 按类别划分为 A/B/C/D/E 类:
• A 类(1.0.0.0–126.255.255.255)用于大型网络,网络位 8 位,主机位 24 位;
• B 类(128.0.0.0–191.255.255.255)用于中型网络,网络位 16 位,主机位 16 位;
• C 类(192.0.0.0–223.255.255.255)用于小型网络,网络位 24 位,主机位 8 位;
• D 类用于组播,E 类为保留地址。
现代网络更常用 无类别域间路由,通过“IP 地址/子网掩码长度”表示,例如 192.168.1.0/24,其中前 24 位为网络位,后 8 位为主机位,可灵活调整网络和主机数量,提高地址利用率。