学习如何用C语言来进行文件输入输出操作

时间:2021-05-02

理解 I/O 有助于提升你的效率。

如果你打算学习 C 语言的输入、输出,可以从stdio.h包含文件开始。正如你从其名字中猜到的,该文件定义了所有的标准(“std”)的输入和输出(“io”)函数。

大多数人学习的第一个stdio.h的函数是打印格式化输出的printf函数。或者是用来打印一个字符串的puts函数。这些函数非常有用,可以将信息打印给用户,但是如果你想做更多的事情,则需要了解其他函数。

你可以通过编写一个常见 Linux 命令的副本来了解其中一些功能和方法。cp命令主要用于复制文件。如果你查看cp的帮助手册,可以看到cp命令支持非常多的参数和选项。但最简单的功能,就是复制文件:

  • cp infile outfile
  • 你只需使用一些读写文件的基本函数,就可以用 C 语言来自己实现cp命令。

    一次读写一个字符

    你可以使用fgetc和fputc函数轻松地进行输入输出。这些函数一次只读写一个字符。该用法被定义在stdio.h,并且这也很浅显易懂:fgetc是从文件中读取一个字符,fputc是将一个字符保存到文件中。

  • int fgetc(FILE *stream);
  • int fputc(int c, FILE *stream);
  • 编写cp命令需要访问文件。在 C 语言中,你使用fopen函数打开一个文件,该函数需要两个参数:文件名和打开文件的模式。模式通常是从文件读取(r)或向文件写入(w)。打开文件的方式也有其他选项,但是对于本教程而言,仅关注于读写操作。

    因此,将一个文件复制到另一个文件就变成了打开源文件和目标文件,接着,不断从第一个文件读取字符,然后将该字符写入第二个文件。fgetc函数返回从输入文件中读取的单个字符,或者当文件完成后返回文件结束标记(EOF)。一旦读取到EOF,你就完成了复制操作,就可以关闭两个文件。该代码如下所示:

  • do {
  • ch = fgetc(infile);
  • if (ch != EOF) {
  • fputc(ch, outfile);
  • }
  • } while (ch != EOF);
  • 你可以使用此循环编写自己的cp程序,以使用fgetc和fputc函数一次读写一个字符。cp.c源代码如下所示:

  • #include <stdio.h>
  • int
  • main(int argc, char **argv)
  • {
  • FILE *infile;
  • FILE *outfile;
  • int ch;
  • if (argc != 3) {
  • fprintf(stderr, "Incorrect usage\n");
  • fprintf(stderr, "Usage: cp infile outfile\n");
  • return 1;
  • }
  • infile = fopen(argv[1], "r");
  • if (infile == NULL) {
  • fprintf(stderr, "Cannot open file for reading: %s\n", argv[1]);
  • return 2;
  • }
  • outfile = fopen(argv[2], "w");
  • if (outfile == NULL) {
  • fprintf(stderr, "Cannot open file for writing: %s\n", argv[2]);
  • fclose(infile);
  • return 3;
  • }
  • do {
  • ch = fgetc(infile);
  • if (ch != EOF) {
  • fputc(ch, outfile);
  • }
  • } while (ch != EOF);
  • fclose(infile);
  • fclose(outfile);
  • return 0;
  • }
  • 你可以使用gcc来将cp.c文件编译成一个可执行文件:

  • $ gcc -Wall -o cp cp.c
  • -o cp选项告诉编译器将编译后的程序保存到cp文件中。-Wall选项告诉编译器提示所有可能的警告,如果你没有看到任何警告,则表示一切正常。

    读写数据块

    通过每次读写一个字符来实现自己的cp命令可以完成这项工作,但这并不是很快。在复制“日常”文件(例如文档和文本文件)时,你可能不会注意到,但是在复制大型文件或通过网络复制文件时,你才会注意到差异。每次处理一个字符需要大量的开销。

    实现此cp命令的一种更好的方法是,读取一块的输入数据到内存中(称为缓存),然后将该数据集合写入到第二个文件。这样做的速度要快得多,因为程序可以一次读取更多的数据,这就就减少了从文件中“读取”的次数。

    你可以使用fread函数将文件读入一个变量中。这个函数有几个参数:将数据读入的数组或内存缓冲区的指针(ptr),要读取的最小对象的大小(size),要读取对象的个数(nmemb),以及要读取的文件(stream):

  • size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 不同的选项为更高级的文件输入和输出(例如,读取和写入具有特定数据结构的文件)提供了很大的灵活性。但是,在从一个文件读取数据并将数据写入另一个文件的简单情况下,可以使用一个由字符数组组成的缓冲区。

    你可以使用fwrite函数将缓冲区中的数据写入到另一个文件。这使用了与fread函数有相似的一组选项:要从中读取数据的数组或内存缓冲区的指针,要读取的最小对象的大小,要读取对象的个数以及要写入的文件。

  • size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 如果程序将文件读入缓冲区,然后将该缓冲区写入另一个文件,则数组(ptr)可以是固定大小的数组。例如,你可以使用长度为 200 个字符的字符数组作为缓冲区。

    在该假设下,你需要更改cp程序中的循环,以将数据从文件读取到缓冲区中,然后将该缓冲区写入另一个文件中:

  • while (!feof(infile)) {
  • buffer_length = fread(buffer, sizeof(char), 200, infile);
  • fwrite(buffer, sizeof(char), buffer_length, outfile);
  • }
  • 这是更新后的cp程序的完整源代码,该程序现在使用缓冲区读取和写入数据:

  • #include <stdio.h>
  • int
  • main(int argc, char **argv)
  • {
  • FILE *infile;
  • FILE *outfile;
  • char buffer[200];
  • size_t buffer_length;
  • if (argc != 3) {
  • fprintf(stderr, "Incorrect usage\n");
  • fprintf(stderr, "Usage: cp infile outfile\n");
  • return 1;
  • }
  • infile = fopen(argv[1], "r");
  • if (infile == NULL) {
  • fprintf(stderr, "Cannot open file for reading: %s\n", argv[1]);
  • return 2;
  • }
  • outfile = fopen(argv[2], "w");
  • if (outfile == NULL) {
  • fprintf(stderr, "Cannot open file for writing: %s\n", argv[2]);
  • fclose(infile);
  • return 3;
  • }
  • while (!feof(infile)) {
  • buffer_length = fread(buffer, sizeof(char), 200, infile);
  • fwrite(buffer, sizeof(char), buffer_length, outfile);
  • }
  • fclose(infile);
  • fclose(outfile);
  • return 0;
  • }
  • 由于你想将此程序与其他程序进行比较,因此请将此源代码另存为cp2.c。你可以使用gcc编译程序:

  • $ gcc -Wall -o cp2 cp2.c
  • 和之前一样,-o cp2选项告诉编译器将编译后的程序保存到cp2程序文件中。-Wall选项告诉编译器打开所有警告。如果你没有看到任何警告,则表示一切正常。

    是的,这真的更快了

    使用缓冲区读取和写入数据是实现此版本cp程序更好的方法。由于它可以一次将文件的多个数据读取到内存中,因此该程序不需要频繁读取数据。在小文件中,你可能没有注意到使用这两种方案的区别,但是如果你需要复制大文件,或者在较慢的介质(例如通过网络连接)上复制数据时,会发现明显的差距。

    我使用 Linuxtime命令进行了比较。此命令可以运行另一个程序,然后告诉你该程序花费了多长时间。对于我的测试,我希望了解所花费时间的差距,因此我复制了系统上的 628 MB CD-ROM 镜像文件。

    我首先使用标准的 Linux 的cp命令复制了映像文件,以查看所需多长时间。一开始通过运行 Linux 的cp命令,同时我还避免使用 Linux 内置的文件缓存系统,使其不会给程序带来误导性能提升的可能性。使用 Linuxcp进行的测试,总计花费不到一秒钟的时间:

  • $ time cp FD13LIVE.iso tmpfile
  • real 0m0.040s
  • user 0m0.001s
  • sys 0m0.003s
  • 运行我自己实现的cp命令版本,复制同一文件要花费更长的时间。每次读写一个字符则花了将近五秒钟来复制文件:

  • $ time ./cp FD13LIVE.iso tmpfile
  • real 0m4.823s
  • user 0m4.100s
  • sys 0m0.571s
  • 从输入读取数据到缓冲区,然后将该缓冲区写入输出文件则要快得多。使用此方法复制文件花不到一秒钟:

  • $ time ./cp2 FD13LIVE.iso tmpfile
  • real 0m0.944s
  • user 0m0.224s
  • sys 0m0.608s
  • 我演示的cp程序使用了 200 个字符大小的缓冲区。我确信如果一次将更多文件数据读入内存,该程序将运行得更快。但是,通过这种比较,即使只有 200 个字符的缓冲区,你也已经看到了性能上的巨大差异。

    原文地址:https://linux.cn/article-13252-1.html

    声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

    相关文章