Merge pull request #21450 from wxy/20210315-Learn-how-file-input-and-output-works-in-C

PRF&PUB:20210315 learn how file input and output works in c
This commit is contained in:
Xingyu.Wang 2021-03-30 22:39:21 +08:00 committed by GitHub
commit c30186603c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -3,59 +3,55 @@
[#]: author: (Jim Hall https://opensource.com/users/jim-hall)
[#]: collector: (lujun9972)
[#]: translator: (wyxplus)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: reviewer: (wxy)
[#]: publisher: (wxy)
[#]: url: (https://linux.cn/article-13252-1.html)
学习如何用 C 语言来进行文件输入输出操作
======
理解 I/O 有助于提升你的效率。
> 理解 I/O 有助于提升你的效率。
![4 manilla folders, yellow, green, purple, blue][1]
![](https://img.linux.net.cn/data/attachment/album/202103/30/222717gyuegz88ryu8ry7i.jpg)
如果你打算学习用 C 语言进行输入输出,首先关注 `stdio.h` 包含的文件。你可能从其名字中猜到,该文件定义了所有的标准输入输出函数。
如果你打算学习 C 语言的输入、输出,可以从 `stdio.h` 包含文件开始。正如你从其名字中猜到的该文件定义了所有的标准“std”的输入和输出“io”函数。
大多数人学习的第一个 `stdio.h` 的函数是 `printf` 函数,用于打印格式化输出。或者使用 `puts` 函数来打印一个字符串。这些函数非常有用,可以将信息打印给用户,但是如果你想做更多的事情,则需要了解其他函数。
你可以通过编写常见 Linux 命令的副本来了解其中一些功能和方法。`cp` 命令主要用于复制文件。如果你查看 `cp` 的帮助手册,可以看到 `cp` 命令支持非常多的参数和选项。但最简单的功能,就是复制文件:
大多数人学习的第一个 `stdio.h` 的函数是打印格式化输出的 `printf` 函数。或者是用来打印一个字符串的 `puts` 函数。这些函数非常有用,可以将信息打印给用户,但是如果你想做更多的事情,则需要了解其他函数。
你可以通过编写一个常见 Linux 命令的副本来了解其中一些功能和方法。`cp` 命令主要用于复制文件。如果你查看 `cp` 的帮助手册,可以看到 `cp` 命令支持非常多的参数和选项。但最简单的功能,就是复制文件:
```
`cp infile outfile`
cp infile outfile
```
你只需使用一些读写文件的基本函数,就可以用 C 语言来自己实现 `cp` 命令。
你只需使用一些读写文件的基本函数,就可以用 C 语言来自己实现 `cp` 命令。
### 同时读写一个字符
你可以使用 `fgetc``fputc` 函数轻松地进行输入输出。这些函数一次读写一个字符。该用法被定义在 `stdio.h`,并且这也很浅显易懂:`fgetc` 是从文件中读取一个字符,`fputc` 是将一个字符保存到文件中。
### 一次读写一个字符
你可以使用 `fgetc``fputc` 函数轻松地进行输入输出。这些函数一次只读写一个字符。该用法被定义在 `stdio.h`,并且这也很浅显易懂:`fgetc` 是从文件中读取一个字符,`fputc` 是将一个字符保存到文件中。
```
int [fgetc][2](FILE *stream);
int [fputc][3](int c, FILE *stream);
int fgetc(FILE *stream);
int fputc(int c, FILE *stream);
```
编写 `cp` 命令需要访问文件。在 C 语言中,你使用 `fopen` 函数打开一个文件,该函数带有两个参数:文件名和打开文件的方式。该方式通常是从文件读取 `r` 或向文件写入 `w`。打开文件的方式也有其他选项,但是对于本教程而言,仅关注于读写操作。
因此,将一个文件复制到另一个文件就变成了打开源文件和目标文件的问题,接着,不断从第一个文件读取字符,然后将该字符写入第二个文件。`fgetc` 函数返回从输入文件中读取的单个字符,或者返回文件完成后的(`EOF`)标记。一旦遇到 `EOF`,你就完成了复制操作,可以关闭两个文件。该代码如下所示:
编写 `cp` 命令需要访问文件。在 C 语言中,你使用 `fopen` 函数打开一个文件,该函数需要两个参数:文件名和打开文件的模式。模式通常是从文件读取(`r`)或向文件写入(`w`)。打开文件的方式也有其他选项,但是对于本教程而言,仅关注于读写操作。
因此,将一个文件复制到另一个文件就变成了打开源文件和目标文件,接着,不断从第一个文件读取字符,然后将该字符写入第二个文件。`fgetc` 函数返回从输入文件中读取的单个字符,或者当文件完成后返回文件结束标记(`EOF`)。一旦读取到 `EOF`,你就完成了复制操作,就可以关闭两个文件。该代码如下所示:
```
do {
ch = [fgetc][2](infile);
ch = fgetc(infile);
if (ch != EOF) {
[fputc][3](ch, outfile);
fputc(ch, outfile);
}
} while (ch != EOF);
```
你可以使用此循环编写自己的`cp`程序,以使用`fgetc`和`fputc`函数一次读取和写入一个字符。`cp.c` 源代码如下所示:
你可以使用此循环编写自己的 `cp` 程序,以使用 `fgetc``fputc` 函数一次读写一个字符。`cp.c` 源代码如下所示:
```
#include <stdio.h>
#include <stdio.h>
int
main(int argc, char **argv)
@ -69,25 +65,25 @@ main(int argc, char **argv)
/* usage: cp infile outfile */
if (argc != 3) {
[fprintf][4](stderr, "Incorrect usage\n");
[fprintf][4](stderr, "Usage: cp infile outfile\n");
fprintf(stderr, "Incorrect usage\n");
fprintf(stderr, "Usage: cp infile outfile\n");
return 1;
}
/* open the input file */
infile = [fopen][5](argv[1], "r");
infile = fopen(argv[1], "r");
if (infile == NULL) {
[fprintf][4](stderr, "Cannot open file for reading: %s\n", argv[1]);
fprintf(stderr, "Cannot open file for reading: %s\n", argv[1]);
return 2;
}
/* open the output file */
outfile = [fopen][5](argv[2], "w");
outfile = fopen(argv[2], "w");
if (outfile == NULL) {
[fprintf][4](stderr, "Cannot open file for writing: %s\n", argv[2]);
[fclose][6](infile);
fprintf(stderr, "Cannot open file for writing: %s\n", argv[2]);
fclose(infile);
return 3;
}
@ -96,71 +92,64 @@ main(int argc, char **argv)
/* use fgetc and fputc */
do {
ch = [fgetc][2](infile);
ch = fgetc(infile);
if (ch != EOF) {
[fputc][3](ch, outfile);
fputc(ch, outfile);
}
} while (ch != EOF);
/* done */
[fclose][6](infile);
[fclose][6](outfile);
fclose(infile);
fclose(outfile);
return 0;
}
```
你可以使用 GCC 来将 `cp.c` 文件编译成一个可执行文件:
你可以使用 `gcc` 来将 `cp.c` 文件编译成一个可执行文件:
```
`$ gcc -Wall -o cp cp.c`
$ gcc -Wall -o cp cp.c
```
`-o cp` 选项告诉编译器将编译后的程序保存到 `cp` 文件中。` -Wall` 选项告诉编译器提示所有可能的警告,如果你没有看到任何警告,则表示一切正常。
`-o cp` 选项告诉编译器将编译后的程序保存到 `cp` 文件中。`-Wall` 选项告诉编译器提示所有可能的警告,如果你没有看到任何警告,则表示一切正常。
### 读写数据块
通过每次读写一个字符来实现自己的 `cp` 命令可以完成这项工作,但这并不是很快。在复制“日常”文件(例如文档和文本文件)时,你可能不会注意到,但是在复制大型文件或通过网络复制文件时,你才会注意到差异。每次处理一个字符需要大量的开销。
实现此 `cp` 命令的一种更好的方法是,将输入的一部分读取到内存中(称为缓存),然后将该数据集合写入第二个文件。因为程序可以一次读取更多的数据,所以减少了文件读取次数,因此速度更快。
你可以使用 `fread` 函数将文件读入内存中。这个函数有几个参数:将数据读入的数组或内存缓冲区的指针(`ptr`),要读取的最小对象的大小(`size`),要读取对象的个数(`nmemb`),以及要从输入流(`stream`)读取的文件:
实现此 `cp` 命令的一种更好的方法是,读取一块的输入数据到内存中(称为缓存),然后将该数据集合写入到第二个文件。这样做的速度要快得多,因为程序可以一次读取更多的数据,这就就减少了从文件中“读取”的次数。
你可以使用 `fread` 函数将文件读入一个变量中。这个函数有几个参数:将数据读入的数组或内存缓冲区的指针(`ptr`),要读取的最小对象的大小(`size`),要读取对象的个数(`nmemb`),以及要读取的文件(`stream`
```
`size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);`
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
```
不同的选项为更高级的文件输入和输出(例如,读取和写入具有特定数据结构的文件)提供了很大的灵活性。但是,在从一个文件读取数据并将数据写入另一个文件的简单情况下,可以使用一个由字符数组组成的缓冲区。
你可以使用 `fwrite` 函数将缓冲区中数据写入到另一个文件。这使用了与 `fread` 函数有相似的一组选项:要从中读取数据的数组或内存缓冲区的指针,要读取的最小对象的大小,要读取对象的个数以及要写入的文件。
你可以使用 `fwrite` 函数将缓冲区中的数据写入到另一个文件。这使用了与 `fread` 函数有相似的一组选项:要从中读取数据的数组或内存缓冲区的指针,要读取的最小对象的大小,要读取对象的个数以及要写入的文件。
```
`size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);`
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
```
如果程序将文件读入缓冲区,然后将该缓冲区写入另一个文件,则数组(`ptr`)可以是固定大小的数组。例如,你可以使用长度为 200 个字符的字符数组作为缓冲区。
在该假设下,你需要更改 `cp` 程序中的循环,以将数据从文件读取到缓冲区中,然后将该缓冲区写入另一个文件中:
```
while (![feof][7](infile)) {
buffer_length = [fread][8](buffer, sizeof(char), 200, infile);
[fwrite][9](buffer, sizeof(char), buffer_length, outfile);
while (!feof(infile)) {
buffer_length = fread(buffer, sizeof(char), 200, infile);
fwrite(buffer, sizeof(char), buffer_length, outfile);
}
```
这是更新后的 `cp` 程序的完整源代码,该程序现在使用缓冲区读取和写入数据:
```
#include &lt;stdio.h&gt;
#include <stdio.h>
int
main(int argc, char **argv)
@ -175,25 +164,25 @@ main(int argc, char **argv)
/* usage: cp infile outfile */
if (argc != 3) {
[fprintf][4](stderr, "Incorrect usage\n");
[fprintf][4](stderr, "Usage: cp infile outfile\n");
fprintf(stderr, "Incorrect usage\n");
fprintf(stderr, "Usage: cp infile outfile\n");
return 1;
}
/* open the input file */
infile = [fopen][5](argv[1], "r");
infile = fopen(argv[1], "r");
if (infile == NULL) {
[fprintf][4](stderr, "Cannot open file for reading: %s\n", argv[1]);
fprintf(stderr, "Cannot open file for reading: %s\n", argv[1]);
return 2;
}
/* open the output file */
outfile = [fopen][5](argv[2], "w");
outfile = fopen(argv[2], "w");
if (outfile == NULL) {
[fprintf][4](stderr, "Cannot open file for writing: %s\n", argv[2]);
[fclose][6](infile);
fprintf(stderr, "Cannot open file for writing: %s\n", argv[2]);
fclose(infile);
return 3;
}
@ -201,25 +190,24 @@ main(int argc, char **argv)
/* use fread and fwrite */
while (![feof][7](infile)) {
buffer_length = [fread][8](buffer, sizeof(char), 200, infile);
[fwrite][9](buffer, sizeof(char), buffer_length, outfile);
while (!feof(infile)) {
buffer_length = fread(buffer, sizeof(char), 200, infile);
fwrite(buffer, sizeof(char), buffer_length, outfile);
}
/* done */
[fclose][6](infile);
[fclose][6](outfile);
fclose(infile);
fclose(outfile);
return 0;
}
```
由于你想将此程序与其他程序进行比较,因此请将此源代码另存为 `cp2.c`。你可以使用 GCC 编译程序:
由于你想将此程序与其他程序进行比较,因此请将此源代码另存为 `cp2.c`。你可以使用 `gcc` 编译程序:
```
`$ gcc -Wall -o cp2 cp2.c`
$ gcc -Wall -o cp2 cp2.c
```
和之前一样,`-o cp2` 选项告诉编译器将编译后的程序保存到 `cp2` 程序文件中。`-Wall` 选项告诉编译器打开所有警告。如果你没有看到任何警告,则表示一切正常。
@ -228,11 +216,10 @@ main(int argc, char **argv)
使用缓冲区读取和写入数据是实现此版本 `cp` 程序更好的方法。由于它可以一次将文件的多个数据读取到内存中,因此该程序不需要频繁读取数据。在小文件中,你可能没有注意到使用这两种方案的区别,但是如果你需要复制大文件,或者在较慢的介质(例如通过网络连接)上复制数据时,会发现明显的差距。
我使用 Linux `time` 命令进行了比较。此命令运行另一个程序,然后告诉你该程序花费了多长时间。对于我的测试,我希望了解所花费时间的差距,因此我复制了系统上的 628 MB CD-ROM 像文件。
我使用 Linux `time` 命令进行了比较。此命令可以运行另一个程序,然后告诉你该程序花费了多长时间。对于我的测试,我希望了解所花费时间的差距,因此我复制了系统上的 628 MB CD-ROM 像文件。
我首先使用标准的 Linux 的 `cp` 命令复制了映像文件,以查看所需多长时间。一开始通过运行 Linux 的 `cp` 命令,同时我还避免使用 Linux 内置的文件缓存系统,使其不会给程序带来误导性能提升的可能性。使用 Linux `cp` 进行的测试,总计花费不到一秒钟的时间:
```
$ time cp FD13LIVE.iso tmpfile
@ -243,7 +230,6 @@ sys 0m0.003s
运行我自己实现的 `cp` 命令版本,复制同一文件要花费更长的时间。每次读写一个字符则花了将近五秒钟来复制文件:
```
$ time ./cp FD13LIVE.iso tmpfile
@ -254,7 +240,6 @@ sys 0m0.571s
从输入读取数据到缓冲区,然后将该缓冲区写入输出文件则要快得多。使用此方法复制文件花不到一秒钟:
```
$ time ./cp2 FD13LIVE.iso tmpfile
@ -272,7 +257,7 @@ via: https://opensource.com/article/21/3/file-io-c
作者:[Jim Hall][a]
选题:[lujun9972][b]
译者:[wyxplus](https://github.com/wyxplus)
校对:[校对者ID](https://github.com/校对者ID)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出