文件的读写操作

一 、文件读写的常用函数

(1) 读写文本文件:

读文本文件 :一般都使用 getc 、fgets 、fscanf 函数

  • 使用getc读文件

    #include <stdio.h>
    int main()
    {
        char a[20] = "a.txt";
        FILE *p = fopen(a,"r");
        if(p)
        {
            char c;
            while(1)
            {
                c = getc(p);
                if(c == EOF)
                    break;
                printf("%c",c);
            }
            printf("\n");
            fclose(p);
        }
        else
        {
            printf("fail\n");
        }
        return 0;
    }
    
  • 使用fgets读文件

    #include <stdio.h>
    int main()
    {
        FILE *p = fopen("test.txt","r");
        if(p == NULL)
            return 0;
        char temp[1024];
        fgets(temp,sizeof(temp),p);
        while(!feof(p))
        {
            printf("%s",temp);
            fgets(temp,sizeof(temp),p);
        }
        fclose(p);
        return 0;
    }
    
  • 使用fscanf读文件

    #include <stdio.h>
    int main()
    {
        FILE *p = fopen("a.txt", "r");
        if(p == NULL)
        {
            printf("the file is close!");
            return 0;
        }
        char str1[200], str2[200], str3[200];
        int year;
        int status = fscanf(p, "%s %s %s %d", str1, str2, str3, &year); 
        while(!feof(p))
        {
            printf("%s %s %d %d status = %d\n", str1, str2, str3, year, status);
            status = fscanf(p, "%s %s %s %d", str1, str2, str3, &year);
        }
        fclose(p);
        return 0;
    }
    

写文本文件 :一般都使用 putc 、 fputs、fprintf 函数

  • 使用putc写文件

    #include <stdio.h>
    int main()
    {
        FILE *p = fopen("a.txt","w");
        if(p)
        {
            putc('h',p);
            fclose(p);
        }
        return 0;
    }
    
  • 使用fputs写文件

    #include <stdio.h>
    int main()
    {
        FILE *p = fopen("abc.c","w");
        if(p)
        {
            fputs("#include <stdio.h>\nint main()\n{\nprintf(\"hello world\\n\");\nreturn 0;\n}",p);
            fclose(p);
        }
        return 0;
    }
    
  • 使用fprintf写文件

    #include <stdio.h>
    #include <string.h>
    int main()
    {
        FILE *p = fopen("a.txt","w");
        char buf[1024];
        fgets(buf, sizeof(buf), stdin);
        while(strcmp(buf,"exit\n") != 0)
        {
           fprintf(p, "%s", buf);
           fgets(buf, sizeof(buf), stdin); 
        }
        fclose(p);
        return 0;
    }
    
(2) 读写二进制文件

读二进制文件: 使用fread 函数

  • 代码:

    #include <stdio.h>
    void readFile()
    {
        FILE *p = fopen("a.txt", "rb");
        char buf[20] = { 0 };
        int index = 0;
        while(1)
        {
            int res = fread(buf, 1, 5, p);
            printf("res = %d , ", res);
            if(feof(p))
                break;
            printf("buf = %s\n",buf);
            index++;
        }
        fclose(p);
        printf("%d\n", index);
    }
    int main()
    {
        readFile();
        return 0;
    }
    

写二进制文件: 使用fwrite 函数

  • 代码

    #include <stdio.h>
    void writeFile()
    {
        FILE *p = fopen("b.txt", "w");
        char buf = 'a';
        int i;
        for(i = 0; i < 10 ; i++)
        {
            fwrite(&buf, 1, 1, p);
            buf++;
        }
        fclose(p);
    }
    int main()
    {
        writeFile();
        return 0;
    }
    

二、文件读写的深层次的剖析

(1)操作系统与硬件储存

众所周知,我们的计算机操作系统都是由C语言写的,通过C语言协调物理内存与虚拟内存,操作系统想要向物理内存中写数据的时候,会费很大的力。

写数据到物理内存

操作系统有一个字节的数据想要写入物理磁盘中,首先要经历一下几个步骤:

  1. 调用CPU的控制器,发出存储请求。
  2. 控制器把将要存储的数据放入临时寄存器。
  3. 查看总线是否被占用,请求总线控制。
  4. 往地址总线发送将要使用的一个字节的空间指定。
  5. 讲数据寄存器中数据发送到数据总线上,传输到先一步找到的物理地址。
  6. 存储完毕,释放总线。

从磁盘中读数据

读数据的操作与写数据的操作正好相反,也是要经历总线请求与总线释放的过程。

总之,操作系统想要往磁盘中读写数据要经历很繁琐的过程。

(2)操作系统封装读写数据函数接口

每次操作系统与磁盘进行数据交互的时候,都会对CPU有一系列的操作指令,这些读写指令的合集就是读写函数,通过C语言实现这些读写函数再往外部抛出一系列的接口,就实现的读写数据的函数封装。

当用户想往磁盘中写入数据的时候只需要调用这些函数接口就行了。

