Linux系统的所有程序和数据都是以“文件”的形式进行存储,所有Linux用户和程序看到的文件、目录以及文件保护信息都存储在其中,在Linux系统中,一切皆文件。
Linux系统可以把文件分为以下七种:
1.规则文件 (-) 存储在外部存储器中,用于存储普通数据
2.目录文件 (d) 用于存放目录项,是文件系统管理的重要文件类型
3.管道文件 (p) 用于进程间通信的特殊文件,也被命名为管道(FIFO,先进先出)
4.套接字文件 (s) 一种用于网络间通信的特殊文件
5.链接文件 (l) 用于间接访问另外一个目标文件,类似于Windows的快捷方式,其中软连接具有独立的inode号,其在链接到的目标文件被删除后将失效,硬链接和源文件是相同的inode号,在源文件被删除后仍然可以正常访问
6.字符设备文件 (c) 字符设备在应用层的访问接口
7.块设备文件 (b) 块设备在应用层的访问接口
上述的数据都是以文件的形式存储在Linux系统当中,且Linux系统为简化不同类型的文件的操作流程,提供了一套POSIX标准,对不同操作系统的访问接口做出了统一的规范,其目的是提高程序的兼容性和可阅读性。
C语言同样也具有语法标准,并且C语法标准在发布的时候也会提供相应的库给用户,库函数同样遵循POSIX标准进行设计,并且具有良好的跨平台兼容性。
Linux系统提供了一套系统IO接口,系统IO是内核暴露给用户的系统调用接口,在调用时,系统会执行trap,CPU切换到内核态。系统IO的调用开销大,每次调用的时候都需要执行内核态与用户态之间的切换,并且由于不同系统的调用接口不同,其程序的跨平台兼容性相对较差。
在此基础上,标准C库提供了一套标准IO,其在系统IO的基础上封装了一层缓冲区,利用文件流的形式包装fd,通过批量读写减少系统调用次数。
标准IO与系统IO各有利弊,在常见的文件读取操作下,使用标准IO能够有效降低系统开销,并且标准IO提供了按行写入和按块读取等高级操作,其读写相对更方便,但是对于实时性要求较高的环境下或者进行网络编程时,仍然需要使用系统IO快速访问。
标准C库中关于文件输入和输出的函数接口一般被称为标准IO,访问文件的常用的标准IO函数有fopen(), fread(), fwrite(), fclose(), fputc(), fputs(), fscanf()等。
1.打开文件
在标准C库中,如果要打开访问一个文件,标准IO中提供了一个fopen函数,用户只需要包含头文件stdio.h即可调用,如果文件打开成功,则返回指向该文件的一个FILE类型指针,如果文件打开失败则返回NULL。FILE*是标准c定义的一个文件流指针,其数据结构为一个链表的形式,内核采用链表的形式进行管理。FILE是一个宏定义,其表示为一个_IO_FILE的结构体。
在调用fopen打开文件时,内核会从堆内存中申请一块FILE结构体大小的空间用于存储文件的所有信息,然后按照文件打开的顺序把每个打开的文件的结构体形成一张链表,然后使用链表头进行管理。打开文件的目的是对文件进行操作,在每次运行程序的时候,都会有三个文件流被打开,它们分别是标准输入流stdin、标准输出流stdout以及标准错误流stderr。
fopen函数的第一个参数为文件路径字符串,第二个路径也为字符串,其可以选择的范围有:
“r” 只读打开,需要文件存在
“r+” 以读写的方式打开,要求文件必须存在
“w” 以只写的方式打开,文件不存在将创建新文件,文件已存在将清空文件
“w+” 以读写的方式打开,文件不存在将创建文件,文件已存在将清空文件
“a” 以只写的方式打开,文件不存在将创建文件,文件光标自动定位到文件末尾,以追加方式进行写入
“a+” 以读写的方式打开,文件不存在将创建文件,文件光标自动定位到文件末尾,以追加的方式进行写入
“b” 以二进制的形式打开,在默认没有b参数的时候,文件默认按文本形式打开z
在Windows环境下,其换行默认为\r\n,因此,在计算机识别到\r\n是,会默认将其转换为\n返回,如果是写入\n,则会将其转换为\r\n,如果在文件打开时使用了b参数,则会将\r当作普通字符进行处理,与Unix环境统一。为了保证程序的跨平台兼容性,采用b参数可以预防一些意料之外的错误。
2.读取文件
读取函数在标准C库中主要有以下三类,分别是字符读取fgetc(), 按行读取fgets(), 按块读取fread()。
其中,fgetc函数为逐字符读取,其返回类型为unsigned char,在识别到"\n"后会将其当作普通字符返回,特别注意的是,当函数读取到EOF之后,其将返回EOF,且文件光标位置不会向后移动,如果再次读取其仍然会返回EOF。
fgets是按行读取函数,其在读取到"\n"、EOF或者第n-1个字符后将会自动终止。fread函数表示按块读取,其函数原型为
fread(void *restrict ptr, size_t size, size_t n, FILE *restrict stream)
其中第一个参数ptr表示读取结果dst存放地址,第二个表示单个数据的空间尺寸,第三个表示读取的块的个数,第四个参数为文件流。fread函数返回成功读取的文件个数,在不出错的情况下返回n。
在读取过程中,如果出现了错误,函数的返回值也会是EOF,在读取到末尾的时候也会是EOF,因此,在标准C库中还有一个feof()以及clearerr()接口,其中feof函数返回为一个int类型数据,其仅在文件流的指针指向末尾的时候返回非0值,换句话说,如果是正常读取到了文件的末尾,函数返回值为真,如果发生了错误,函数返回值为假。
clearerr函数是void类型函数,其主要作用是清除文件错误标识符。
3.写入文件
常见的写入函数有fputc、fputs等,其中fputc是按字符写入,其函数原型为int fputc(int c, FILE *stream),如果写入成功则返回写入的值,写入失败则返回EOF,特别注意写入的参数c是int类别,其在写入的时候会被默认截断为unsigned int类别,这个操作通常是静默的。通常情况下,可以将返回值与写入值进行比较,以确保写入正确,未发生意外的截断。
fputs则是按字符串写入到文件,其函数原型为fputs(const char *restrict s, FILE *restrict stream),写入成功返回一个非负整数,写入失败则返回EOF。这个写入在遇到"\0"时结束,并且在结尾处不会自动添加换行,如果需要添加换行,则需要自行添加\n。
此外还有标准格式化写入,fprintf(FILE *restrict _Nonnull, const char *restrict _Nonnull, ...),其第一个参数为目标写入的文件,后面的参数的使用方法与printf完全一致,如果将第一个参数填写为标准输出流,那么这个函数等效于printf。
4.文件光标移动
在写入或者读取过程中,有时需要调整文件的光标的位置,在标准C库中通常提供了文件光标的有关操作函数。常见的光标操作函数有fgetpos(FILE *restrict stream, fpos_t *restrict pos)、fseek(FILE *stream, long off, int whence)
fgetpos函数用于读取当前打开的文件的光标位置,其函数原型为fgetpos(FILE *restrict stream, fpos_t *restrict pos),其中fpos_t是一个结构体类型数据,其包含pos和state两个变量,其中pos是一个long类型变量用于记录指针偏移量,state是一个结构体类型变量,其主要用于记录多字节字符和宽字符的转换状态,其结构类型为
typedef struct
{
int __count;
union
{
__WINT_TYPE__ __wch;
char __wchb[4];
} __value; /* Value so far. */
} __mbstate_t;
其中,__count记录转换器当前已处理但尚未凑齐一个完整字符的字节数量,如果为0则代表上一个已经处理完毕,联合体变量__wch记录解析值,__wchb[4]记录已经读取的不完整的部分原始值。
fgetpos函数其读取到的光标位置存储在fpos_t变量里,其如果成功则返回0,失败则返回一个非0数值,并且会在errno中存储错误。
fseek函数能够快速实现对光标的偏移,其函数原型fseek(FILE *stream, long off, int whence),stream参数传入打开的文件的文件流指针,off传入偏移量,向前偏移为正数向后偏移为负数,whence表示起始位置,其由三个宏来表达,分别是SEEK_SET,SEEK_CUR,SEEK_END,分别表示文件开头、当前位置、文件末尾。如果设置成功函数返回0,失败则返回一个非0值。
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://zixblog.com/zix/linux%e5%9f%ba%e7%a1%80/wenjianio/