本博客内容依据《UNIX环境高级编程(中文第三版)》进行整理
标准IO与系统IO区分:
标准IO依赖系统IO ,系统IO没有缓冲区,缓冲区的作用是合并系统调用
标准IO移植性比较强,要让程序的吞吐量更大,需要用标准IO(更多的追求是让程序的吞吐量更大)。
系统IO会让程序响应速度更快,
一、标准IO
IO:input & output (文件的写入或读出,是一切…的接口)
标准IO:stdio 系统调用IO:sysio
标准化的函数移植性比较强,但是需要包含头文件
重定向/管道:十七阿哥的博客《进程相关,管道》
1、文件操作报错信息打印
报错提示信息文件位置: vim /usr/include/asm-generic/errno.h
errno:自动打印报错信息,返回报错信息的代码.
printf("fopen failed.errno:%d",errno); //需要包含<errno.h>
perror() :函数显示您传给它的字符串,自动打印并翻译出来详细的错误信息(errno翻译).
peeror("fopen()");
strerror():返回一个指针,指针指向当前 errno 值的文本表示形式。
printf("fopen():%d\n",strerror(errno)); //需要包含<errno.h> <string.h>
fprintf(stderr,"Usage:%s srcfile destfile\n",argv[0]);
2、标准IO函数
*当一个进程被打开,默认有三个流stdin(标准输入) stdout(标准输出) stderr(标准输错) 被打开,类型为FILE 。
查看man手册
man 3 fopen //标准库函数在第三章
//看man手册:读概述(NAME),格式(SYNOPSIS),描述(DESCRIPTION,参数),返回值(RETURN VALUE),errors
//SEE ALSO(当前函数的关联函数)
(1).fopen() && fclose()
- fopen():
函数作用:使用给定的模式 mode 打开 filename 所指向的文件。
模式mode:以字符串的开头
就是一个标准函数(stdio),在windows下依赖系统调用函数openfile ,在linux下依赖系统调用函数open.
FILE *fopen(const char *pathname, const char *mode);
//返回值:成功-->文件描述符FILE * 失败-->返回NULL 设置全局变量 errno 来标识错误。
/* 打开模式
r:打开一个用于读取的文件,该文件必须存在。流被被定位到文件起始位置(第一个字节)
r+:打开一个用于更新的文件,可读取也可写入。该文件必须存在,流被定位到文件起始位置 (第一个字节)
w:创建一个用于写入的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件。
w+:以读和写的形式打开文件,文件有则清空,无则创建,流被定位到文件起始位置
a:追加到一个文件。以写入文件末尾的方式打开文件,流被定位到文件末尾位置
a+: 以读的方式,流被定位到文件起始位置,以写的方式,流被定位到文件末尾位置
新文件的默认权限: 0666 & ~uamsk
*/
执行fopen之后返回的指针指向堆,不可以在静态区,在静态区的话就只能打开一个文件,等他关闭之后才能再次打开第二个文件。也不可能在栈区,局部变量存放在栈上,需要一个申请了一个去释放。
代码展现:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
FILE *fp;
fp = fopen("tmp.txt","w");
if(fp == NULL)
{
// printf("fopen failed.errno:%d\n",errno);
// perror("fopen()");
printf("fopen():%s\n",strerror(errno));
exit(1);
}
else
{
puts("ok!");
fclose(fp);
}
exit(0);
}
- fclose():
int fclose(FILE *stream);
//返回值:成功-->0 失败-->EOF
(2).fscanf() && fprintf()
- fscanf():
函数作用:根据规定的数据格式 , 从输入流中读取数据 。
int fscanf(FILE * stream, const char * format, [argument...]);
- fprintf():
函数作用:根据规定的数据格式 , 向输出流中写出数据 。
int fprintf(FILE *stream, const char *format, ...);//返回值:成功-->返回写入的字符总数 失败-->返回一个负数fprintf(stderr,"Usage:%s srcfile destfile\n",argv[0]); //argv[0]终端执行的文件名
参数
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
- format – 这是 C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。
stdout
(3).fgetc() && fputc()
- fgetc():
函数作用:从文件流中读取一个字符(文件位置指针顺次向后移动)
int fgetc(FILE *stream); //返回值:成功-->读到的内容的ASLL码 失败--> -1(宏EOF)
- fputc():
函数作用:向文件中写出一个单个字符 ;
int fputc(int c, FILE *stream);//返回值:成功-->返回一个非负数 失败-->返回EOF宏//写入一个字符c,然后输出到指定的流stream中
代码实现字符拷贝:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc,char *argv[])
{
FILE *fps,*fpd;
int ch;
if(argc < 3)
{
fprintf(stderr,"Usage:%s srcfile destfile\n",argv[0]);
exit(1);
}
fps = fopen(argv[1],"r");
if(fps == NULL)
{
perror("fopen()");
exit(1);
}
fpd = fopen(argv[2],"w");
if(fpd == NULL)
{
fclose(fps);
perror("fopen()");
exit(1);
}
while((ch = fgetc(fps)) != EOF) //EOF = -1
fputc(ch,fpd);
fclose(fpd);
fclose(fps);
exit(0);
}
(4).fgets() && fputs()
- fgets():
函数作用:从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
char *fgets(char *s, int size, FILE *stream);
//s:可写的空间起始位置 stream->流文件
//返回值:成功-->s的指针 失败-->空指针
- fputs():
函数作用:把字符串写入到指定的流 stream 中,但不包括空字符。
int fputs(const char *s, FILE *stream);
//返回值:成功--> 写进去的内容的ASLL码 失败-->EOT
代码实现字符拷贝:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define BUFSIZE 128
int main(int argc,char *argv[])
{
FILE *fps,*fpd;
char buf[BUFSIZE];
if(argc < 3)
{
fprintf(stderr,"Usage:%s srcfile destfile\n",argv[0]);
exit(1);
}
fps = fopen(argv[1],"r");
if(fps == NULL)
{
perror("fopen()");
exit(1);
}
fpd = fopen(argv[2],"w");
if(fpd == NULL)
{
fclose(fps);
perror("fopen()");
exit(1);
}
while(fgets(buf,BUFSIZE,fps) != NULL) //一次读取一行
fputs(buf,fpd);
fclose(fpd);
fclose(fps);
exit(0);
}
(5).fread() && fwrite()
- fread():
函数功能:从给定流 stream读取数据到 ptr 所指向的数组中。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
//返回值:成功读取的对象nmemb个数
参数:
- ptr – 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
- size – 这是要读取的每个对象的大小,以字节为单位。
- nmemb – 这是读取对象的个数,每个对象的大小为 size 字节。
- stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
一般size用为
- fwrite():
函数功能:把 ptr 所指向的数组中的数据写入到给定流 stream 中。
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
//返回值:成功写入对象的个数
参数:
- ptr – 这是指向要被写入的元素数组的指针。代操作字符串首地址
- size – 这是要写入的每个对象的大小,以字节为单位。
- nmemb – 这是读取对象的个数,每个对象的大小为 size 字节。
- stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
代码展现:实现文件拷贝
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define BUFSIZE 1024
int main(int argc,char *argv[])
{
FILE *fps,*fpd;
char buf[BUFSIZE];
int n;
if(argc < 3)
{
fprintf(stderr,"Usage:%s srcfile destfile\n",argv[0]);
exit(1);
}
fps = fopen(argv[1],"r");
if(fps == NULL)
{
perror("fopen()");
exit(1);
}
fpd = fopen(argv[2],"w");
if(fpd == NULL)
{
fclose(fps);
perror("fopen()");
exit(1);
}
while((n = fread(buf,1,BUFSIZE,fps)) > 0) //每一次读BUFSIZE个字节
fwrite(buf,1,n,fpd);
fclose(fpd);
fclose(fps);
exit(0);
}
(6).fseek() && ftell() && rewind()
文件位置指针
- fseek()
函数功能:设置文件指针位置偏移offset个字节,参数 offset 意味着从给定的 whence 位置查找的字节数。
int fseek(FILE *stream, long offset, int whence);//offset:偏移量 whence:相对位置
fseek(fp,0,SEEK_END) //把指针定位到文件尾
参数:
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
- offset – 这是相对 whence 的偏移量,以字节为单位。(负号前移,正号后移。)
- whence – 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
常量 | 描述 |
---|---|
SEEK_SET | 文件的开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾 |
- ftell()
函数功能:反馈文件位置指针在哪,以字节为单位开始计数,从文件首开始。
long ftell(FILE *stream);
//stream:这是指向 FILE 对象的指针,该 FILE 对象标识了流。
//返回值:函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。
- rewind()
函数功能:设置文件位置为给定流 stream 的文件的开头。
void rewind(FILE *stream);//返回值:该函数不返回任何值。
函数测试:读取一个文件的大小
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc,char *argv[])
{
FILE *fp;
long int num;
fp = fopen(argv[1],"r+");
if(fp == NULL)
{
printf("fopen():%s\n",strerror(errno));
exit(1);
}
fseek(fp,0,SEEK_END); //把指针定位到文件尾
num = ftell(fp); //返回给定流 stream 的当前文件位置。
printf("%s size = %ld\n",argv[1],num);
fclose(fp);
exit(1);
}
(7).fflush()
函数功能:刷新流 stream 的输出缓冲区(掌握缓冲区的作用及分类)
int fflush(FILE *stream);//返回值:如果成功,该函数返回零值。如果发生错误,则返回 EOF,且设置错误标识符(即 feof)。
缓冲区:暂存空间,大多数情况下,缓冲区的存在是件好事,作用合并系统调用
行缓冲:stdout,换行时,满了时,强制刷新
全缓冲:默认,(只要不是终端设备,全采用全缓冲模式)满了时,强制刷新
无缓冲:stderr,需要立即输出
int main()
{
int i;
for(i = 0 ; i < 5; i++)
{
putchar('x');
sleep(1);
}
//putchar是标准IO,依赖于系统调用,有缓存区。
putchar('\n'); //调用exit(0); 5s后输出5个x
/*
int i= 1;
printf("Befor while()");
fflush(stdout); //强制刷新输出缓冲区
while(1);
printf("After while()");
*/
exit(0);
}
exit :调用若干个钩子函数,
(8).getline()
函数功能:用于读取一行 字符直到换行符,包括换行符(如果此行内容超出给定长度,自动重新申请内存)
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
//返回值成功:读到的字节个数 失败:-1,设置errno
参数:
- lineptr – 一块可写的数组起始位置
- n – 用来回调新申请后的空间大小
- stream – 文件流
3、拓展提升
1、查看一个程序最多能打开多少个文件
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
FILE *fp;
int i=0;
char filename[2000];
while(1)
{
sprintf(filename,"tmp%d.txt",i);
if((fp = fopen(filename,"r")) != NULL)
{
i++;
}
else
{
printf("fopen():%s\n",strerror(errno));
break;
}
//fclose(fp);
}
printf("max = %d\n",i);
}
//输出:1021
*当一个进程被打开,默认有三个流stdin(标准输入) stdout(标准输出) stderr(标准输错) 被打开,类型为FILE 。
2、ulimit:
man ulimit / setrlimit/getrlimit,以及相关链
查看或者设置已被调用的进程的限制
ulimit -a //列出所有的参数选项
ulimit -n //设置应许打开文件数
ulimit -H //设置硬件资源限制
ulimit -S //设置软件资源限制
ulimit -c -n -s //软限制
ulimit -c -n -s -H //硬限制
函数原型:
long ulimit(int cmd, long newlimit); //
备注:
1. unlimited表示no limit, 即内核的最大值
2. 当不指定limit的时候,该命令显示当前值。这里要注意的是,当你要修改limit的时候,如果不指定-S或者-H,默认是同时设置soft limit和hard limit。也就是之后设置时只能减不能增。所以,建议使用ulimit设置limit参数是加上-S。
3、setrlimit() && getrlimit()
获取或设置资源使用限制,linux下每种资源都有相关的软硬限制,软限制是内核强加给相应资源的限制值,硬限制是软限制的最大值。
int setrlimit(int resource, const struct rlimit *rlim);
int getrlimit(int resource, struct rlimit *rlim);
//两个函数返回值:若成功则返回0,若出错则返回非0值
//#include <sys/resource.h>
对这两个函数的每一次调用都会指定一个资源以及一个指向下列结构的指针。
struct rlimit {
rlim_t rlim_cur; /* soft limit: current limit */
rlim_t rlim_max; /* hard limit: maximum value for rlim_cur */
};
在更改资源限制时,须遵循下列三条规则:
(1)任何一个进程都可将一个软限制值更改为小于或等于其硬限制值。
(2)任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值。这种降低对普通用户而言是不可逆的。
(3)只有超级用户进程可以提高硬限制值。
4、面试题:请用3种不同的方法来获取文件长度
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc,char *argv[])
{
FILE *fp;
long int num;
fp = fopen(argv[1],"r+");
if(fp == NULL)
{
printf("fopen():%s\n",strerror(errno));
exit(1);
}
fseek(fp,0,SEEK_END);
num = ftell(fp);
printf("%s size = %ld\n",argv[1],num);
fclose(fp);
exit(1);
}
5、空洞文件:
在UNIX文件操作中,文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被设为 0。
fseek(fp,1024,SEEK_END)
6、读《UNIX环境高级编程 5.4 缓冲 5.8 5.13》
缓冲:
标准I/O的效率:
临时文件:
7、测试一次读取多少个对象,拷贝时间能最少
先创建一个5G的文件,然后一点点测试。