一、系统IO和标准IO
- I/O:是一切实现的基础
- stdio标准IO
- sysio系统调用IO(文件IO)
1、stdio:FILE类型贯穿始终
2、sysio:fd是在文件IO中贯穿始终的类型,fd是文件描述符
3、文件描述符的概念:
(1)整型数,数组的下标,文件描述符优先使用当前可用范围内最小的
二、标准IO
(一)fopen()和fclose()
1、fopen()
#include <stdio.h>
FILE *fopen(const char *path,const char *mode);
- (1)fopen()函数返回值:是栈、静态区、堆的哪一个?
FILE *fopen(const char *path,const char *mode)//栈上猜测
{
FILE tmp;
tmp. = ;
...........
return &tmp;
}
FILE *fopen(const char *path,const char *mode)//静态区猜测
{
static FILE tmp;
tmp. = ;
...........
return &tmp;
}
FILE *fopen(const char *path,const char *mode)//放在堆上
{
FILE *tmp = NULL;
tmp = malloc(sizeof(FILE));
tmp-> = ;
...........
return tmp;
}
- 小结
栈(F) 静态区(F) 堆(Ture)
fopen();和fclose();是互逆操作,都是放在堆上 ———> 有互逆操作一定放在堆上
如果没有互逆操作的,就要自己用程序去判断是放在静态区还是堆上。 - (2)fopen()只读方式,查看文件报错信息
int main()
{
FILE *fp;
fp = fopen("tmp","r");
if (fp == NULL)
{
fprintf(stderr,"fopen() faild! errno = %d\n",errno);//errno已经被私有化
perror("fopen()");
fprintf(stderr, "fopen()%s\n", strerror(errno));
exit(1);
}
else
{
fputs("ok!\n",stdout);
fputs("OK\n",fp);
}
exit(0);
}
2、fclose()
int main()
{
FILE *fp;
fp = fopen("tmp","w");
if (fp == NULL)
{
fprintf(stderr,"fopen() faild! errno = %d\n",errno);//errno已经被私有化
perror("fopen()");
fprintf(stderr, "fopen()%s\n", strerror(errno));
exit(1);
}
puts("ok!");
fclose(fp);
exit(0);
}
3、文件打开信息
- (1)一个进程中,默认打开3个流 stream —> stdin、stdout、stderr
- (2)ulimit -a 控制打开的个数
- (3)新文件权限 产生的公式:
0666(8进制) & ~umask
- (4)umask 的值默认是0002(8进制),新建文件权限默认是:0664(8进制)
//maxfopen(),w 查看文件最多可以打开多少个
int main()
{
int count = 0;
FILE *fp = NULL;
while (1)
{
fp = fopen("tmp", "w");
if (fp == NULL)
{
perror("fopen()");
break;
}
count++;
}
printf("count=%d\n", count);//输出count=1021,因为默认打开3个流 stream ---> stdin stdout stderr
exit(0);
}
4、比较文件是否相同
- diff 文件名1 文件名2
(二)fgetc()和fputc()
1、fgetc()
//这节主要讲fgetc 可以用来统计文件的大小
int main(int argc,char **argv)
{
FILE *fp = NULL;
int count = 0;
if (argc < 2)
{
fprintf(stderr,"Usage:.....\n");
exit(1);
}
fp = fopen(argv[1],"r");
if (fp == NULL)
{
perror("fopen()");
exit(1);
}
while(fgetc(fp) != EOF)
{
count++;
}
printf("%d\n",count);
fclose(fp);
exit(0);
}
2、fgetc()和fputc()联合使用
- 通过fgetc()和fputc()函数章节,实现文件内容以字符操作方式
int main(int argc,char **argv)
{
FILE *fps = NULL;
FILE *fpd = NULL;
int ch;
if (argc < 3) //必须是3个参数
{
fprintf(stderr,"Usage:<s_file> <d_file>\n");
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 (1)
{
ch = fgetc(fps);
if(ch == EOF)
{
break;
}
fputc(ch, fpd);
}
fclose(fpd);//先关目标文件,再关源文件
fclose(fps);
exit(0);
}
(三)fgets()和fputs()
- 功能:实现文件内容以字符串操作方式
1、fgets()函数详解
//gets()函数不要使用,C基础中有讲
//char *fgets(char *s, int size, FILE *stream);
#define SIZE 5
char bug[SIZE];
fgets(buf,SIZE,stream);
//fgets遇到 size-1 or '\n'停止
(1)文件内容abcdef 读SIZE-1
1 -----> a b c d '\0'
(2)文件内容ab 读SIZE-1
2 -----> a b '\n' '\0'
(3)文件内容abcd 擦边球 用fgets(buf,SIZE,stream);来读取,读取两次
1-> a b c d '\0'
2-> '\n' '\0' ....
2、fgets()和fputs()函数进行读写
#define BUFSIZE 1024
int main(int argc,char **argv)
{
FILE *fps = NULL;
FILE *fpd = NULL;
char buf[BUFSIZE];
if (argc < 3) //必须是3个参数
{
fprintf(stderr,"Usage:<s_file> <d_file>\n");
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);
}
(四)fread()和fwrite()函数
1、fread()函数详解
- fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- fread保险起见,size=1个字节方式读
//1、知识点草稿
//fread(buf, size, nmemb, fp);
1 ---> 数据量足够 读一次 保险起见多1个字节方式 ********
fread(buf,1,10,fp) ---> 10个对象,读到10个字节
fread(buf,10,1,fp) ---> 1个对象, 读到10个字节
2 ---> 只有5个字节
fread(buf,1,10,fp) ---> 5个对象,读到5个字节
fread(buf,10,1,fp) ---> 0个对象,文件剩余多少???不知道
2、fread()和fwrite()函数进行读写
#define BUFSIZE 1024
int main(int argc,char **argv)
{
FILE *fps = NULL;
FILE *fpd = NULL;
char buf[BUFSIZE];
int n;
if (argc < 3) //必须是3个参数
{
fprintf(stderr,"Usage:<s_file> <d_file>\n");
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) //返回读到的字节数,然后写n个字节
{
printf("%d\n", n);
fwrite(buf, 1, n, fpd);
}
fclose(fpd);//先关目标文件,再关源文件
fclose(fps);
exit(0);
}
(五)printf()和scanf()函数
1、引申 sprintf atoi**
(1)atoi函数使用
int main()
{
char str[] = "123a456";
printf("%d\n", atoi(str));
exit(0);
}
(2)sprintf函数使用
int main()
{
char buf[1024];
int y = 2022, m = 3, d = 26;
sprintf(buf,"%d-%d-%d",y,m,d);
puts(buf);
exit(0);
}
(六)fseek()和ftell()函数
1、fseek()和ftell()函数的缺陷
- int fseek(FILE *stream, long offset, int whence);//设置文件指针位置 offset是偏移位置
- long ftell(FILE *stream);//返回文件指针位置
- 大小有局限性,因为long的大小在不同编译器中不确定
(1)通过 fseek()和ftell() 两个函数可以读取 文件大小
int main(int argc,char **argv)
{
FILE *fp = NULL;
long count;
if (argc < 2)
{
fprintf(stderr,"Usage:.....\n");
exit(1);
}
fp = fopen(argv[1],"r");
if (fp == NULL)
{
perror("fopen()");
exit(1);
}
fseek(fp, 0, SEEK_END); //SEEK_SET, SEEK_CUR, or SEEK_END
count = ftell(fp);
printf("%ld\n",count);
fclose(fp);
exit(0);
}
2、fwind()函数
- void rewind(FILE *stream);//使得文件指针回到文件开始位置
- rewind是对fseek的封装:(void) fseek(stream, 0L, SEEK_SET)
3、文件指针
(1)何谓文件指针
就像读书时眼睛移动一样,文件指针逐行移动
(2)什么时候用?
对一个文件先写后读的时候,比如:
- FILE *fp = fopen();
- fputc(fp); //放入10个字节,文件中有个文件位置指针,是指向最后一个字节的
- fgetc(fp); //所以,再读取10个字节,无法得到刚刚写入的东西(指针已经向后偏移了)
(七)fseeko()和ftello()函数的出现
- int fseeko(FILE *stream, off_t offset, int whence);
- off_t ftello(FILE *stream);
1、弥补了fseek()和ftell()缺陷
2、off_t的大小可以在makefile中加入宏定义: CFLAGS+=-D_FILE_OFFSET_BITS=64
(八)标准IO的缓冲区和fflush()函数
缓冲区的作用:大多数情况下是好事,合并系统调用
1、行缓冲
换行时候刷新,满了的时候刷新,强制刷新(标准输出都是这样的,因为是终端设备) stdout
2、全缓冲
(1)满了的时候刷新,强制刷新(默认,只要不是终端设备都是全缓冲)
(2)现在的"\n"只是起到了换行作用
(3)缓冲区可以改的函数 setvbuf()
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
3、无缓冲
如:stderr,需要立即输出的内容
4、缓冲区的实例说明
(1) 缓冲区不够,不输出
//无法输出Before while
int main()
{
printf("Before while");
while(1)
{
;
}
printf("After while");
exit(0);
}
(2) 行缓冲输出方式
//末尾加换行符,可以输出Before while
int main()
{
printf("Before while\n");
while (1)
{
;
}
printf("After while\n");
exit(0);
}
(3)无缓冲输出方式
//使用fflush()函数可以输出Before while
int main()
{
printf("Before while");
fflush(stdout);
while (1)
{
;
}
printf("After while");
fflush(NULL);
exit(0);
}
(九)getline()函数
- 作用:取到完整的一行函数
- getline()内部封装形式: malloc() realloc() realloc()…realloc()
- getline()属于可控内存泄漏 还有不可控的内存泄漏:服务器一直运行下去
- 使用getline()时,在makefile里面加-D_GNU_SOURCE 或者 函数里引用 #define _GNU_SOURCE
int main(int argc, char **argv)
{
FILE *fp = NULL;
char *linebuf;
size_t linesize;
if (argc < 2)
{
fprintf(stderr,"Usage:<s_file> <d_file>\n");
exit(1);
}
fp = fopen(argv[1], "r");
if(fp == NULL)
{
perror("fopen()");
exit(1);
}
printf("%s——%d——%s\n", __FILE__, __LINE__, __FUNCTION__);
linebuf = NULL; //加这句很重要!!!!!
linesize = 0; //加这句话很重!!!!!
while (1)
{
if(getline(&linebuf, &linesize, fp) < 0)//getline()存在可控内存泄漏
{
break;
}
printf("%ld\n", strlen(linebuf));
printf("%ld\n", linesize);
}
fclose(fp);
exit(0);
}
(十)临时文件
1、记住两点,①如何不冲突,②及时销毁
(1)tmpnam():不合适,容易多个人同时拿到
(2)tmpfile(): 合适,无文件名,不冲突,只要进程不是无休止的运行,不用fclose()也可以释放
(3)用fclose()可以及时销毁!!!!!
三、系统IO
(一)open()和close()
1、文件的拷贝
#define BUFSIZE 1024
int main(int argc, char **argv)
{
int sfd, dfd;
char buf[BUFSIZE];
int len, ret, pos;
if (argc < 3)
{
fprintf(stderr, "Usage ....\n");
exit(1);
}
sfd = open(argv[1], O_RDONLY);
if (sfd < 0)
{
fprintf(stderr, "sfd open = %d\n", errno);
perror("open()");
exit(0);
}
dfd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (dfd < 0)
{
close(sfd);
fprintf(stderr, "dfd open = %d\n", errno);
exit(0);
}
while (1)
{
len = read(sfd, buf, BUFSIZE);
if(len < 0)
{
perror("read()");
break;
}
if(len == 0)
break;
pos = 0;
while (len > 0)
{
ret = write(dfd, buf + pos, len);
if (ret < 0)
{
perror("write()");
close(dfd);
close(sfd);
exit(1);
}
pos = ret;
len -= ret;
}
}
close(dfd);
close(sfd);
exit(0);
}
//139运行测试
//1、 ./139ReadWrite /etc/services ./out
//2、 diff /etc/services ./out ————> 比较文件是否一样
//140运行测试
//1、 time ./139ReadWrite /etc/services ./out 当前进程执行所要的时间
//(1)运行结果:
// real 0m0.001s
// user 0m0.001s
// sys 0m0.000s
//(2)时间:
// real = user + sys + (调度等待时间)
// 程序员解决是user和sys的时间
(二)文件IO与系统IO的区别
1、举例
快递员送快递,分拣后批量送快递、单个快递送
2、区别
响应速度(系统IO)&吞吐量(标准IO的缓冲区)
3、如何使一个程序更快
结合响应速度和吞吐量回答
4、转换
fileno()和fdopen()
5、注意:标准IO与文件IO不可以混用
(1)FILE *fp; fputc —> pos++ ; fputc —> pos++
(2)标准IO的pos有缓冲区,pos指针移动,不会影响系统IO的pos。
举例:word保存与不保存。
(3)系统IO的pos是等到标准IO缓冲区满,才会使系统IO的pos指针移动
6、通过strace ./xxx
strace可以用来查看一个可执行文件的系统调用
//1、 strace ./140StdioSysioDiff 运行后可以看到标准IO存在缓冲区,最后输出项
int main()
{
printf("a");
write(1, "b", 1);
printf("a");
write(1, "b", 1);
printf("a");
write(1, "b", 1);
printf("\n");
exit(0);
}
//更好的验证stdio的缓冲区
//2、 ./140StdioSysioDiff 可以验证ulimit -a 内容里面的数值
// ulimit -a中的限制open files (-n) 1024
int main()
{
for (int i = 0; i < 1025; i++)
{
printf("a");
write(1, "b", 1);
}
printf("\n");
exit(0);
}
(三)IO效率问题
1、习题
将mycpy.c程序进行更改,将BUFSIZE的值一直放大,并观察进程所消耗的时间,注意性能最佳拐点出现时的BUFSIZE值,以及何时程序会出现问题。
(四)文件共享
1、多个任务共同操作一个文件或者协同完成任务
(1)面试:写程序删除一个文件的第10行
补充函数:truncate() / ftruncate
(五)原子操作
1、不可分割的操作
- 作用: 解决竞争和冲突,比如tmpnam的操作,不是原子
(六)程序的重定向dup和dup2
1、dup的作用
int dup(int oldfd); 将传入的文件描述符复制到(未使用的)最小新文件描述符
(1)close(1)是关闭stdout流 原始办法来实现输出到文件中
#define FNAME "./out"
int main()
{
int fd;
close(1);
fd = open(FNAME, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if(fd < 0)
{
perror("open()");
exit(0);
}
/********************************************/
puts("hello!");
exit(0);
}
(2)使用dup函数,这个程序存在两种情况,主要因为dup函数不是原子操作
①dup() 假设天生只有0、2,没有1,那么函数中fd本身创建好就是1
②旁边的兄弟也在运行,也突然创建一个,那么就到1上去了,你本身是3最多副本占据到4
#define FNAME "./out"
int main()
{
int fd;
fd = open(FNAME, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if(fd < 0)
{
perror("open()");
exit(0);
}
close(1);
dup(fd);
close(fd);
/********************************************/
puts("hello!");
exit(0);
}
2、dup2的函数
(1)、dup2属于原子操作方式
//实现输出到终端信息的内容,输出到对应的文件中
#define FNAME "./out"
int main()
{
int fd;
fd = open(FNAME, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if(fd < 0)
{
perror("open()");
exit(0);
}
dup2(fd, 1);//相当于这两句操作:close(1);dup(fd);
if (fd != 1)
{
close(fd);
}
/********************************************/
puts("hello!");
exit(0);
(七)同步
1、sync设备即将解除挂载时进行全局催促,将buffer cache的数据刷新
2、fsync刷新文件的数据,并更新文件的属性
3、fdatasync函数只刷新文件的数据部分,而除数据外的文件属性(文件的时间)不更新
(八)Linux的ioctl和fcntl
1、fcntl函数
管家级函数,文件描述符所变得魔术几乎都来源于该函数
2、ioctl函数
(1)设备相关的内容
(2)ioctl_list是跟设备打交道
(九)虚目录
1、/dev/fd/ 显示的是当前进程的文件描述符信息
2、ls -l /dev/fd/ 看到谁就是查看谁,即在看ls