作者:杨老师,华清远见教育科技集团讲师。
在linux下的C程序编程中对文件的IO操作有标准IO和文件IO两种操作类型。标准IO是带缓冲的IO属于库函数,文件IO是不带缓冲的属于系统调用。系统调用和库函数之间的区别如下图1-1
图 1-1
一、 标准IO
标准IO的缓冲类型分为:全缓冲、行缓冲、不缓冲三种类型。
在标准IO中对文件操作默认是全缓冲的,对于磁盘文件的缓冲区是由标准IO库中的函数通过malloc来获得的。
在标准IO中与终端相关的通常是行缓冲,但是如果标准输入、标准输出不涉及交互时也可以设成全缓冲。
标准出错是不带缓冲的,这样就可以将出错信息实时的显示出来。
这三种缓冲类型可以通过void setbuf(FILE * f p, char * b u f) 和int setvbuf(FILE * f p, char * b u f, int m o d e, size_t s i z e) 这两个函数进行修改。比如,在linux下标准输出的行缓冲的大小默认设置为1024个字节,如果你想把它扩大2048个字节,那么就可在应用程序中调用setbuf函数来设置。设置后再向终端进行输出时如果不加其他刷新条件,那么直到输出超过2048个字节时数据才会被刷新出来。 而setvbuf函数还可以明确的指定缓冲类型,比如_IOFBF(全缓冲)、_IOLBF(行缓冲)、_IONBF(无缓冲)。
标准IO中的freopen函数是用来将一个流进行重新定向的。如果该流之前已经被重新定向,那么freopen函数将会清除之前的定向并且使用本次定向。通常用来将一个文件打开为stdin、stdout、stderr三个流。
fdopen函数是用来将一个已经打开的文件描述和一个流进行关联。因为管道和socket套接字等描述符是不可以用标准IO的函数打开的,因此可以调用此函数进行关联,然后就可以用标准IO的函数进行操作了。
标准IO中gets函数是用来从终端读入字符串的,因为它并不会检测字符串的长度,因此如果缓冲的大小设置不合适会导致未知的错误。gets函数从终端上读到的字符串中后一个字符其实是‘\n’字符,然后gets函数又将‘\n’字符转换成了‘\0’字符,所以gets函数会将后的换行符读走。
相对于gets函数来说fgets函数是安全的因为它会检测读入字符的长度。当使用fgets函数从终端上读入字符时有两种情况:
第一种:如果终端上输入的字符个数小于fgets函数指定的size-1个,那么它也会将终端上‘\n’字符读走并且在结尾添加上‘\0’字符。
第二种:如果终端上输入的字符个数大于fgets函数指定的sisz-1个,那么它就不会读走终端上的‘\n’字符,而且末尾始终会添加‘\0’字符。
二、文件IO
文件IO是不带缓冲,是因为每个read和write都会调用内核中的系统调用。当使用文件IO的读写函数对文件进行操作后,其实数据也并不会实时的写入磁盘,只是放到了内核缓冲区中,如果该缓冲区没有写满,那么不会将该缓冲区放入到输出队列。只有缓冲区满了或者内核需要该缓冲区,那么才将该缓冲区加入到输出队列中,等待其到达队首时才可以进行实际的IO操作。
在使用open函数打开文件时,如果使用了O_SYNC标志,那么当调用的write函数在返回时就已经将数据写入到磁盘上了。而如果是调用sync()函数,那么当该函数返回时数据并没有写到磁盘上而仅仅是被放到了输出队列中。
文件IO中的定位函数lseek可以返回一个负的文件指针偏移,因为对于某些设备文件允许负的偏移量。因此在对lseek函数进行出错判断时,不可以用是否小于0,而应该用是否等于-1。当时用lseek函数生成一个空洞文件时,其实文件的空洞部分是不占磁盘块的。使用ls –ls命令查看两个字节大小相同但是一个有空洞,一个没有空洞的文件时会发现,有空洞的文件所占的磁盘块要少(空洞的大小要足够大时才可以看到现象),如下图1-2:
图 1-2