6

考虑以下用于将文件内容读入缓冲区的代码

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

问题:

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

样本输出

bytes_read=6

buff[0]=H

buff[1]=e

buff[2]=l

buff[3]=l

buff[4]=o

buff[5]=

PS:输入文件是用户创建的文件,不是由程序创建的(使用writeAPI)。只是在这里提一下,以防万一。

4

4 回答 4

7

由于您要读取整个文件,因此最好的方法是使缓冲区与文件大小一样大。在你去的时候调整缓冲区的大小是没有意义的。这只会在没有充分理由的情况下损害性能。

您可以通过多种方式获取文件大小。快速而肮脏的方法是到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(),您可以将文件的一部分映射到内存。这样,文件有多大并不重要,因为您一次只能处理其中的一部分,从而控制内存使用。

于 2012-11-10T13:58:25.217 回答
3

1、你可以用stat(filename, &stat)得到文件大小,但是把缓冲区定义为页面大小就可以了

2、首先,“Hello”后面没有NULL字符,一定是你分配的栈区在你的代码执行前是0,请参考APUE 7.6章节。事实上,你必须在使用它之前初始化局部变量。

我尝试使用 vim、emacs 和 echo -n Hello > file-to-buff.txt 生成文本文件,只有 vim 自动添加换行符

于 2012-11-10T15:42:33.983 回答
2

您可以考虑通过首先创建一个固定大小的缓冲区来动态分配缓冲区,然后在填充它时将大小malloc加倍(使用)。realloc这将具有良好的时间复杂度和空间折衷。

目前你反复读入同一个缓冲区。您应该在每次读取后增加缓冲区中的点,否则您将使用文件的下一部分覆盖缓冲区内容。

您提供的代码为缓冲区分配 50 个字节,但您将 4096 作为大小传递给read. 这可能导致大小超过 50 字节的任何文件的缓冲区溢出。

至于'\n'和'\0'。换行符可能在文件中,而 '\0' 已经在缓冲区中。缓冲区在代码中的堆栈上分配,如果堆栈的该部分尚未使用,它可能包含零,在加载程序时由操作系统放置在那里。

操作系统不会尝试终止从文件中读取的数据,它可能是二进制数据或它不理解的字符集。如果需要,终止字符串由您决定。

其他一些更与风格有关的观点:

  • 您可以考虑在最后打印输出使用for (i = 0; buff[i]; ++i)循环而不是一段时间。这样,如果有人弄乱了索引变量i,您将不受影响。
  • 您可以在完成读取文件后提前关闭文件,以避免文件长时间打开(如果发生某种错误,可能会忘记关闭它)。
于 2012-11-10T13:15:57.903 回答
1

对于第二个问题,read不要自动添加 character '\0'。如果您认为您的文件是文本文件,则必须'\0'在调用后添加一个read,用于指示字符串的结尾。

在 C 中,字符串的结尾由这个字符表示。如果read设置了 4 个字符,printf将读取这 4 个字符,并测试第 5 个:如果不是'\0',则继续打印直到下一个'\0'。它也是缓冲区溢出的来源

对于'\n',它可能在输入文件中。

于 2012-11-10T13:40:26.100 回答