核心区别:C标准库 vs. 操作系统API
这是理解两者最关键的一点:

(图片来源网络,侵删)
-
fread(Formatted Read)- 所属库: C标准库,也就是我们常说的
stdio.h。 - 性质: 是一个高级I/O函数,它被设计为缓冲的,并且是可移植的,可以在任何支持C标准的操作系统(如Windows, Linux, macOS)上以相同的方式工作。
- 工作方式:
fread并不直接与硬件(如磁盘)交互,它会通过一个缓冲区来操作,当你调用fread时,它可能先从文件描述符(底层操作系统资源)所关联的缓冲区读取数据,如果缓冲区为空,它会自动调用更底层的系统函数(在Linux/Unix上是read,在Windows上是ReadFile)来填充缓冲区,这个过程称为缓存或I/O优化。
- 所属库: C标准库,也就是我们常说的
-
read(Read)- 所属库: POSIX标准,通常通过
unistd.h(在Linux/Unix/macOS上) 或io.h(在Windows上) 引入。 - 性质: 是一个底层I/O系统调用,它是操作系统提供给应用程序的接口,直接与内核交互。
- 工作方式:
read是无缓冲的(或者说,缓冲区由操作系统内核管理,应用程序无法直接控制),它直接从文件描述符(一个整数,代表一个打开的文件、套接字等)读取原始字节到指定的内存缓冲区,它不关心数据的格式,只负责搬运字节。
- 所属库: POSIX标准,通常通过
函数原型详解
fread
#include <stdio.h> size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- 参数:
void *ptr: 指向一个内存块的指针,fread会将读取到的数据存放到这里。size: 每个数据项的大小(单位:字节),如果你想读取一个int数组,sizesizeof(int)。nmemb: 你想要读取的数据项的个数。FILE *stream: 一个指向FILE对象的指针,这个FILE对象代表了你想要读取的流(通过fopen打开的文件)。
- 返回值:
- 成功时,返回实际读取到的数据项的个数(注意,不是字节数!)。
- 如果到达文件末尾或发生错误,返回值可能小于
nmemb。 - 如果发生错误或读到文件末尾且没有读取任何数据,则返回
0,你可以使用feof()或ferror()函数来区分是文件结束还是错误。
- 特点:
- 面向“记录”或“块”:它以“size * nmemb”大小的块为单位进行操作。
- 缓冲:性能通常更高,因为它减少了直接的系统调用次数。
- 格式无关:它也是直接读写二进制数据,但常与
fwrite配合使用来读写结构化数据。
read (以Linux/Unix为例)
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
- 参数:
int fd: 文件描述符,一个非负整数,唯一标识一个打开的文件、管道、套接字等,通过open()函数获得。void *buf: 指向一个内存缓冲区的指针,用于存放读取到的数据。size_t count: 你希望读取的字节数。
- 返回值:
- 成功时,返回实际读取到的字节数。
- 如果到达文件末尾,返回
0。 - 如果发生错误,返回
-1,并设置errno来表示具体的错误。
- 特点:
- 面向“字节流”:它以字节为单位进行操作。
- 无缓冲:直接与内核交互,每次调用都会触发系统调用。
- 低级:功能更基础,更接近操作系统。
使用场景对比
| 特性 | fread |
read |
|---|---|---|
| 所属标准 | C标准库 (stdio.h) |
POSIX标准 (unistd.h) |
| 抽象级别 | 高级 | 底层 |
| 操作对象 | FILE* 流 |
文件描述符 (int fd) |
| 缓冲机制 | 有缓冲 (用户空间缓冲) | 无缓冲 (内核缓冲) |
| 性能 | 通常更高 (系统调用次数少) | 通常较低 (每次调用都是系统调用) |
| 可移植性 | 极高 (所有平台行为一致) | 较低 (Windows API不同) |
| 主要用途 | 读取文本文件 (fopen + fread)读写二进制数据(如结构体) 大多数常规文件I/O |
需要精确控制I/O时 网络编程 (读取套接字) 设备文件I/O 高性能场景下自行管理缓冲区 |
代码示例
示例 1: 使用 fread 读取文件
#include <stdio.h>
int main() {
FILE *fp;
char buffer[256];
// 以二进制模式打开文件,避免平台相关的换行符转换
fp = fopen("example.txt", "rb");
if (fp == NULL) {
perror("Error opening file");
return 1;
}
// 一次读取一个 sizeof(char) 大小的数据项,共 256 个
size_t items_read = fread(buffer, sizeof(char), 256, fp);
if (items_read > 0) {
printf("Successfully read %zu characters.\n", items_read);
buffer[items_read] = '\0'; // 确保字符串正确终止
printf("Content: %s\n", buffer);
} else if (feof(fp)) {
printf("Reached end of file.\n");
} else if (ferror(fp)) {
perror("Error reading file");
}
fclose(fp);
return 0;
}
示例 2: 使用 read 读取文件 (Linux/Unix)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int main() {
int fd;
char buffer[256];
ssize_t bytes_read;
// 打开文件,返回文件描述符
fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("Error opening file");
return 1;
}
// 循环读取,直到文件末尾或出错
while ((bytes_read = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
buffer[bytes_read] = '\0'; // 确保字符串正确终止
printf("Read %zd bytes. Content: %s\n", bytes_read, buffer);
}
if (bytes_read == 0) {
printf("Reached end of file.\n");
} else if (bytes_read == -1) {
perror("Error reading file");
}
close(fd);
return 0;
}
总结与选择建议
如何选择?
-
首选
fread:
(图片来源网络,侵删)- 如果你正在进行常规的文件操作,无论是文本还是二进制,
fread几乎总是更好的选择。 - 它的缓冲机制为你提供了更好的性能和更少的代码量。
- 它的可移植性让你无需关心底层操作系统的差异。
- 如果你正在进行常规的文件操作,无论是文本还是二进制,
-
考虑
read:- 当你需要进行网络编程(通过
socket的文件描述符读写数据)时,read是标准做法。 - 当你需要与特殊设备文件(如
/dev/input/mice)交互时。 - 在一些对性能要求极致的特殊场景下,你可能想绕过C库的缓冲,自己管理内存缓冲区,这时可以直接使用
read。 - 当你编写的代码需要高度依赖POSIX特性,并且不打算移植到Windows时。
- 当你需要进行网络编程(通过
一个简单的比喻:
fread就像你去超市买东西,你直接推着购物车(缓冲区)去货架取货,超市会保证货架上有货(自动填充缓冲区),你不需要关心货是从哪个仓库运来的。read就像你直接去仓库,每次只能让仓库给你搬运一小箱货物(直接系统调用)到你指定的位置,你需要自己管理货物的存放和搬运计划。
对于绝大多数应用程序来说,使用 fread 这样的高级I/O函数是更安全、更高效、更简单的方式。

(图片来源网络,侵删)
