文件知识复习总结

【摘要】最近把之前学的关于文件的知识再次复习了一下,并且和Linux下的文件描述符fd进行了一个对比,希望这篇博客可以对你们有所帮助。

目录

  • 复习C文件IO相关操作
  • 认识文件相关系统调用接口
  • 认识文件描述符,理解重定向
  • 对比fd和FILE,理解系统调用和库函数的关系
  • 软硬链接,对比区别
  • 动态静态库

先复习一下C文件接口

在C语言中,要想单个的输出字符,可以使用fgetc ,fputc
要想输出字符串到文件,可以使用 fputs,fgets
格式化的方式读写文件
fprintf(fp,"%d,%6.2f",i,j);
fscanf(fp,"%d,,%6.2f",&i,&j);  //格式化读写对磁盘文件读写,使用方便,容易理解,但由于在输入时要将文件中的ASCII码转换成二进制形式再保存在内存变量中,在输出时又要将内存中的二进制形式转换成字符,要花费较多时间,因此,在内存和磁盘频繁交换数据的情况下,最好不要使用fprintf,fscanf函数
用二进制形式向文件读写一组数据
  • C语言允许用fread函数从文件中读一个数据块,用fwrite函数向文件写一个数据块,在读写时是以二进制形式进行的。在向磁盘写数据时,直接将内存中一组数据原封不动,不加转换地复制到磁盘文件,在读入时也是将磁盘文件中若干字节的内容一批读入内存。
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);

fread(&stud[i],sizeof(struct Student_type),1,fp);
fwrite(&stud[i],sizeof(struct Student_type),1,fp);
随机读写数据文件
  • 随机访问不是按数据在文件中的物理位置次序进行读写,而是可以对任何位置上的数据进行访问,显然这种方法比顺序访问效率高得多
  • 对流式文件既可以进行顺序读写,也可以进行随机读写,关键在于控制文件的位置标记。如果文件位置标记是按字节位置顺序移动的,就是顺序读写。如果能将文件位置标记按需要移动到任意位置,就可以实现随机读写。
rewind使文件位置标记重新返回文件的开头,此函数没有返回值。
fseek 改变文件位置标记
fseek(文件类型指针,位移量,起始点)
文件开始位置 SEEK_SET 0 / 文件当前位置 SEEK_CUR 1 / 文件结尾位置 SEEK_END 2

fseek 一般用于二进制文件
fseek(fp,100L,0) //从开头100个字节处
fseek(fp,50L,1)  //从当前位置移到第50个字节处
fseek(fp,-10L,2) //从末尾前移动10个字节处
#include<stdio.h>
#include<string.h>

int main()
{
    FILE*fp = fopen("hello.txt","w");
    if(fp == NULL){
        printf("fopen error\n");
    }
    const char* msg ="hello";
    int count =5;
    while(count--){
        fwrite(msg,strlen(msg),1,fp);
    }
    fclose(fp);
    return 0;

}

#include<stdio.h>
#include<string.h>

