Linux中國

學習如何用 C 語言來進行文件輸入輸出操作

如果你打算學習 C 語言的輸入、輸出,可以從 stdio.h 包含文件開始。正如你從其名字中猜到的,該文件定義了所有的標準(「std」)的輸入和輸出(「io」)函數。

大多數人學習的第一個 stdio.h 的函數是列印格式化輸出的 printf 函數。或者是用來列印一個字元串的 puts 函數。這些函數非常有用,可以將信息列印給用戶,但是如果你想做更多的事情,則需要了解其他函數。

你可以通過編寫一個常見 Linux 命令的副本來了解其中一些功能和方法。cp 命令主要用於複製文件。如果你查看 cp 的幫助手冊,可以看到 cp 命令支持非常多的參數和選項。但最簡單的功能,就是複製文件:

cp infile outfile

你只需使用一些讀寫文件的基本函數,就可以用 C 語言來自己實現 cp 命令。

一次讀寫一個字元

你可以使用 fgetcfputc 函數輕鬆地進行輸入輸出。這些函數一次只讀寫一個字元。該用法被定義在 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 程序,以使用 fgetcfputc 函數一次讀寫一個字元。cp.c 源代碼如下所示:

#include <stdio.h>

int
main(int argc, char **argv)
{
  FILE *infile;
  FILE *outfile;
  int ch;

  /* parse the command line */

  /* usage: cp infile outfile */

  if (argc != 3) {
    fprintf(stderr, "Incorrect usagen");
    fprintf(stderr, "Usage: cp infile outfilen");
    return 1;
  }

  /* open the input file */

  infile = fopen(argv[1], "r");
  if (infile == NULL) {
    fprintf(stderr, "Cannot open file for reading: %sn", argv[1]);
    return 2;
  }

  /* open the output file */

  outfile = fopen(argv[2], "w");
  if (outfile == NULL) {
    fprintf(stderr, "Cannot open file for writing: %sn", argv[2]);
    fclose(infile);
    return 3;
  }

  /* copy one file to the other */

  /* use fgetc and fputc */

  do {
    ch = fgetc(infile);
    if (ch != EOF) {
      fputc(ch, outfile);
    }
  } while (ch != EOF);

  /* done */

  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;

  /* parse the command line */

  /* usage: cp infile outfile */

  if (argc != 3) {
    fprintf(stderr, "Incorrect usagen");
    fprintf(stderr, "Usage: cp infile outfilen");
    return 1;
  }

  /* open the input file */

  infile = fopen(argv[1], "r");
  if (infile == NULL) {
    fprintf(stderr, "Cannot open file for reading: %sn", argv[1]);
    return 2;
  }

  /* open the output file */

  outfile = fopen(argv[2], "w");
  if (outfile == NULL) {
    fprintf(stderr, "Cannot open file for writing: %sn", argv[2]);
    fclose(infile);
    return 3;
  }

  /* copy one file to the other */

  /* use fread and fwrite */

  while (!feof(infile)) {
    buffer_length = fread(buffer, sizeof(char), 200, infile);
    fwrite(buffer, sizeof(char), buffer_length, outfile);
  }

  /* done */

  fclose(infile);
  fclose(outfile);

  return 0;
}

由於你想將此程序與其他程序進行比較,因此請將此源代碼另存為 cp2.c。你可以使用 gcc 編譯程序:

$ gcc -Wall -o cp2 cp2.c

和之前一樣,-o cp2 選項告訴編譯器將編譯後的程序保存到 cp2 程序文件中。-Wall 選項告訴編譯器打開所有警告。如果你沒有看到任何警告,則表示一切正常。

是的,這真的更快了

使用緩衝區讀取和寫入數據是實現此版本 cp 程序更好的方法。由於它可以一次將文件的多個數據讀取到內存中,因此該程序不需要頻繁讀取數據。在小文件中,你可能沒有注意到使用這兩種方案的區別,但是如果你需要複製大文件,或者在較慢的介質(例如通過網路連接)上複製數據時,會發現明顯的差距。

我使用 Linux time 命令進行了比較。此命令可以運行另一個程序,然後告訴你該程序花費了多長時間。對於我的測試,我希望了解所花費時間的差距,因此我複製了系統上的 628 MB CD-ROM 鏡像文件。

我首先使用標準的 Linux 的 cp 命令複製了映像文件,以查看所需多長時間。一開始通過運行 Linux 的 cp 命令,同時我還避免使用 Linux 內置的文件緩存系統,使其不會給程序帶來誤導性能提升的可能性。使用 Linux cp 進行的測試,總計花費不到一秒鐘的時間:

$ 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 個字元的緩衝區,你也已經看到了性能上的巨大差異。

via: https://opensource.com/article/21/3/file-io-c

作者:Jim Hall 選題:lujun9972 譯者:wyxplus 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國