然而这些函数都是针对最底层的数据存储操作,不利于计算机的操作性能提高,所以,有人就在这层函数的基础上再次进行封装,把性能更加好,更加优化的读写数据函数封装到C语言的标准库中。

在标准库中的读写数据函数,例如fwrite和fputs等等。它们不是每次写入一个字节数据就往物理磁盘中写入数据,而是利用函数在内存中开辟出一个固定大小的字节缓冲池,通过这个缓冲池,来实现数据的读与写。

(3)C语言中的数据缓冲池

C语言的所有读写函数,都会使用到数据缓冲池,无论是读数据操作,还是写数据操作,都会把数据临时存储到这里。

写数据

当我们使用C语言往文件中写入一个字节的数据的时候,会经历一下几个步骤:

  1. 使用函数往目标文件中写入一个字节的数据。
  2. 数据被存储在内存的数据缓冲池中。
  3. 关闭要写入的文件。
  4. 底层函数把缓冲池中的数据写入磁盘中。

从上面的步骤中可以看出我们的函数在执行到写入数据的操作的时候,数据并没有真正的写入到磁盘中,而是等到写入文件的操作结束的时候,才把数据缓冲池中的数据写入磁盘中。

同样的我们使用C语言读一个字节数据的时候,同样会经历一下步骤:

  1. 打开要读的文件。
  2. 底层函数从磁盘中读取满整个缓冲池的数据。
  3. 关闭要读的文件。
  4. 从缓冲池中读取要读取的字节数,剩余没用的数据不读。

缓冲池工作原理

当知道我们的所有读写操作都要进入缓冲池时,我们同样要知道缓冲池的工作原理。

  • 缓冲池有一定固定的大小

  • 缓冲池在进行读数据的操作时,如果要读取的数据字节小于缓冲池的大小,缓冲池会从磁盘中读取满整个缓冲池的数据,所以当每次读取的字节数过小,读取次数过多的时候,会减少访问实际磁盘的磁盘,增加程序运行的性能。

  • 缓冲池的刷新,每刷新一次都会进行与实际物理内存的交互。

    当缓冲池满了或者目标操作文件关闭的时候,缓冲池会自动刷新,写数据时,缓冲池的数据会进入物理磁盘;读数据时,缓冲池的数据会数据固定字节的数据进入输出终端。

总而言之,缓冲池仅仅是用户与物理磁盘之间数据交户的一个缓冲而已,不用想的太深奥。

三、使用函数刷新缓冲池

fflush函数:

函数作用:

刷新缓冲池

函数定义:

int fflush(FILE *P);

参数 返回值:

  • P :目标操作文件
  • 返回值:如果成功,该函数返回零值。如果发生错误,则返回 EOF 。

使用示例:

现有空文件a.txt

利用标准输入数据,在文件没有关闭的时候,查看文件内是否有写入的数据。

#include <stdio.h>
#include <string.h>
void write_file()
{
    FILE *p = fopen("a.txt", "w");
    char temp[100] = { 0 };
    while(1)
    {
        fgets(temp, sizeof(temp), stdin);
        if(strcmp(temp, "exit\n") == 0)
            break;
        fprintf(p, "%s", temp);
    }
    fclose(p);
}
int main()
{
    write_file();
    return 0;
}

运行结果:

  1. 输入中

    [文件]$ gcc -o a fflush.c
    [文件]$ a
    hello
    
    
  2. 查看文件a.txt

    
    

    可以看到文件为空,输入的文件现在正存储在缓冲池中。

  3. 输入exit退出输入,查看文件a

    [文件]$ cat a.txt
    hello
    

    文件关闭,缓冲池中的数据写入磁盘。

现在我们创建一个函数使每输入一次都写入一次磁盘:

#include <stdio.h>
#include <string.h>
void write_file()
{
    FILE *p = fopen("a.txt", "w");
    char temp[100] = { 0 };
    while(1)
    {
        fgets(temp, sizeof(temp), stdin);
        if(strcmp(temp, "exit\n") == 0)
            break;
        fprintf(p, "%s", temp);
        fflush(p);
    }
    fclose(p);
}
int main()
{
    write_file();
    return 0;
}

运行结果:

  1. 输入中,查看a.txt

    [文件]$ gcc -o a fflush.c
    [文件]$ a
    hello
    
    

    再开启另一个终端查看文件 a.txt 状态:

    [文件]$ cat a.txt
    hello
    
  2. 输入结束,查看a.txt

    [文件]$ a
    hello
    exit
    [文件]$ cat a.txt
    hello
    [文件]$ 
    

可以看到使用fflush函数把缓冲池中的数据写入了磁盘。

四、总结

当我们在往磁盘中录入重要的非常重要的数据的时候,一定要使用fflush不断的刷新缓冲池,因为数据非常重要;但我们输入不重要的数据是一定要尽量减少使用fflush函数的次数,因为那样会让程序变得缓慢。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