int main()
{
    FILE *fp = fopen("myfile","r");
    if(!fp){
        printf("fopen error\n");
    }
    char buf[1024];
    const char* msg ="hello";
    while(1){
        ssize_t s = fread(buf,1,strlen(msg),fp);
        if(s>0){
            buf[s]= 0;
            printf("%s",buf);
        }
    if(feof(fp)){
        break;

    }
  }
  fclose(fp);
  return 0;

}
系统文件IO
//头文件先不写了
int main()
{
    umask(0);
    int fd  = open("myfile",O_WRONLY |O_CREAT,0644);
    if(fd < 0){
        perror("open");
        return 1;
    }
    int count = 5;
    const char* msg = "hello";
    int len = strlen(msg);
    }

    while(count--){
        write(fd,msg,len);
    }
    close(fd);
    return 0;
}
//读文件
int main()
{
    int fd = open("myfile",O_RDONLY);
    if(fd < 0{
        perror("open");
        return 1;
    }

    const char* msg = "hello";
    char buf[1024];
    while(1){
        ssize_t s = read(fd,buf,strlen(msg));
        if(s > 0){
            printf("%s",buf);
        } else {
            break;
        }

    }
    close(fd);
    return 0;
}
关于文件描述符
  • 文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要出阿年相应的数据机构来描述文件,于是就有了file结构体,表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files,指向一张表files_struct.该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针。本质上来说,文件描述符就是该数组的下标,所以只要拿着文件描述符,就可以找到对应的文件。
#include<stdio.h>
#include<string.h>

int main()
{
  const char* msg0 = "hello printf\n";
  const char* msg1 = "hello fwrite\n";
  const char* msg2 = "hello write\n";

  printf("%s",msg0);
  fwrite(msg1,strlen(msg0),1,stdout);
  write(1,msg2,strlen(msg2));

  fork();
  return 0;
}

//运行结果
hello write
hello printf
hello fwrite
hello printf
hello fwrite
  • printf和fwrite(库函数)都输出了两次,而write只输出了一次(系统调用)
  • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
  • printf,fwrite库函数会自带缓冲区,当发生过重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  • 而我们放到缓冲区中的数据,就不会立即刷新,甚至fork之后
  • 但是进程退出之后,会统一刷新,写入文件当中。
  • 但是当fork 的时候,父子数据会发生写实拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随机产生两份数据。
  • write没有变化,说明没有所谓的缓冲。
  • 简单点说,就是缓冲区里面的数据被子进程拷贝了一份,因此子进程也会输出那两个库函数的输出语句
  • 我们这里所说的缓冲区只是用户级的缓冲区,为了提升整体性能,操作系统也会提供相关的内核级缓冲区
关于文件系统
  • 在Linux系统中一切皆文件,因此我们有必要将文件先描述起来再组织起来
  • 和PCB类似的,文件管理也是使用一个结构体来进行管理。iNode节点就可以看做是描述文件的标志
  • 要新建一个文件,那就需要分配一个新的iNode节点和一段内存空间,这两个都必须同时满足才可以建立成功
  • iNode节点表里面放着的是文件的各种属性,数据区里面放的是各种数据。
  • 在建立文件成功之后,要将文件名添加到目录中,目录其实也是文件,这时候新建的文件应该和目录有一个映射关系,表名目录中有这个文件。
  • 同样的,当我们需要删除这个文件时,只需要把目录中对应的节点映射关系删除掉就可以了,也就是这样,操作系统认为这个iNode节点已经处于可用状态
  • 当重新创建一个新的文件时,分配这个iNode,然后分配对应的存储空间。
  • 因此数据区里面的数据才真正被覆盖从而完成映射。
  • 因此,如果我们不小心删除了文件时,不要乱动,文件还可以找回来的。
关于软硬链接
  • 硬链接其实就是文件的别名,每当给一个文件创建一个硬链接,相当于它的连接数多了一个
  • 内核会记录下来每个文件的连接数,当我们需要删除一个文件时,需要把每个文件连接数删除完毕才能将对应的磁盘文件释放掉。
  • 软链接是通过iNode引用另外一个文件,相当于Windows下的快捷方式。所以软链接是独立的一个文件
touch abc
ln abc def
ls -li abc def

abc.s -> abc
关于动态库和静态库
  • 静态库:程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
  • 动态库:程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码
  • 一个动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
  • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接
  • 动态库可在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟地址机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
printf是c库当中的IO函数,一般往stdout中输出,但是stdout底层访问文件的时候,找的还是fd:1,但此时,fd:1所表示内容,已经变成了myfile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向

总结:
不论是文件指针还是fd文件描述符,本质上都是数组的下标,并且都遵守最小未被使用的整数的原则,文件的管理不管是那种类型,都需要一个结构体来描述,使用数据结构来管理和维护。对于文件的一些操作都是相同,只有函数调用有些许不同。文件的读写都是通过位置标识进行读写,这些都是共通的。因此我们要想掌握文件的知识,那就必须自己总结一下两者之间的异同。
注:本文如有纰漏,欢迎大家指正,感谢^.^

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值