使用 POSIX API 读取文件
Posted
技术标签:
【中文标题】使用 POSIX API 读取文件【英文标题】:File read using POSIX API's 【发布时间】:2012-10-30 14:42:13 【问题描述】:考虑以下将文件内容读入缓冲区的代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define BLOCK_SIZE 4096
int main()
int fd=-1;
ssize_t bytes_read=-1;
int i=0;
char buff[50];
//Arbitary size for the buffer?? How to optimise.
//Dynamic allocation is a choice but what is the
//right way to relate the file size to bufffer size.
fd=open("./file-to-buff.txt",O_RDONLY);
if(-1 == fd)
perror("Open Failed");
return 1;
while((bytes_read=read(fd,buff,BLOCK_SIZE))>0)
printf("bytes_read=%d\n",bytes_read);
//Test to characters read from the file to buffer.The file contains "Hello"
while(buff[i]!='\0')
printf("buff[%d]=%d\n",i,buff[i]);
i++;
//buff[5]=\n-How?
//buff[6]=`\0`-How?
close(fd);
return 0;
代码说明:
输入文件包含字符串“Hello” 需要将此内容复制到缓冲区中。open
和 read
POSIX API 实现了目标。
读取 API 使用指向*任意大小*缓冲区的指针来复制数据。
问题:
动态分配是必须用于优化缓冲区大小的方法。从输入文件大小关联/导出缓冲区大小的正确过程是什么? 我看到在read
操作结束时,除了字符“Hello” 之外,读取还复制了new line character
和NULL
字符。请详细说明这种读取行为。
样本输出
bytes_read=6
buff[0]=H
buff[1]=e
buff[2]=l
buff[3]=l
buff[4]=o
buff[5]=
PS:输入文件是用户创建的文件,不是由程序创建的(使用write
API)。在这里提一下,以防万一。
【问题讨论】:
没有NULL
字符。您可能指的是字符NUL
(又名'\0'
),它与整数0
相同。
阅读关于 1 个“换行符”(又名 LF
、'\n'
)、“回车”(又名 CR
、'\r'
)和 2 个“0-终止”字符串C.
是的,我的意思是buff[6]
有\0
一开始我用echo Hello > file-to-buff.txt
生成测试文件,发现最后加了一个'\n',然后我用vi新建了一个上下文为“Hello”的文件,还有一个'\n'太奇怪了!
@Vivek27:我发现vim会automatically在文件末尾添加'\n'。如果我们使用echo -n Hello > file-to-buff.txt
,则不会有'\n'文件结束。
【参考方案1】:
您可以考虑动态分配缓冲区,方法是首先使用malloc
创建一个固定大小的缓冲区,然后在填满它时将大小加倍(使用realloc
)。这将具有良好的时间复杂度和空间折衷。
此时您反复读入同一个缓冲区。您应该在每次读取后增加缓冲区中的点,否则您将使用文件的下一部分覆盖缓冲区内容。
您提供的代码为缓冲区分配 50 个字节,但您将 4096 作为大小传递给 read
。这可能导致大小超过 50 字节的任何文件的缓冲区溢出。
至于“\n”和“\0”。换行符可能在文件中,而 '\0' 已经在缓冲区中。缓冲区是在代码中的堆栈上分配的,如果堆栈的该部分尚未使用,它可能包含零,在加载程序时由操作系统放置在那里。
操作系统不会尝试终止从文件中读取的数据,它可能是二进制数据或它不理解的字符集。如果需要,终止字符串由您决定。
其他一些更与风格有关的观点:
您可以考虑使用for (i = 0; buff[i]; ++i)
循环而不是while 来打印最后。这样,如果有人弄乱了索引变量i
,您将不受影响。
您可以在完成读取文件后提前关闭文件,以避免文件长时间打开(如果发生某种错误,可能会忘记关闭它)。
【讨论】:
+1 表示read(fd, buff, BLOCK_SIZE)
不好。当缓冲区是本地的(如此处)时,使用read(fd, buff, sizeof(buff))
;当缓冲区作为参数提供时(function(..., char *buffer, size_t buflen, ...)
,使用指定的buflen
大小。如果函数接口没有明确指定缓冲区的大小,则必须找出隐式缓冲区大小是多少,即可能很棘手。最好写一个显式接口。【参考方案2】:
对于第二个问题,read
不要自动添加字符 '\0'
。
如果你认为你的文件是文本文件,你必须在调用read
之后添加一个'\0'
,用于表示字符串的结尾。
在 C 中,字符串的结尾由这个字符表示。如果read
设置了4个字符,printf
会读取这4个字符,并会测试第5个:如果不是'\0'
,会继续打印到下一个'\0'
。
也是缓冲区溢出的来源
对于'\n'
,它可能在输入文件中。
【讨论】:
【参考方案3】:由于您要读取整个文件,因此最好的方法是使缓冲区与文件大小一样大。在你去的时候调整缓冲区的大小是没有意义的。这只会无缘无故地损害性能。
您可以通过多种方式获取文件大小。又快又脏的方法是lseek()
到文件末尾:
// Get size.
off_t size = lseek(fd, 0, SEEK_END); // You should check for an error return in real code
// Seek back to the beginning.
lseek(fd, 0, SEEK_SET);
// Allocate enough to hold the whole contents plus a '\0' char.
char *buff = malloc(size + 1);
另一种方式是使用fstat()
获取信息:
struct stat fileStat;
fstat(fd, &fileStat); // Don't forget to check for an error return in real code
// Allocate enough to hold the whole contents plus a '\0' char.
char *buff = malloc(fileStat.st_size + 1);
要获取所有需要的类型和函数原型,请确保包含所需的标头:
#include <sys/stat.h> // For fstat()
#include <unistd.h> // For lseek()
请注意,read()
不会自动终止带有\0
的数据。您需要手动执行此操作,这就是我们为缓冲区分配一个额外字符 (size+1) 的原因。在您的情况下,那里已经有一个 \0
字符的原因是纯粹的随机机会。
当然,既然buf
现在是一个动态分配的数组,当你不再需要它的时候别忘了再释放它:
free(buff);
但请注意,分配与您要读入的文件一样大的缓冲区可能很危险。想象一下,如果(错误地或故意地,无关紧要)文件有几 GB 大。对于这种情况,最好设置一个最大允许大小。但是,如果您不想要任何此类限制,那么您应该切换到另一种读取文件的方法:mmap()
。使用mmap()
,您可以将文件的一部分映射到内存。这样一来,文件的大小就无关紧要了,因为您一次只能处理其中的一部分,从而控制内存使用。
【讨论】:
这些天你很少需要<sys/types.h>
。它曾经是至关重要的,但在这个千年中,如果他们需要来自它的信息,主要的标题会确保包含<sys/types.h>
。
@JonathanLeffler 手册页提供了该信息。我对此变得非常偏执,因为 GCC 版本 4.4(或者是 4.5?)开始清理它们的包含链并且大量应用程序停止编译(我使用的是基于源的 Linux 发行版,所以我觉得.) 他们假设一个标题会自动包含在另一个标题中,但情况已不再如此。在那之后,如果文档说正确的包含是什么,我会按照他们的信来:-)
查看POSIX 2008 规范。查找lseek
;它说#include <unistd.h>
。查找fstat()
;它说#include <sys/stat.h>
。我不知道 POSIX 2008 中需要 <sys/types.h>
的函数条目;这并不是说没有,但它不是我看过的。 POSIX (1997) 不同:lseek()
。
@JonathanLeffler 我正在查看man 2 fstat
,这是 Linux 联机帮助页。 man 3p fstat
是 POSIX 手册页,实际上它只列出了 <sys/stat.h>
。不知道为什么 Linux 手册页不同意。
谢谢尼科斯!!它有助于。只是一个小建议,我相信lseek
将返回off_t
类型的返回【参考方案4】:
1、你可以用stat(filename, &stat)来获取文件大小,但是把缓冲区定义为页面大小就可以了
2、首先,“Hello”后面没有NULL字符,一定是你分配的栈区在你的代码执行前是0,请参考APUE 7.6章节。实际上你必须在使用它之前初始化局部变量。
我尝试用vim、emacs和echo -n Hello > file-to-buff.txt生成文本文件,只有vim自动添加换行符
【讨论】:
那是因为vim
主要编辑文本文件,文本文件以换行符结尾。 C 标准规定,如果文件是文本文件(但如果是二进制文件),库可以删除文件中最后一个换行符之后的字符。以上是关于使用 POSIX API 读取文件的主要内容,如果未能解决你的问题,请参考以下文章
posix_fadvise(WILLNEED) 使 IO 变慢?
读取文件夹中的所有文件以及python中的文件名? [复制]