Linux文件编程——read函数与lseek函数
一、read函数
在 Linux 文件编程中,read
函数是一个系统调用,用于从文件描述符(File Descriptor)指向的文件或设备中读取数据到缓冲区。它是 Unix/Linux 系统编程中实现底层 I/O 操作的核心函数之一。以下是 read
函数的详细使用方法和注意事项。
1. 函数原型
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
- 参数:
fd
:文件描述符(通过open
或creat
等函数获得)。buf
:指向存储读取数据的缓冲区的指针。count
:要读取的最大字节数。
- 返回值:
- 成功时返回实际读取的字节数(可能小于
count
,例如到达文件末尾时返回0
)。 - 失败时返回
-1
,并设置errno
表示错误原因。
- 成功时返回实际读取的字节数(可能小于
2. 使用方法
基本示例
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>int main() {int fd;char buf[256];ssize_t bytes_read;// 打开文件(只读模式)fd = open("example.txt", O_RDONLY);if (fd == -1) {perror("open failed");return 1;}// 读取数据bytes_read = read(fd, buf, sizeof(buf) - 1); // 保留一个字节给 '\0'if (bytes_read == -1) {perror("read failed");close(fd);return 1;}// 添加字符串终止符并打印buf[bytes_read] = '\0'; // 确保是字符串printf("Read %zd bytes: %s\n", bytes_read, buf);// 关闭文件close(fd);return 0;
}
关键步骤
- 打开文件:
- 使用
open
函数获取文件描述符,指定读取模式(如O_RDONLY
)。
- 使用
- 读取数据:
- 调用
read(fd, buf, count)
,将数据从文件读取到buf
中。 - 检查返回值,确保读取成功。
- 调用
- 处理数据:
- 如果读取的是文本数据,通常需要手动添加字符串终止符
\0
。
- 如果读取的是文本数据,通常需要手动添加字符串终止符
- 关闭文件:
- 使用
close(fd)
释放文件描述符。
- 使用
3. 注意事项
(1) 返回值处理
- 正常情况:
- 返回实际读取的字节数(可能小于
count
,例如文件末尾或缓冲区不足)。 - 返回
0
表示已到达文件末尾(EOF)。
- 返回实际读取的字节数(可能小于
- 错误情况:
- 返回
-1
,需检查errno
(如EBADF
、EINTR
、EIO
等)。
- 返回
- 示例代码(循环读取):
ssize_t total_read = 0;
while (total_read < count) {ssize_t bytes_read = read(fd, buf + total_read, count - total_read);if (bytes_read == -1) {if (errno == EINTR) continue; // 被信号中断,重试perror("read failed");break;} else if (bytes_read == 0) {break; // EOF}total_read += bytes_read;
}
(2) 错误处理
- 常见错误包括:
EBADF
:无效的文件描述符。EINTR
:被信号中断(需重试)。EIO
:底层 I/O 错误。EAGAIN
/EWOULDBLOCK
:非阻塞模式下无数据可读(需结合select
/poll
使用)。
- 使用
perror
或strerror(errno)
打印错误信息。
(3) 缓冲与非阻塞 I/O
read
是无缓冲的(直接调用系统调用),但文件可能因缓冲设置(如O_NONBLOCK
)或设备特性而阻塞。- 对于非阻塞文件描述符(如套接字),
read
可能返回EAGAIN
或EWOULDBLOCK
,需结合select
/poll
使用。
(4) 性能优化
- 批量读取(减少系统调用次数)。
- 使用
mmap
映射文件到内存(适合大文件随机访问)。 - 对于大文件,考虑
pread
进行分散/聚集读取(Scatter-Gather I/O)。
(5) 安全性
- 确保缓冲区
buf
的大小足够,避免缓冲区溢出。 - 验证文件描述符
fd
的合法性(如通过fstat
检查)。 - 避免读取未初始化的数据或越界访问。
(6) 信号中断
- 如果
read
被信号中断(EINTR
),通常需要重新尝试读取。
(7) 二进制 vs 文本数据
read
是字节级操作,不关心数据格式(文本或二进制)。- 读取文本时需手动处理换行符或编码问题。
4. 替代函数
pread
:在指定偏移量处读取(无需lseek
)。readv
:聚集读取(将数据分散到多个缓冲区)。recv
:网络编程中用于套接字的读取(支持标志位)。
5. 常见问题
(1) 为什么 read
返回的字节数小于 count
?
- 文件末尾(EOF)。
- 缓冲区不足。
- 信号中断(
EINTR
)。 - 非阻塞 I/O 且无数据可读(
EAGAIN
/EWOULDBLOCK
)。
(2) 如何确保读取完整数据?
- 循环读取,直到满足条件(如读取到特定分隔符或固定长度)。
- 示例(读取固定长度数据):
#define FIXED_SIZE 1024
char buf[FIXED_SIZE];
ssize_t total = 0;
while (total < FIXED_SIZE) {ssize_t ret = read(fd, buf + total, FIXED_SIZE - total);if (ret <= 0) break; // 错误或 EOFtotal += ret;
}
(3) 如何处理大文件?
- 使用
mmap
映射文件到内存,避免频繁read
。 - 分块读取并处理。
6. 总结
read
是 Linux 文件编程的基础函数,使用时需注意:
- 正确处理返回值:区分 EOF、错误和部分读取。
- 错误处理:检查
errno
并处理信号中断。 - 性能优化:批量读取、避免频繁系统调用。
- 安全性:防止缓冲区溢出和非法访问。
- 非阻塞 I/O:结合
select
/poll
使用。
通过合理使用 read
,可以高效、可靠地完成文件 I/O 操作。
二、lseek
函数的使用方法
1.函数原型
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
- 参数:
fd
:文件描述符。offset
:偏移量(字节数),可正可负。whence
:基准点,取值如下:SEEK_SET
:从文件开头偏移。SEEK_CUR
:从当前位置偏移。SEEK_END
:从文件末尾偏移。
- 返回值:成功时返回新的文件偏移量,失败时返回
-1
并设置errno
。
2.典型用途
随机访问文件:通过定位到文件的任意位置进行读写。例如,读取文件第1024字节处的512字节数据:
int fd = open("data.dat", O_RDONLY);
lseek(fd, 1024, SEEK_SET); // 定位到文件开头后1024字节处
char buf[512];
read(fd, buf, sizeof(buf));
close(fd);
获取文件大小:通过定位到文件末尾,再获取当前偏移量:
int fd = open("file.txt", O_RDONLY);
off_t size = lseek(fd, 0, SEEK_END); // 偏移量为文件大小
printf("File size: %lld bytes\n", (long long)size);
close(fd);
创建空洞文件:通过将偏移量移动到超过当前文件大小的位置,再写入数据,中间的区域会被视为空洞(不占用磁盘空间):
int fd = open("large_file.dat", O_CREAT | O_WRONLY, 0644);
lseek(fd, 1024 * 1024 * 1024 - 1, SEEK_SET); // 定位到1GB偏移处(最后一个字节)
write(fd, "", 1); // 写入一个字节,文件大小变为1GB
close(fd);
三、lseek
函数的注意事项
- 返回值检查
- 成功时返回新的文件偏移量,失败时返回
-1
,不能用<0
判断,因为偏移量可能是负的(如SEEK_CUR
或SEEK_END
为基准且offset
为负时)。 - 必须检查返回值以确保操作成功。
- 成功时返回新的文件偏移量,失败时返回
- 文件类型限制
lseek
仅适用于支持随机访问的文件(如普通文件),对管道、套接字等流式文件无效。例如,在管道或设备文件上调用lseek
会失败,返回-1
且errno=ESPIPE
。
- 偏移量溢出风险
- 在32位系统中,
off_t
为4字节,偏移量超过2GB可能导致溢出。64位系统中,off_t
为8字节,可处理更大的文件。
- 在32位系统中,
- 并发与原子性
lseek
是原子操作,多线程环境下无需额外同步,但读写操作仍需考虑线程安全。
- 文件打开模式的影响
- 如果文件以追加模式(
O_APPEND
)打开,lseek
设置的偏移量对write
操作无效,write
仍会追加到文件末尾。
- 如果文件以追加模式(
四、lseek
与read
函数的关系
- 协同工作
lseek
用于调整文件的读写位置,read
用于从当前位置读取数据。例如:
int fd = open("file.txt", O_RDONLY);
lseek(fd, 100, SEEK_SET); // 定位到第100字节
char buf[10];
read(fd, buf, sizeof(buf)); // 从第100字节开始读取10字节
close(fd);
- 文件偏移量的更新
read
函数会更新文件的当前偏移量,而lseek
可以显式地修改偏移量。两者共同决定了下一次读写操作的位置。
- 性能优化
在需要随机访问文件时,lseek
可以避免不必要的顺序读取,提高效率。例如,在数据库文件中,通过lseek
直接定位到指定记录的起始位置进行读写操作。
- 错误处理的关联性
如果lseek
调用失败,后续的read
操作可能会读取到错误的数据或返回错误。因此,必须在使用lseek
后检查其返回值。