Java基础
Java基础
反射相关
反射机制指的是程序在运行时能够获取自身的信息。
反射操作的目标对象(实例)是从堆(Heap)中获得的,而类的元数据(如方法、字段、构造方法等结构信息)是从方法区(Method Area)或元空间(Metaspace) 中获得的。
为什么反射慢?
- 由于反射涉及动态解析的类型,因此不能执行某些Java虚拟机优化,如JIT优化。
- 在使用反射时,参数需要包装(boxing)成Object[] 类型,但是真正方法执行的时候,又需要再拆包(unboxing)成真正的类型,这些动作不仅消耗时间,而且过程中也会产生很多对象,对象一多就容易导致GC,GC也会导致应用变慢。
- 反射调用方法时会从方法数组中遍历查找,并且会检查可见性。这些动作都是耗时的。
a=a+b 与 a+=b 的区别
+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。
byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok
// (因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)
finalize是什么
Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,但是什么时候调用 finalize 没有保证。
为什么不能用BigDecimal的equals方法做等值比较?
因为BigDecimal的equals方法和compareTo并不一样,equals方法会比较两部分内容,分别是值(value)和标度(scale),而对于0.1和0.10这两个数字,他们的值虽然一样,但是精度是不一样的,所以在使用equals比较的时候会返回false。
String、StringBuilder和StringBuffer
Java中的+对字符串的拼接,其实现原理是使用StringBuilder.append。
StringBuilder线程不安全 StringBuffer线程安全
JDK动态代理和Cglib动态代理的区别
JDK 动态代理是基于接口的,所以要求代理类一定是有定义接口的。
CGLIB 基于 ASM 字节码生成工具,它是通过继承的方式生成目标类的子类来实现代理类,所以 要注意 final 方法。
它们之间的性能随着 JDK 版本的不同而不同
- jdk6 下,在运行次数较少的情况下,jdk动态代理与 cglib 差距不明显,甚至更快一 些;而当调用次数增加之后, cglib 表现稍微更快一些
- jdk7 下,情况发生了逆转!在运行次数较少(1,000,000)的情况下,jdk动态代理比 cglib 快了差不多30%;而当调用次数增加之后(50,000,000), 动态代理比 cglib 快了 接近1倍
- jdk8 表现和 jdk7 基本一致
finally中代码一定会执行吗?
如果没有符合这两个条件的话,finally中的代码就无法被执行,如发生以下情况,都会导致finally不会执行:
1、System.exit()方法被执行
2、Runtime.getRuntime().halt()方法被执行
3、try或者catch中有死循环
4、操作系统强制杀掉了JVM进程,如执行了kill -9
5、其他原因导致的虚拟机崩溃了
6、虚拟机所运行的环境挂了,如计算机电源断了
7、如果一个finally是由守护线程执行的,那么是不保证一定能执行的,如果这时候JVM要退出,JVM会检查其他非守护线程,如果都执行完了,那么就直接退出了。这时候finally可能就没办法执行完。
什么是AIO、BIO和NIO?
BIO (Blocking I/O):同步阻塞I/O,是JDK1.4之前的传统IO模型。 线程发起IO请求后,一直阻塞,直到缓冲区数据就绪后,再进入下一步操作。
NIO ( I/O):同步非阻塞IO,线程发起IO请求后,不需要阻塞,立即返回。用户线程不原地等待IO缓冲区,可以先做一些其他操作,只需要定时轮询检查IO缓冲区数据是否就绪即可。
AIO ( Asynchronous I/O):异步非阻塞I/O模型。线程发起IO请求后,不需要阻塞,立即返回,也不需要定时轮询检查结果,异步IO操作之后会回调通知调用方。

