Linux下IO编程(二)
标准IO
标准IO与文件IO的区别:
直观来看:
文件IO:是直接调用内核提供的系统调用函数,头文件为unistd.h
标准IO:是间接调用系统调用函数,头文件是stdio.h。比如像printf()、scanf()、getchar()、putchar(),标准IO中的相关函数,不仅可以读写普通文件,还可以向标准的输入(如键盘)或标准的输出(显示器)中读或写。
从定义来看:
文件IO:文件I/O称之为不带缓存的IO(unbuffered I/O)。不带缓存指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级I/O——操作系统提供的基本IO服务,与os绑定,特定于linix或unix平台。
标准IO:标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性。标准I/O库处理很多细节。例如缓存分配,以优化长度执行I/O等。标准的I/O提供了三种类型的缓存:
- 全缓存:当填满标准I/O缓存后才进行实际的I/O操作(有fread,fwrite)
- 行缓存:当输入或输出中遇到新行符时(”\n“)或写满,标准I/O库执行I/O操作(有fgets,fputs,gets,puts,printf,scanf、fprintf、sprintf)
- 不带缓存:只要用户调用这个函数,就会进行IO操作,stderr(标准错误)就是了
三个缓存空间的概念(数组)
-
我们程序中的缓存,就是你想从内核读写的缓存(数组)——用户空间的缓存
-
每打开一个文件,内核在内核空间中也会开辟一块缓存——内核空间的缓存
文件IO的写就是将用户空间的缓存写入到内核空间的缓存中;
文件IO的读就是将内核空间的缓存写入到用户空间的缓存中。
-
标准IO的库函数中也有一个缓存,这个缓存叫——库缓存
接下来举例来感受标准IO缓存的机制,以printf()函数为例:
例1
#include <stdio.h>
#include <unistd.h>
int main()
{
char buf[]="hello linux";
printf("%s",buf);
//write(1,buf,sizeof(buf));
while(1); //加死循环
return 0;
}
经过编译执行,发现以上并五输出内容。
例2
#include <stdio.h>
#include <unistd.h>
int main()
{
char buf[]="hello linux";
//printf("%s",buf);
write(1,buf,sizeof(buf));
while(1); //加死循环
return 0;
}
执行后,虽死循环但命令行有输出”hello liunx“。
对比例1和例2,前者为标准IO,结果表明库缓存未写入内核缓存空间;后者为文件IO,结果表明用户空间缓存写入到内存空间。
例3——在例1的基础上加上新行符”\n“
#include <stdio.h>
#include <unistd.h>
int main()
{
char buf[]="hello linux\n";
printf("%s",buf);
while(1); //加死循环
return 0;
}
此时可输出”hello linux“,表明printf函数在当输入或输出中遇到新行符时(”\n“),标准I/O库执行I/O操作,将库缓存写入内核空间缓存。
例4——将printf的库缓存填满
#include <stdio.h>
#include <unistd.h>
int main()
{
char buf[]="hello linux";
int i=1;
while(i<=95)
{
printf("%s",buf);
i++;
}
while(1);
return 0;
}
此时也可输出多个“hello linux”,通过while函数循环将字符串不断放入printf的库缓存,当库缓存填满时,标准I/O库执行I/O操作,将库缓存写入内核空间缓存。
标准IO和文件IO的区别
综合以上来看,可见:
通过文件I/O读写文件时,每次操作都会执行相关系统调用。这样处理的好处是直接读写实际文件,坏处是频繁的系统调用会增加系统开销,标准I/O可以看成是在文件I/O的基础上封装了缓冲机制。先读写缓冲区,必要时再访问实际文件,从而减少了系统调用的次数。
fopen函数(fclose)
FILE * fopen(const char * path,const char * mode);
参数:(相比open函数没有第三个参数)
- path ——文件路径及文件名
- mode——流形态
r 打开只读文件,该文件必须存在。
r+ 打开可读写的文件,该文件必须存在。
rb 打开一个二进制文件,只读。
rb+ 以读/写方式打开一个二进制文件。
w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
wb 打开一个二进制文件,只写。
wb+ 以读/写方式建立一个新的二进制文件。
a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。
a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。
ab 打开一个二进制文件,进行追加 。
ab+ 以读/写方式打开一个二进制文件进行追加 。
mode总结:
-
b:二进制文件
-
r:只读方式打开文件,文件必须存在
-
w或a:只写方式打开文件,不存在则创建
区别:w等价于O_TRUNC,a等价于O_APPEND;
-
+:读写方式打开文件,文件必须存在
函数返回值:FILE*
FILE*——文件流指针,类似于文件IO中的文件描述符。
FILE的定义:struct _IO_FILE(usr/include/libio.h),包含读写缓存的首地址、大小、位置指针等。
类型 | File* | 文件描述符fd |
---|---|---|
标准输入(流) | stdin | 0 |
标准输出(流) | stdout | 1 |
标准错误(流) | stder | 2 |
举例——用fopen实现touch功能
#include <stdio.h>
int main(int argc,char* argv[])
{
FILE* fp;
fp=fopen(argv[1],"w+");
if(fp==NULL)
{
printf("create file is failed\n");
return -1;
}
printf("creat file:%s is successful\n",argv[1] );
fclose(fp;
return 0;
}
关于fopen函数创建文件的权限问题:
通过执行以上代码创建文件,查看创建文件的权限:
虽然fopen没有第三个参数,但是创建的新文件的权限为0666&(~umask)。
如果将umask设置为0000,则可知新创建的文件权限为0666:
fopen流程:
fopen等标准IO函数先开辟文件的缓存,对缓存进行后续读写。
fopen函数与open函数的区别:
- fopen有缓存,open无缓存,fopen因为有库缓存,减少了内核空间与用户空间的切换,而open函数需要不断进行切换;表现为,如果顺序访问文件,fopen系列的函数要比直接调用open系列快;如果随机访问文件open要比fopen快。
- open是系统函数,不可移植,fopen是标准C函数,可移植。
fgets函数(行缓存的读)
char* fgets(char* s,int size,FILE* stream)
参数:
- s——缓存,即读到哪里去
- size——读多少字节
- stream——从哪读
**函数返回值:**若成功,返回缓存的地址(即s),失败或已经处于文件尾端返回NULL。
fputs函数(行缓存的写)
int fputs(const char*s,FILE* stream);
参数:
- s——缓存,即要写什么
- stream——写到哪
**函数返回值:**成功返回非负值,失败或者文件尾端为EOF(-1)。
注:
-
如果先调用fputs函数写入到一文件内,再对此文件使用fgets函数读其内容,是读不到内容的,fputs和fgets函数同样涉及文件的读写指针。
-
行缓存在输入或输出中遇到新行符时(”\n“)或写满时才会进行IO操作,如果在调用行缓存的函数时,并未满足以上两点,按理说应该不会写入或读入的,但是一般在代码结尾会有fclose函数,fclose函数在文件关闭前,如果标准IO已经为该流分配了一个缓存,则释放此缓存,即会写入或读入。
#include <stdio.h> int main(int argc,char* argv[]) { char buf[]="hello linux"; //虽然没加”\n",而且行缓存也没满,但因为fclose FILE* fp; //最终生成的文件中会写入hello linux fp=fopen(argv[1],"w+"); if(fp==NULL) { printf("open file is failed\n"); return -1; } printf("open file:%s sucess\n",argv[1]); fputs(buf,fp); //while(1) 加入死循环的话最终文件中不会写入 fclose(fp); return 0; }
fflush函数
fflush(FILE* fp)
作用:把库函数中的缓存内容强制写入内核中
#include <stdio.h>
int main(int argc,char* argv[])
{
char buf[]="hello linux"; //虽然没加”\n",而且行缓存也没满,但因为fclose
FILE* fp; //最终生成的文件中会写入hello linux
fp=fopen(argv[1],"w+");
if(fp==NULL)
{
printf("open file is failed\n");
return -1;
}
printf("open file:%s sucess\n",argv[1]);
fputs(buf,fp);
fflush(fp;
while(1) //加入死循环
fclose(fp);
return 0;
}
在循环前加入fflush后,文件中会写入“hello linux”字符串。(fclose函数中就包含fflush函数功能
)
fseek函数
int fseek(FILE *stream, long offset, int whence);
**函数功能:**调整读写指针位置
参数:与lseek函数参数相同,返回值不同。
- stream——文件指针
- offset——偏移量(字节为单位)
- whence——基准(SEEK_SET、SEEK_END、SEEK_CUR)
函数返回值:
fseek函数成功返回0,失败返回-1;lseek成功返回的是最终的文件读写指针的位置。
rewind函数
void rewind(FILE *stream);
**函数作用:**用于设定文件的位置指针为文件开始,相当于void fseek(fp,0, SEEK_SET);
ftell函数
long ftell(FILE *stream);
函数功能:用于返回当前文件的位置指示。
gets和puts(行缓存的读写)
char *gets(char *s);
int puts(const char *s);
gets和fgets的区别:
- gets()时不能指定缓存的长度,这样就可能导致缓存越界
- gets()只能从标准输入中读
- gets()并不将新行符存入缓存中,fgets()将新行符存入缓存中
puts和fputs的区别:
- puts函数只能向标准输出中写
- puts输出时会添加一个新行符,而fputs不会
fprintf和sprintf(行缓存)
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *buffer, const char *format, [argument]...)
**fprint函数功能:**把格式化字符串输出到指定文件中
**sprintf函数功能:**把格式化字符串输出到指定字符串
fputc和fgetc
feof和ferro、clearerr
执行clearerr后,不管之前值为多少,feof和ferror都变为0。
举例:使用fputc,fgetc feof实现cat功能
#include <stdio.h>
int main(int argc,char* argv[])
{
FILE* fp;
int read_ret;
if(argc<2)
{
printf("open file is failed\n");
return -1;
}
fp=fopen(argv[1],"r");
if(fp==NULL)
{
printf("open file: %s is failed\n",argv[1]);
return -2;
}
while(1)
{
read_ret=fgetc(fp); //读一个字符到read_ret中
fputc(read_ret,stdout); //将read_ret的字符写到标准输出
if(feof(fp))
{
break;
}
}
fclose(fp);
return 0;
fread函数(全缓存)
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数
- ptr——用于接受数据地址的指针,即读到哪里
- size ——读的每一个单元的字节大小(要看void*中实际的类型)
- nmemb——有多少个单元
- stream——文件流
函数返回值:实际读的单元个数
fwrite函数(全缓存)
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
参数
- ptr——要写的内容的地址指针
- size——每个单元的字节大小
- nmemb——写多少个单元
- stream——文件流
函数返回值:实际写的单元个数