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(标准错误)就是了

三个缓存空间的概念(数组)

  1. 我们程序中的缓存,就是你想从内核读写的缓存(数组)——用户空间的缓存

  2. 每打开一个文件,内核在内核空间中也会开辟一块缓存——内核空间的缓存

    文件IO的写就是将用户空间的缓存写入到内核空间的缓存中;

    文件IO的读就是将内核空间的缓存写入到用户空间的缓存中。

  3. 标准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
标准输入(流)stdin0
标准输出(流)stdout1
标准错误(流)stder2

举例——用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函数的区别:

  1. fopen有缓存,open无缓存,fopen因为有库缓存,减少了内核空间与用户空间的切换,而open函数需要不断进行切换;表现为,如果顺序访问文件,fopen系列的函数要比直接调用open系列快;如果随机访问文件open要比fopen快。
  2. 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)。

注:

  1. 如果先调用fputs函数写入到一文件内,再对此文件使用fgets函数读其内容,是读不到内容的,fputs和fgets函数同样涉及文件的读写指针。

  2. 行缓存在输入或输出中遇到新行符时(”\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——文件流

函数返回值:实际写的单元个数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值