CopyonWriteArraylist是如何实现线程安全的
CopyOnWriteArrayList底层也是通过一个数组保存数据,使用volatile关键字修饰数组,保证当前线程对数组对象重新赋值后,其他线程可以及时感知到。
在写入操作时,加了一把互斥锁ReentrantLock以保证线程安全。
看到源码可以知道写入新元素时,首先会先将原来的数组拷贝一份并且让原来数组的长度+1后就得到了一个新数组,新数组里的元素和旧数组的元素一样并且长度比旧数组多一个长度,然后将新加入的元素放置都在新数组最后一个位置后,用新数组的地址替换掉老数组的地址就能得到最新的数据了。
在我们执行替换地址操作之前,读取的是老数组的数据,数据是有效数据;执行替换地址操作之后,读取的是新数组的数据,同样也是有效数据,而且使用该方式能比读写都加锁要更加的效率。
现在我们来看读操作,读是没有加锁的,所以读是一直都能读
HashMap的大小为什么是2的n次方?
在 JDK1.7 中,HashMap 整个扩容过程就是分别取出数组元素,一般该元素是最后一个放入链表中的元素,然后遍历以该元素为头的单向链表元素,依据每个被遍历元素的 hash 值计算其在新数组中的下标,然后进行交换。这样的扩容方式会将原来哈希冲突的单向链表尾部变成扩容后单向链表的头部。
而在 JDK 1.8 中,HashMap 对扩容操作做了优化。由于扩容数组的长度是 2 倍关系,所以对于假设初始 tableSize = 4 要扩容到 8 来说就是 0100 到 1000 的变化(左移一位就是 2 倍),在扩容中只用判断原来的 hash 值和左移动的一位(newtable 的值)按位与操作是 0 或 1 就行,0 的话索引不变,1 的话索引变成原索引加上扩容前数组。
之所以能通过这种“与运算“来重新分配索引,是因为 hash 值本来就是随机的,而 hash 按位与上 newTable 得到的 0(扩容前的索引位置)和 1(扩容前索引位置加上扩容前数组长度的数值索引处)就是随机的,所以扩容的过程就能把之前哈希冲突的元素再随机分布到不同的索引中去。
ConcurrentHashMap怎么实现的?
JDK 1.7 ConcurrentHashMap
在 JDK 1.7 中它使用的是数组加链表的形式实现的,而数组又分为:大数组 Segment 和小数组 HashEntry。 Segment 是一种可重入锁(ReentrantLock),在 ConcurrentHashMap 里扮演锁的角色;HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一个 Segment 数组,一个 Segment 里包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素。

JDK 1.7 ConcurrentHashMap 分段锁技术将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。
JDK 1.8 ConcurrentHashMap
在 JDK 1.7 中,ConcurrentHashMap 虽然是线程安全的,但因为它的底层实现是数组 + 链表的形式,所以在数据比较多的情况下访问是很慢的,因为要遍历整个链表,而 JDK 1.8 则使用了数组 + 链表/红黑树的方式优化了 ConcurrentHashMap 的实现,具体实现结构如下:

JDK 1.8 ConcurrentHashMap JDK 1.8 ConcurrentHashMap 主要通过 volatile + CAS 或者 synchronized 来实现的线程安全的。添加元素时首先会判断容器是否为空:
- 如果为空则使用 volatile 加 CAS 来初始化
- 如果容器不为空,则根据存储的元素计算该位置是否为空。
- 如果根据存储的元素计算结果为空,则利用 CAS 设置该节点;
- 如果根据存储的元素计算结果不为空,则使用 synchronized ,然后,遍历桶中的数据,并替换或新增节点到桶中,最后再判断是否需要转为红黑树,这样就能保证并发访问时的线程安全了。
如果把上面的执行用一句话归纳的话,就相当于是ConcurrentHashMap通过对头结点加锁来保证线程安全的,锁的粒度相比 Segment 来说更小了,发生冲突和加锁的频率降低了,并发操作的性能就提高了。
而且 JDK 1.8 使用的是红黑树优化了之前的固定链表,那么当数据量比较大的时候,查询性能也得到了很大的提升,从之前的 O(n) 优化到了 O(logn) 的时间复杂度。
Linux常用的命令有哪些
以下是一份Linux常用命令速查表,涵盖文件操作、系统管理、网络工具等核心场景:
📂文件与目录操作
命令 | 作用 | 常用示例 |
---|---|---|
ls | 列出目录内容 | ls -alh (详细列表含隐藏文件) |
cd | 切换目录 | cd ~ (返回家目录) |
pwd | 显示当前路径 | pwd |
mkdir | 创建目录 | mkdir -p dir1/dir2 (递归创建) |
rmdir | 删除空目录 | rmdir empty_dir |
cp | 复制文件 | cp -r dir1/ dir2/ (递归复制目录) |
mv | 移动/重命名文件 | mv old.txt new.txt |
rm | 删除文件 | rm -rf dir/ (⚠️强制递归删除) |
touch | 创建空文件/更新时间戳 | touch file.txt |
cat | 显示文件内容 | cat file.txt |
less / more | 分页查看文件 | less -N log.log (显示行号) |
head / tail | 查看文件头部/尾部 | tail -f app.log (实时追踪日志) |
find | 搜索文件 | find /home -name "*.conf" |
grep | 文本搜索 | grep -r "error" /var/log/ |
chmod | 修改权限 | chmod 755 script.sh |
chown | 修改所有者 | chown user:group file.txt |
💻系统与进程管理
命令 | 作用 | 常用示例 |
---|---|---|
ps | 查看进程 | `ps -ef |
top / htop | 动态监控进程资源 | top -u mysql (按用户过滤) |
kill | 终止进程 | kill -9 12345 (强制终止) |
systemctl | 系统服务管理 | systemctl restart apache2 |
df | 磁盘空间统计 | df -hT (人类可读+文件系统类型) |
du | 目录空间占用 | du -sh /var/ (汇总大小) |
free | 内存使用情况 | free -m (以MB显示) |
uname | 系统信息 | uname -a (内核版本等) |
uptime | 系统运行时间 | uptime (负载情况) |
🌐网络工具
命令 | 作用 | 常用示例 |
---|---|---|
ping | 测试网络连通性 | ping -c 4 google.com |
ifconfig / ip | 网络接口配置 | ip addr show (查看IP地址) |
netstat | 网络连接状态 | netstat -tulpn (监听端口) |
ss | 更高效的socket统计 | ss -ltn (监听TCP端口) |
curl | 网络数据传输 | curl -I https://example.com |
wget | 下载文件 | wget -c http://file.zip |
ssh | 远程登录 | ssh user@192.168.1.100 |
scp | 安全传输文件 | scp file.txt user@host:/tmp/ |
traceroute | 路由追踪 | traceroute example.com |
📦压缩与解压
命令 | 作用 | 常用示例 |
---|---|---|
tar | 打包/解包 | tar -czvf backup.tar.gz dir/ (压缩) tar -xzvf backup.tar.gz (解压) |
gzip / gunzip | .gz压缩/解压 | gzip file.txt → file.txt.gz |
zip / unzip | .zip压缩/解压 | unzip archive.zip -d target/ |
Bio Nio Aio的区别和特点
特性 | BIO (阻塞 I/O) | NIO (非阻塞 I/O / 新 I/O) | AIO (异步 I/O) |
---|---|---|---|
阻塞性 | 阻塞 | 非阻塞 | 非阻塞 |
同步/异步 | 同步 | 同步 | 异步 |
线程模型 | 1 连接 : 1 线程 | 多连接 : 1 线程 (Selector) | 发起 I/O : 0 线程阻塞 |
工作方式 | 调用后阻塞,等待操作完成 | 调用立即返回,需轮询或 Selector 等待事件 | 调用立即返回,内核完成后回调 |
复杂度 | 简单 | 复杂 (Selector, Buffer, 状态管理) | 中等 (回调/Future) |
性能 | 低 (线程开销大) | 高 (单线程处理多连接) | 理论上最高 (线程零阻塞) |
可伸缩性 | 差 | 好 | 好 |
内核角色 | 完成操作后唤醒线程 | 通知事件就绪,应用负责读写 | 负责整个操作并通知完成 |
代表实现 | Java java.io.* | Java java.nio.* , Netty, epoll | Java java.nio.channels.Asynchronous* , Windows IOCP, Linux io_uring |
适用场景 | 低并发、简单应用 | 高并发网络应用 | 极致性能、特定平台、简化异步编程 |
Binlog的格式有哪几种?
STATEMENT (基于语句的复制 - SBR)
- 记录内容: 记录的是实际执行的 SQL 语句本身(如
UPDATE users SET name='Alice' WHERE id=1;
)。 - 优点:
- 日志文件小: 因为只记录 SQL 语句,对于影响大量行的操作(如
UPDATE ... WHERE ...
匹配很多行),日志量远小于 ROW 模式。 - 可读性强: 直接查看 binlog 文件或使用
mysqlbinlog
工具可以清晰地看到执行过的 SQL,便于人工审计和理解。 - 历史悠久: 是最早的 binlog 格式,兼容性好。
- 日志文件小: 因为只记录 SQL 语句,对于影响大量行的操作(如
- 缺点:
- 主从不一致风险 (最主要缺点): 某些 SQL 语句的执行结果可能依赖于上下文(如使用了
NOW()
,RAND()
,USER()
,UUID()
, 存储过程/函数、触发器、使用了不同索引等),导致在主库和从库上执行相同的 SQL 语句可能产生不同的结果,造成数据不一致。 - 锁竞争可能更严重: 某些需要在从库上重放的语句(如
UPDATE ... WHERE ...
没有使用索引)可能在从库上造成全表扫描和锁表。
- 主从不一致风险 (最主要缺点): 某些 SQL 语句的执行结果可能依赖于上下文(如使用了
- 适用场景: 对数据一致性要求不是极其严格,且 SQL 模式简单(避免使用不确定函数、存储过程等)的场景;或者需要节省磁盘空间和网络带宽的场景(需谨慎评估不一致风险)。
ROW (基于行的复制 - RBR)
- 记录内容: 记录的是每一行数据在修改前和修改后的内容(或仅修改后的内容)。对于
UPDATE
,记录被修改行的主键(或唯一标识)以及所有列的新值(或仅被修改列的新值);对于INSERT
,记录新行的所有列值;对于DELETE
,记录被删除行的主键(或唯一标识)和所有列值。 - 优点:
- 数据一致性高 (最主要优点): 由于记录的是实际行的变化,而不是 SQL 语句,因此复制过程与 SQL 语句的执行上下文无关,能最大程度保证主从数据的一致性。解决了
STATEMENT
模式下不确定函数等问题。 - 减少锁竞争: 在从库应用更改时,通常是基于行 ID 操作,可以减少锁的粒度(行锁),降低锁竞争。
- 更安全的复制: 对存储过程、触发器、不确定函数等更友好。
- 数据一致性高 (最主要优点): 由于记录的是实际行的变化,而不是 SQL 语句,因此复制过程与 SQL 语句的执行上下文无关,能最大程度保证主从数据的一致性。解决了
- 缺点:
- 日志文件大: 对于影响大量行的操作(如批量更新、删除),每条被修改的行都会产生一条记录,日志量会非常大,占用更多磁盘空间和网络带宽(尤其是在主从同步时)。
- 可读性差: 使用
mysqlbinlog
查看时,看到的是行的二进制数据或 Base64 编码(除非使用-v
或--verbose
选项解析),不如 SQL 语句直观。 - 生成日志慢: 写入每行变更通常比记录一条 SQL 语句开销更大。
- 适用场景: 推荐默认使用,尤其是在对数据一致性要求极高的场景(如金融交易)、使用了不确定函数、存储过程、触发器,或者主从表结构不完全一致(但需注意列映射)的情况。MySQL 5.7.7 及以后版本的默认 binlog 格式就是
ROW
。
MIXED (混合模式复制)
记录内容:
结合了
STATEMENT
和ROW
两种模式。MySQL 会根据执行的 SQL 语句动态决定使用哪种格式记录 binlog。
- 对于安全的、可以确定在主从库上执行结果一致的语句(如不包含不确定函数的简单 CRUD),使用
STATEMENT
格式记录(节省空间)。 - 对于不安全的、可能导致主从不一致的语句(如包含
NOW()
,UUID()
, 用户自定义函数 UDF 等),则自动切换到ROW
格式记录(保证一致性)。
- 对于安全的、可以确定在主从库上执行结果一致的语句(如不包含不确定函数的简单 CRUD),使用
优点:
- 兼顾空间和一致性: 试图在
STATEMENT
的空间效率和ROW
的数据一致性之间取得平衡。 - 灵活性: 自动选择最优(或最安全)的记录方式。
- 兼顾空间和一致性: 试图在
缺点:
- 规则复杂: 需要理解 MySQL 判断“安全”和“不安全”的内部规则。
- 仍有小概率不一致风险: 虽然减少了
STATEMENT
的风险,但理论上如果 MySQL 错误地将一个“不安全”的语句判断为“安全”并用STATEMENT
记录,仍可能导致不一致(不过这种情况较少)。 - 日志大小不确定: 日志大小取决于实际执行的语句类型,不如单一模式可预测。
适用场景: 希望在一定程度上平衡日志大小和数据一致性风险的场景。在 MySQL 5.7.7 之前是默认格式。
select poll epoll的区别和特点
特性 | select | poll | epoll |
---|---|---|---|
基本原理 | 遍历+条件触发 | 遍历+条件触发 | 回调+事件触发 |
时间复杂 (检查就绪) | O(n) | O(n) | O(1) (就绪数目) |
fd 数量限制 | 有 (通常 1024) | 无 (由系统资源限制) | 无 (由系统资源限制) |
数据结构 | fd_set (位图) | struct pollfd 数组 | 内核红黑树 + 就绪链表 |
内核/用户空间交互 | 每次调用拷贝 fd_set | 每次调用拷贝 pollfd 数组 | 使用 mmap 共享内存 |
工作模式 | 条件触发 (LT) | 条件触发 (LT) | 支持条件触发 (LT) 和边缘触发 (ET) |
性能 (fd 多且活跃少时) | 低 | 低 | 高 |
跨平台 | 广泛支持 (POSIX) | 多数 UNIX | Linux 特有 (内核2.5.44+) |
API 使用复杂度 | 中等 | 中等 | 较高 |
适用场景 | 低并发/跨平台要求 | 稍高并发/需突破 select 限制 | 高并发/高性能服务器 |
select
特点与原理:
select
使用三个位图集合 (fd_set
) 来分别表示需要监视的可读、可写、异常文件描述符。- 调用时,用户态将这三个集合传递给内核。内核遍历所有传入的 fd,检查它们的状态。
select
会阻塞(直到有 fd 就绪或超时),或者非阻塞轮询。- 当
select
返回时,它会修改传入的fd_set
,标识哪些 fd 已经就绪,同时返回就绪的 fd 总数。 - 用户程序需要再次遍历自己之前传入的所有 fd,找到那些被
select
标记为就绪的 fd 进行处理。
优点:
- 跨平台: 被几乎所有主流操作系统支持,有良好的可移植性。
- API 相对简单: 概念清晰。
缺点:
fd 数量限制: 单个进程所能监视的 fd 数量有上限(由
FD_SETSIZE
定义,通常为 1024)。这是fd_set
位图的结构限制。线性扫描瓶颈:
- 内核态: 每次调用,内核都必须线性扫描 (O(n)) 所有传递给它的 fd(即使只有几个活跃)。
- 用户态:
select
返回后,用户程序也必须线性扫描所有自己关心的 fd 来确定哪些就绪(因为fd_set
告诉你有就绪,但不告诉你是哪几个,除非遍历)。
重复数据拷贝: 每次调用
select
都需要将包含大量 fd 信息的fd_set
从用户空间拷贝到内核空间;返回时又要将修改后的fd_set
从内核空间拷贝回用户空间。这对 fd 集合很大时带来显著开销。仅支持条件触发 (LT):
select
只支持水平触发模式。
总结: 简单、跨平台,但性能低下,只适用于连接数较少的场景。
poll
特点与原理:
poll
使用一个struct pollfd
的数组来管理 fd。每个pollfd
结构包含 fd 本身、用户关心的事件掩码 (events
)、以及内核返回的已发生事件掩码 (revents
)。- 调用时,用户态将这个数组指针传递给内核。内核遍历数组中的所有 fd,检查它们的状态。
poll
的行为同样会阻塞(或非阻塞),直到有 fd 就绪或超时。- 当
poll
返回时,它会修改每个pollfd
结构中的revents
字段来标识该 fd 上发生了什么事件(可读、可写、错误等),同时返回就绪的 fd 总数。 - 用户程序需要遍历整个
pollfd
数组,检查每个元素的revents
字段,找出就绪的 fd 进行处理。
优点:
- 突破 fd 数量限制: 使用数组传递 fd,不再有类似
select
的硬编码FD_SETSIZE
限制。fd 数量的理论上限仅受系统资源(如进程能打开的最大文件数)约束。 - 事件区分更精细:
pollfd
中的events
和revents
允许指定和区分更多类型的事件(如普通数据、带外数据、错误、连接挂断等),比select
的三种简单集合更灵活。
- 突破 fd 数量限制: 使用数组传递 fd,不再有类似
缺点:
线性扫描瓶颈依然存在:
- 内核态: 仍然需要线性扫描所有传入的 fd (O(n))。
- 用户态: 仍然需要线性扫描整个数组 (
pollfd
) 以找到就绪的 fd (revents
!= 0)。
重复数据拷贝: 每次调用
poll
都需要将包含大量pollfd
结构的数组从用户空间拷贝到内核空间;返回时也要将修改后的revents
从内核空间拷贝回用户空间。仅支持条件触发 (LT): 和
select
一样。
总结: 解决了
select
的 fd 数量限制,提供了更精细的事件描述,但性能瓶颈(内核和用户态的双重线性扫描、数据拷贝)与select
本质相同,仍不适用于大规模高并发。
epoll
特点与原理:
是 Linux 特有的高效 I/O 多路复用机制。它的核心设计思想是:
避免每次调用时的无差别轮询扫描和大量数据拷贝,直接跟踪真正活跃的 fd。
三个阶段:
epoll_create()
: 创建一个 epoll 实例(一个句柄epfd
),该实例在内核对应一个数据结构(通常包含一个红黑树和一个就绪链表)。
epoll_ctl()
: 向 epoll 实例(epfd
)注册/修改/删除 需要监听的 fd。这个操作只需要在你关注的事件集合变化时才调用(例如新连接建立时添加)。调用时提供epfd
,op
(操作类型),fd
, 和struct epoll_event
(包含用户关心的事件和关联数据)。epoll_wait()
: 等待事件发生。调用时,它阻塞(或限时)直到至少有一个之前注册的 fd 上发生事件,或者超时。当它返回时,它只填充用户提供的epoll_event
数组,其中只包含实际就绪的事件信息(fd 和具体事件)。用户程序只需遍历这个填充好的数组(里面全是就绪事件)即可处理,无需扫描所有被监视的 fd。
高效的核心机制:
- 内核数据结构: 内核使用一个红黑树来高效管理所有注册的 fd(
epoll_ctl
插入/删除 O(log n))。更重要的是,使用一个就绪链表(队列) 来存放被触发的事件。当某个 fd 上的事件发生时(例如收到数据),内核会通过注册的回调函数(callback)迅速地将一个表示该事件的epitem
结构放入就绪链表。这是epoll
事件驱动特性的关键,避免了轮询扫描。
- 内核数据结构: 内核使用一个红黑树来高效管理所有注册的 fd(
内存映射 (
mmap
):epoll
使用mmap
在内核空间和用户空间共享一块内存。当epoll_wait
返回时,内核可以直接将就绪事件的信息填入这块共享内存区域 (epoll_event
数组),用户程序也能直接读取。这避免了select/poll
那种数据在用户-内核间来回拷贝的开销。
触发模式:
- 条件触发 (LT - Level Triggered): 默认模式。只要 fd 处于就绪状态(如接收缓冲区有数据可读),每次调用
epoll_wait
都会报告它。类似于select/poll
的行为。 - 边缘触发 (ET - Edge Triggered): 只有当 fd 的状态发生改变时(例如从无数据变为有数据,或者新数据到达),才会报告一次该 fd 上的事件。使用 ET 模式时,用户程序必须一次性处理完所有可用的数据(循环读取/写入直到出现
EAGAIN
错误),否则后续epoll_wait
调用不会再通知你(除非又有新事件导致状态变化)。ET 模式可以进一步减少epoll_wait
的调用次数,提高效率。
- 条件触发 (LT - Level Triggered): 默认模式。只要 fd 处于就绪状态(如接收缓冲区有数据可读),每次调用
优点:
极致性能:
- 内核无扫描: 检查就绪事件的复杂度是 O(1),因为它只看就绪链表,而不是扫描所有 fd。
用户无扫描:
epoll_wait
返回的数组中只包含活跃事件,用户只需处理这些事件即可 (O(就绪事件数) )。无数据拷贝: 利用
mmap
共享内存,避免了用户-内核空间的大数据拷贝。无内置 fd 数量限制: 仅受系统全局资源限制。
支持边缘触发 (ET): 提供更高效的工作模式(需要正确使用)。
缺点:
- Linux 专属: 是 Linux 独有的特性,不具备
select/poll
的跨平台性。 - API 相对复杂: 需要理解三个函数 (
create
,ctl
,wait
) 和事件结构。ET 模式的使用有特殊要求。 - 调试相对复杂: 内核回调机制使得追踪事件来源不像遍历那么简单。
- Linux 专属: 是 Linux 独有的特性,不具备