平时准备 Java 后端面试时,我们经常会碰到一组看起来没什么联系的问题:RAID 有哪些级别?软链接和硬链接有什么区别?BIO、NIO、AIO 又分别代表什么?
其实,这些知识都和“数据如何存储、如何被程序读取”有关。RAID 解决的是磁盘层面的容量、性能和可靠性问题;软硬链接属于文件系统;各种 I/O 模型则决定了程序等待和读取数据的方式。
一、什么是 RAID
RAID 的全称是 Redundant Array of Independent Disks,中文通常叫“独立磁盘冗余阵列”。简单理解,就是把多块物理磁盘组合起来,对外表现为一个逻辑磁盘。
为什么要这样做?因为单块磁盘通常很难同时满足三个要求:容量大、读写快、故障后数据不丢。RAID 通过条带、镜像和校验等方式,在容量、性能与可靠性之间做取舍。
RAID 0:只追求性能
RAID 0 会把数据拆分后,分别写入多块磁盘。比如一份数据被分成 A、B、C、D 四块,两块磁盘可以分别保存 A、C 和 B、D。多块磁盘并行工作,因此读写速度较快。
它的缺点也很直接:没有任何冗余。只要其中一块磁盘损坏,整个阵列的数据都可能无法正常使用。
-
最少需要 2 块磁盘;
-
可用容量等于所有磁盘容量之和;
-
读写性能好,但不能容错;
-
适合临时数据、缓存或可重新生成的数据。
RAID 1:用空间换可靠性
RAID 1 也叫镜像。写入一份数据时,会在另一块磁盘中保存完全相同的副本。一块磁盘坏了,还能从另一块读取数据。
它的问题是空间利用率低。两块 1 TB 磁盘组成 RAID 1,真正可用的容量通常只有 1 TB。
RAID 5:容量与安全之间的折中
RAID 5 至少需要 3 块磁盘。它除了保存数据,还会把校验信息分散到不同磁盘上。任意一块磁盘损坏后,可以利用剩余数据和校验信息恢复内容。
RAID 5 的读取性能通常不错,容量利用率也高于 RAID 1。不过写入时需要计算并更新校验信息,因此写性能会受到一定影响。阵列处于降级状态时,如果又有一块磁盘损坏,数据仍可能丢失。
RAID 10:先镜像,再条带
RAID 10 可以理解为 RAID 1 和 RAID 0 的组合。它先把磁盘两两组成镜像,再对镜像组做条带化,因此同时具备较好的性能与可靠性。
代价是至少需要 4 块磁盘,而且只有一半左右的总容量可以使用。数据库等既重视性能又重视可靠性的场景,经常会考虑 RAID 10。
| RAID 0 | 2 | N × 单盘容量 | 无 | 性能高、风险大 |
| RAID 1 | 2 | 约一半总容量 | 通常允许镜像组坏 1 块 | 简单可靠、空间利用率低 |
| RAID 5 | 3 | (N – 1) × 单盘容量 | 允许坏 1 块 | 容量利用率较高、写入有校验开销 |
| RAID 10 | 4 | 约一半总容量 | 取决于损坏位置 | 性能与可靠性较均衡 |
二、硬链接和软链接有什么区别
Linux 文件系统通常使用 inode 记录文件的元数据和数据位置,文件名则可以理解为指向 inode 的入口。
硬链接相当于给同一个 inode 再取一个名字。两个文件名地位基本相同,删除其中一个名称,只是减少了一条引用;只要还有硬链接存在,文件数据就不会被真正删除。
ln source.txt hard-link.txt
软链接也叫符号链接,更像 Windows 的快捷方式。它是一个独立的小文件,保存的是目标文件的路径。如果目标被删除或移动,软链接仍然存在,但会变成无法访问的“失效链接”。
ln -s source.txt soft-link.txt
如果只记一句话:硬链接是“同一份数据的另一个名字”,软链接是“通往目标路径的快捷方式”。
三、BIO、NIO 和 AIO 的区别
在 Java 语境下,这三个概念常用来描述不同的 I/O 编程方式。
BIO:同步阻塞
BIO 是传统的阻塞 I/O。线程调用 read() 后,如果数据还没有到达,就会停在那里等待。早期网络服务器常采用“一个连接对应一个线程”的方式,代码直观,但连接数量很大时会创建大量线程,线程切换和内存开销也会随之增加。
BIO 并不是性能一定差。连接数不多、请求处理简单时,它反而容易开发和维护。
NIO:同步非阻塞
Java NIO 提供了 Channel、Buffer 和 Selector 等组件。线程可以通过 Selector 关注多个 Channel,只有某个连接真正就绪时,才处理相应事件。
这里容易出现一个误区:NIO 不代表整个过程都是异步的。数据就绪后,应用线程通常仍要自己执行读取操作,所以它更准确地说是同步非阻塞 I/O,并经常与 I/O 多路复用一起使用。
AIO:异步非阻塞
AIO 的思路是,应用发起读取请求后可以继续做其他事情;等操作系统完成 I/O,再通过回调或通知告诉应用结果。线程不需要反复询问数据是否准备好,也不必一直阻塞等待。
| BIO | 线程阻塞 | 应用线程等待并读取 | 简单,连接多时线程开销大 |
| NIO | 可先处理其他连接 | 应用在线程收到就绪事件后读取 | 适合连接多、活跃连接较少的场景 |
| AIO | 继续执行其他任务 | 系统完成后通知应用 | 编程模型异步,对系统支持有要求 |
四、什么是 I/O 多路复用
假设服务器同时维护一万个连接,但真正有数据到达的可能只有几十个。如果给每个连接安排一个线程,大部分线程都在等待,资源利用率并不高。
I/O 多路复用解决的正是这个问题:让一个线程同时关注多个文件描述符,等系统告诉它“哪些连接已经就绪”,再处理这些连接。这里的“多路”是多个连接,“复用”是复用少量线程。
多路复用并不表示一个线程能在同一时刻执行多个业务任务。它主要优化的是“等待哪些连接可读、可写”这件事。事件就绪后的业务处理如果很耗时,仍然可以交给工作线程池。
五、常见的五种 I/O 模型
理解 I/O 模型时,可以把一次读取分为两个阶段:第一阶段等待数据准备好,第二阶段把数据从内核缓冲区复制到应用缓冲区。不同模型的区别,主要就发生在这两个阶段。
1. 阻塞 I/O
调用读取方法后,线程从等待数据到数据复制完成,整个过程都被阻塞。模型简单,但等待期间线程无法做其他事情。
2. 非阻塞 I/O
如果数据没有准备好,系统会立即返回,应用过一会儿再询问。这避免了长时间阻塞,但不断轮询也会消耗 CPU。实际开发中,通常不会只靠这种忙轮询处理大量连接。
3. I/O 多路复用
应用通过 select、poll 或 epoll 等机制统一等待多个连接。某个连接就绪后,再由应用执行真正的读写。它仍属于同步 I/O,因为数据复制阶段通常由应用线程参与完成。
4. 信号驱动 I/O
应用先注册信号处理函数,然后继续执行其他工作。数据准备好时,内核发送信号通知应用,应用再进行读取。它减少了主动轮询,但信号处理和程序控制相对复杂,工程中没有多路复用常见。
5. 异步 I/O
应用提交 I/O 请求后立即返回,等待数据和复制数据都由系统负责。整个操作完成后,系统再通知应用。它和信号驱动 I/O 的关键区别是:信号驱动通常通知“可以读了”,异步 I/O 通知的是“已经读完了”。
网硕互联帮助中心



评论前必须登录!
注册