标准I/O与文件I/O

在应用开发中,经常要访问文件,Linux 下的文件读写方式分为两大类:标准 I/O 和 文件 I/O,下面分别介绍下两种 I/O 的相关操作,并比较下两种 I/O 的特点。

一、标准I/O

标准I/O库接口由ANSI C标准定义,标准IO函数提供了一种对不用缓冲IO函数的带缓冲的接口。不仅在UNIX系统,在很多操作系统上都实现了标准I/O库,标准I/O库处理很多细节,如缓存分配、以优化长度执行I/O等,这样使用户不必关心如何选择合适的块长度。标准I/O在系统调用基础上构造的,它便于用户使用。标准I/O有两个很重要的定义。

  • 文件指针
    FILE:每个被打开的文件都在内存中开辟一个区域,用来存放文件的有关信息。这些信息保存在一个结构体类型的变量中,该结构体类型是由系统定义的,称为FILE

  • stream:所有的I/O操作本质上都是从文件中输入或输出字节流,所以称为流。

标准I/O预定义3个流,他们可以自动地为进程所使用:标准输入/标准输出/标准错误输出。

1. 文件缓冲

系统自动的在内存中为每一个正在使用的文件开辟一个缓冲区,从内存向磁盘输出数据必须先送到内存缓冲区,装满缓冲区在一起送到磁盘中去。从磁盘中读数据,则一次从磁盘文件将一批数据读入到内存缓冲区中,然后再从缓冲区逐个的将数据送到程序的数据区。这样的目的是尽量减少使用read/write的调用。文件缓冲可以分为三类:

  • 全缓冲
    当填满I/O缓存后才进行实际I/O操作,或者满足一定条件后,系统通过调用malloc来获得所需要的缓冲区域,默认值。
    刷新(fflush):标准I/O的写操作。
    当缓冲区满了,或者满足一定的条件后,就会执行刷新操作。
  • 行缓冲
    当在输入和输出中遇到新行符(\n)时,进行I/O操作。
    当流遇到一个终端时,典型的行缓存。
  • 无缓冲
    标准错误流stderr无缓冲。
    很多的人机交互界面要求不可全缓冲。

2. 打开流

C
FILE *fopen (const char *path, const char *mode);
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE* restrict fp)

fopen()打开由 path 指定的一个文件。
mode 的值如下:
r/rb 打开只读文件,该文件必须存在。
w/wb 打开只写文件,若文件存在则文件长度清为0,若文件不存在则建立该文件。
a/ab 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾。
当给定 b 参数时,表示以二进制方式打开文件。

3. 关闭流

C
int fclose(FILE *stream);


fclose()用于关闭一个已经打开的流:调用成功返回0,失败返回EOF,并设置errno。
在该文件被关闭之前,刷新缓存中的数据。如果标准I / O库已经为该流自动分配了一个缓存,则释放此缓存。
当一个进程正常终止时(直接调用exit函数,或从main函数返回),则所有带未写缓存数据的标准I/O流都被刷新,所有打开的标准I/O流都被关闭。
在调用 fclose() 关闭流后对流所进行的任何操作,包括再次调用 fclose(),其结果都将是未知的

4. 读写流

调用 fopen() 成功打开流之后,可采用三种不同方式对其进行读写操作:
* 每次一个字符的I/O。
使用 fgetc()/fputc() 一次读或写一个字符。
* 每次一行的I/O。
使用 fgets() 和 fputs() 一次读或写一行。每行都以一个换行符终止。当调用 fgets() 时,应指明能处理的最大行长度。
* 直接I/O。
fread() 和 fwrite() 函数支持这种类型的I/O。每次I/O操作读或写某种数量的对象,而每个对象具有指定的长度。这两个函数常用于从二进制文件中读或写。

  1. 字符I/O

    C
    int getc(FILE *stream);
    int fgetc(FILE *stream);
    int getchar(void);


    三个函数的返回:若成功则为下一个字符,若已处文件尾端或出错则为EOF
    函数getchar()等同于getc(stdin)
    注意,不管是出错还是到达文件尾端,这三个函数都返回同样的值。为了区分这两种不同的情况,必须调用ferror()feof()
    getc()的实现是一个宏,而fgetc()是一个函数。
    返回值为int类型。
    C
    int putc(int c, FILE *stream);
    int fputc(int c, FILE *stream);
    int putchar(int c);


    putchar(c) 等价于 putc(c,stdout)
    出错返回 EOF
    putc()实现为宏,/fputc()实现为函数。

  2. 行I/O

    C
    char *gets(char *s);
    char *fgets(char *s, int size, FILE *stream);


    两个函数返回:若成功则为buf,若已处文件尾端或出错则为null
    这两个函数都指定了缓存地址s,读入的行将送入其中。gets()从标准输入读,而fgets()则从指定的流读。
    对于fgets(),必须指定缓存s的长度n。此函数一直读到下一个换行符为止,但是不超过n-1个字符,读入的字符被送入缓存。该缓存以null字符结尾。如若该行,包括最后一个换行符的字符数超过n-1,则只返回一个不完整的行,而且最后总是以null字符结尾。对fgets()的下一次调用会继续读该行。
    gets()是一个不推荐使用的函数,因为调用者在使用gets()时不能指定缓存的长度,这样就可能造成缓存越界(如若该行长于缓存长度),写到缓存之后的存储空间中,从而产生不可预料的后果。
    gets()fgets()的另一个区别是,gets()并不将换行符存入缓存中。
    C
    int puts(const char *s);
    int fputs(const char *s, FILE *stream);


    两个函数返回:若成功则为非负值,若出错则为EOF
    函数fputs()将一个以null符终止的字符串写到指定的流,终止符null不输出。注意,并不一定是每次输出的字符串都包含一个换行符。
    puts()将一个以null符终止的字符串写到标准输出,终止符不输出。随后,puts()又将一个换行符写到标准输出。
    puts()并不像它所对应的gets()那样不安全。但是我们还是应避免使用它,以免需要记住它在最后又加上了一个换行符。如果总是使用fgets()fputs(),那么就会熟知在每行终止处我们必须自己加一个换行符

  3. 二进制I/O
    C
    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);


    两个函数的返回:读或写的对象数
    对于二进制数据我们更愿意一次读或写整个结构。
    为了使用getc()putc()做到这一点,必须循环读取整个结构,一次读或写一个字节。(效率低)
    fputs()在遇到null字节时就停止,而在结构中可能含有null字节,所以不能使用每次一行函数实现这种要求。如果输入数据中包含有null字节或换行符,则fgets()也不能正确工作。(实现限制)

