关于C#:使用POSIX API读取文件

File read using POSIX API's

考虑下面的代码片段,用于将文件内容读入缓冲区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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
"
,bytes_read);
   }

   //Test to characters read from the file to buffer.The file contains"Hello"
   while(buff[i]!='\0')
   {
      printf("buff[%d]=%d
"
,i,buff[i]);
      i++;
      //buff[5]=
-How?
   }
   //buff[6]=`\0`-How?
   close(fd);
   return 0;
}

代码说明:

  • 输入文件包含字符串"Hello"
  • 需要将此内容复制到缓冲区中。
  • 目标是通过openread POSIX API实现的。
  • 读取API使用指向*仲裁大小*的缓冲区的指针来复制数据。

问题:

  • 动态分配是必须用于优化缓冲区大小的方法。从输入文件大小关联/派生缓冲区大小的正确过程是什么?
  • 我在read操作结束时看到,除了字符"Hello"之外,read还复制了一个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)。在这里提一下,如果它有任何区别。


由于您想要读取整个文件,最好的方法是使缓冲区与文件大小一样大。在你去的时候调整缓冲区是没有意义的。这只是没有充分理由伤害表现。

您可以通过多种方式获取文件大小。快速而肮脏的方式是lseek()到文件的末尾:

1
2
3
4
5
6
// 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()获取信息:

1
2
3
4
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);

要获得所有需要的类型和函数原型,请确保包含所需的标头:

1
2
#include <sys/stat.h> // For fstat()
#include <unistd.h>   // For lseek()

请注意,read()不会自动使用\0终止数据。您需要手动执行此操作,这就是我们为缓冲区分配额外字符(大小+ 1)的原因。在你的案例中已经存在\0字符的原因是纯随机机会。

当然,由于buf现在是一个动态分配的数组,所以当你不再需要时,不要忘记再次释放它:

1
free(buff);

但请注意,分配一个与您想要读入的文件一样大的缓冲区可能很危险。想象一下,如果(错误或故意,无所谓)文件是几GB大。对于这样的情况,最好允许最大允许尺寸。但是,如果您不想要任何此类限制,则应切换到另一种从文件中读取的方法:mmap()。使用mmap(),您可以将文件的某些部分映射到内存。这样,文件的大小并不重要,因为您一次只能处理部分文件,从而控制内存使用。


1,你可以用stat(filename,&amp; stat)获取文件大小,但是定义缓冲区到页面大小就好了

2,首先,在"Hello"之后没有NULL字符,在你的代码执行之前你所分配的堆栈区域必须是0,这请参考APUE第7.6章。 实际上,在使用它之前必须初始化局部变量。

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


您可以考虑动态分配缓冲区,方法是先使用malloc创建一个固定大小的缓冲区,然后在填充时加倍(用realloc)大小。这将有一个很好的时间复杂性和空间权衡。

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

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

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

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

其他几点更多的是风格问题:

  • 您可以考虑使用for (i = 0; buff[i]; ++i)循环而不是一段时间来结束打印输出。这样,如果有人混淆索引变量i,你将不会受到影响。
  • 您可以在读完文件后提前关闭该文件,以避免文件长时间打开(如果发生某种错误,可能会忘记关闭它)。


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

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

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