使用 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” 需要将此内容复制到缓冲区中。 openread POSIX API 实现了目标。 读取 API 使用指向*任意大小*缓冲区的指针来复制数据。

问题:

动态分配是必须用于优化缓冲区大小的方法。从输入文件大小关联/导出缓冲区大小的正确过程是什么? 我看到在read 操作结束时,除了字符“Hello” 之外,读取还复制了new line characterNULL 字符。请详细说明这种读取行为。

样本输出

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 &gt; file-to-buff.txt生成测试文件,发现最后加了一个'\n',然后我用vi新建了一个上下文为“Hello”的文件,还有一个'\n'太奇怪了! @Vivek27:我发现vim会automatically在文件末尾添加'\n'。如果我们使用echo -n Hello &gt; 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(),您可以将文件的一部分映射到内存。这样一来,文件的大小就无关紧要了,因为您一次只能处理其中的一部分,从而控制内存使用。

【讨论】:

这些天你很少需要&lt;sys/types.h&gt;。它曾经是至关重要的,但在这个千年中,如果他们需要来自它的信息,主要的标题会确保包含&lt;sys/types.h&gt; @JonathanLeffler 手册页提供了该信息。我对此变得非常偏执,因为 GCC 版本 4.4(或者是 4.5?)开始清理它们的包含链并且大量应用程序停止编译(我使用的是基于源的 Linux 发行版,所以我觉得.) 他们假设一个标题会自动包含在另一个标题中,但情况已不再如此。在那之后,如果文档说正确的包含是什么,我会按照他们的信来:-) 查看POSIX 2008 规范。查找lseek;它说#include &lt;unistd.h&gt;。查找fstat();它说#include &lt;sys/stat.h&gt;。我不知道 POSIX 2008 中需要 &lt;sys/types.h&gt; 的函数条目;这并不是说没有,但它不是我看过的。 POSIX (1997) 不同:lseek() @JonathanLeffler 我正在查看man 2 fstat,这是 Linux 联机帮助页。 man 3p fstat 是 POSIX 手册页,实际上它只列出了 &lt;sys/stat.h&gt;。不知道为什么 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中的文件名? [复制]

POSIX 共享内存写入/读取

使用 Javascript FileReader API 一次读取多个文件

用于在 Java 中读取 IFC 文件的开源 API

linux进程间通信之Posix消息队列