如何正确返回字符数组指针?优化单字节文件读取代码
单字节IO优化:用缓冲区加速文件读取的正确实现
我有一段从文件读取内容的代码,它采用单字节系统调用I/O,速度相当慢:
int readc(io61_file* f) { unsigned char buf[1]; ssize_t nr = read(f->fd, buf, 1); if (nr == 1) { return buf[0]; } else if (nr == 0) { errno = 0; // 清除`errno`表示EOF return -1; } else { assert(nr == -1 && errno > 0); return -1; } }
以下是该函数的使用方式:
// io_read(f, buf, sz) // 从`f`中读取最多`sz`字节到`buf`。成功时返回读取的字节数。 // 如果在读取任何字节前遇到EOF,返回0;如果在读取任何字节前遇到错误,返回-1。 // // 注意:如果在读取完所有`sz`字节前遇到EOF或错误,返回值可能为正但小于`sz`, // 这称为“短读”。 ssize_t io_read(io61_file* f, unsigned char* buf, size_t sz) { size_t nread = 0; while (nread != sz) { int ch = readc(f); if (ch == EOF) { break; } buf[nread] = ch; ++nread; } if (nread != 0 || sz == 0 || errno == 0) { return nread; } else { return -1; } }
我想要加速这段代码,第一步打算将readc函数内部的buf大小增加至10,但尝试了下面的写法后没达到预期效果,请问我哪里出错了?该如何正确实现?
我的尝试代码:
unsigned char* io_readc2(io61_file* f) { static unsigned char buf[1]; ssize_t nr = read(f->fd, buf, 1); if (nr > 0 && nr < 2) { return buf; } else if (nr == 0) { errno = 0; // 清除`errno`表示EOF static unsigned char t[1]; t[0] = -1; return t; } else { assert(nr == -1 && errno > 0); static unsigned char t[1]; t[0] = -1; return t; } }
ssize_t io_read(io61_file* f, unsigned char* buf, size_t sz) { size_t nread = 0; while (nread != sz) { unsigned char* chars = io_readc2(f); int ch = *chars; if (ch == EOF) { break; } buf[nread] = ch; ++nread; } if (nread != 0 || sz == 0 || errno == 0) { return nread; } else { return -1; } }
你的代码为什么没效果?
- 缓冲区大小根本没改:你在
io_readc2里还是用了static unsigned char buf[1],读取长度也还是1,本质还是单字节读取,完全没用到10字节的缓冲区,自然不会有加速效果。 - 返回静态数组的设计不合理:你用多个静态数组返回值,不仅线程不安全,而且每次调用还是只返回一个字节,没有利用缓冲区缓存多个字节来减少系统调用次数——这才是加速的核心。
- EOF判断逻辑有问题:你把
t[0] = -1来表示EOF,但unsigned char的取值范围是0~255,-1会被转换成255,和EOF(通常是-1,int类型)比较时会出问题:*chars是unsigned char,转成int后是255,不等于EOF(-1),导致EOF判断失效。
正确的实现思路:给每个文件句柄关联缓冲区
要实现带缓冲区的单字节读取,核心是给每个io61_file结构体增加缓冲区相关字段(比如缓冲区数组、当前读取位置、缓冲区剩余字节数),这样每次调用readc时,先从缓冲区取字节,缓冲区空了再一次性读取一批(比如10字节)到缓冲区,减少系统调用次数。
修改后的代码实现
首先,修改io61_file结构体(假设原结构体无缓冲区字段):
#define BUF_SIZE 10 typedef struct io61_file { int fd; unsigned char buf[BUF_SIZE]; // 10字节缓冲区 size_t buf_pos; // 当前读取到缓冲区的位置 size_t buf_remaining; // 缓冲区中剩余的未读取字节数 } io61_file;
然后实现带缓冲区的readc函数:
int readc(io61_file* f) { // 如果缓冲区为空,先读取一批数据到缓冲区 if (f->buf_remaining == 0) { ssize_t nr = read(f->fd, f->buf, BUF_SIZE); if (nr == 0) { errno = 0; return -1; // EOF } else if (nr == -1) { assert(errno > 0); return -1; // 错误 } f->buf_pos = 0; f->buf_remaining = nr; } // 从缓冲区取一个字节 unsigned char ch = f->buf[f->buf_pos]; f->buf_pos++; f->buf_remaining--; return ch; }
io_read函数无需修改,因为它调用的readc已经通过缓冲区减少了系统调用次数。
为什么这样能加速?
原来的代码每次readc都调用一次read系统调用,读取1字节;修改后,每调用10次readc才会触发一次read系统调用(读取10字节),系统调用次数减少为原来的1/10——而系统调用是IO操作中耗时的核心部分,因此能显著提升速度。
额外注意事项
- 如果
io61_file是动态创建的,初始化时要把buf_pos和buf_remaining设为0。 - 该实现线程不安全,若要在多线程环境使用,需加锁保护缓冲区字段。
- 可以把
BUF_SIZE设得更大(比如4096,对应系统页大小),加速效果会更明显。
内容的提问来源于stack exchange,提问作者indigo153
相关产品推荐
相关产品推荐