5. 刷新流

int fflush(FILE *fp);
可强制刷新一个流。此函数将流缓冲区中所有未写的数据写到文件中。

6. 定位流

C
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);


ftell()用于取得当前的文件位置,调用成功则为当前文件位置指示,若出错则为-1L
fseek()用户设定stream流的文件位置指示,调用成功返回0,失败返回-1,并设置errno
fseek()whence参数:SEEK_SETSEEK_CURSEEK_END
rewind()用于设定流的文件位置指示为文件开始,该函数调用成功无返回值。
rewind()等价于(void)fseek(stream, 0L, SEEK_SET)

二、文件IO

文件I/O用于应用层与内核层之间文件的输入输出,他的原理是内核通过inode号区分不同的文件,进程中对不同文件的区分使用ID号,来映射不同的inode。文件IO直接调用了系统接口 不需要依赖标准C库。文件I/O是不带缓冲的,对于Linux内核来讲,所有打开文件都由文件描述符表示。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。文件描述符的定义:
* 内核顺序分配的非负整数
* 内核用以标识一个特定进程正在访问的文件
* 其他资源(socket、pipe等)的访问标识
标准输入、标准输出和标准出错由shell默认打开,分别为0/1/2。

1. 文件I/O的打开/创建/关闭

C
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
int close(int flags);


open()creat()调用成功返回文件描述符,失败返回-1,并设置errno。
open()/creat()调用返回的文件描述符一定是最小的可用文件描述符。
creat()等价于open(pathname, O_CREAT|O_WRONLY|O_TRUNC, mode)
pathname是需要打开的文件路径,flags是打开的模式:
O_APPEND 附加模式,在NFS文件系统下可能会出错,该模式可以自动置文件指针到文件末尾
O_CREAT 创建文件
O_EXCL 在CREAT后,若文件存在不创建,返回-1,否则创建
O_RDWR 读写模式打开
O_RDONLY 只读模式打开
O_WRONLY 只写模式打开
O_TRUNC 清零模式
mode权限 仅在创建模式下使用,成功:返回ID号,失败返回文件标识号。

close()调用成功返回0,出错返回-1,并设置errno。
当一个进程终止时,该进程打开的所有文件都由内核自动关闭。
关闭一个文件的同时,也释放该进程加在该文件上的所有记录锁。

2. 文件I/O的读写

C
ssize_t read(int fd, void *buff, size_t count);
ssize_t write(int fd, void *buff, size_t count);


write()/read()调用成功返回已读/写的字节数,失败返回-1,并设置errno
write()的返回值通常与count不同,因此需要循环将全部待写的数据全部写入文件。
write()出错的常见原因:磁盘已满或者超过了一个给定进程的文件长度限制
对于普通文件,写操作从文件的当前位移量处开始,如果在打开文件时,指定了O_APPEND参数,则每次写操作前,将文件位移量设置在文件的当前结尾处,在一次成功的写操作后,该文件的位移量增加实际写的字节数。读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读取的字节数。

3. 文件I/O的定位

C
off_t lseek(int fd, off_t offset, int whence);


每个打开的文件都有一个与其相关的“当前文件位移量”,它是一个非负整数,用以度量从文件开始处计算的字节数。
通常,读/写操作都从当前文件位移量处开始,在读/写调用成功后,使位移量增加所读或者所写的字节数。
lseek()调用成功为新的文件位移量,失败返回-1,并设置errno。
lseek()只对常规文件有效,对socket、管道、FIFO等进行lseek()操作失败。
lseek()仅将当前文件的位移量记录在内核中,它并不引起任何I/O操作。
文件位移量可以大于文件的当前长度,在这种情况下,对该文件的写操作会延长文件,并形成空洞。

三、两种I/O模型的比较

I/O模型文件I/O标准I/O
缓冲方式非缓冲I/O缓冲I/O
操作对象文件描述符流(FILE )
打开open()fopen()/freopen()/fdopen()
read()fread()/fgetc()/fgets()…
write()fwrite()/fputc()/fputs()…
定位lseek()fseek()/ftell()/rewind()/fsetpos()/fgetpos()
关闭close()fclose()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值