一、文件IO概念
1、什么是文件
在Linux下,一切皆文件。
除了我们平时常见的文件,如:1.txt 、2.jpg 、3.bmp ...... 这些文件之外,Linux系统中还会将硬件设备当做是文件,比如:LED灯、蜂鸣器、LCD液晶屏、触摸屏...这些硬件设备在Linux眼里都是文件。
2、什么是IO
IO ---》inoput / output -->输入和输出
所谓的文件IO ---》对文件数据的输入和输出 ---》将数据写入到文件(输入) 从文件中读取数据出来(输出)
3、如何实现文件读取/写入?
不需要用户自定义函数实现,因为linux中,已经有现成的函数来实现。
访问文件的方式有两种:
(1)系统IO
open read write
系统IO ---》系统调用 ---2 System calls (functions provided by the kernel) --->系统IO接口
(2)标准IO
fopen fread fwrite fclose
标准IO --->标准C库的接口 ---3 Library calls (functions within program libraries)
4、系统IO 与 标准IO 的区别
(1)系统IO是 linux内核提供给应用层开发的接口,标准IO 是标准C库里面的接口。实际上,标准IO是 系统IO的封装。
(2) 系统IO处理文件,没有缓冲区,直接按字节来处理。标准IO 处理文件,有缓冲区,数据按块来处理。
(3)作用对象:
访问硬件设备(lcd液晶屏、触摸屏、蜂鸣器...) ----》使用系统IO来处理。
比如: 触摸屏 --》使用系统IO接口来处理 触摸屏的数据
访问普通文件(1.txt 2.jpg 3.bmp...)--->使用标准IO(接口功能丰富)来处理
比如:访问 :1.txt文件 ---》使用 标准IO
5、文件类型(7种)
- 普通文件 ---》标准IO
d 目标文件 ---》标准IO中的目录IO
l 链接文件
p 管道文件 ---》系统IO 进程之间的通信方式
s 套接字文件 --》 网络编程
c 字符设备文件 ---》系统IO
b 块设备文件 - ---系统IO
二、如何使用系统IO来访问文件
1、打开文件(open)
打开文件 ---->man 2 open
头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函数作用:
打开一个文件或者创建一个文件
函数参数:
pathname --》你要打开文件的路径名 路径+名字 路径:相对路径 绝对路径
./1.txt /home/gec/1.txt
flags ---》你要以什么方式打开这个文件
只能三选一
O_RDONLY readonly 只读
O_WRONLY writeonly 只写
O_RDWR. readwrite 可读可写
比如: 以可读的方式 打开当前目录下的1.txt
open("1.txt",O_RDONLY);
函数返回值:
成功返回 一个文件描述符fd(这个文件描述符就是文件的代号,用来代表这个打开的文件)
失败返回 -1
2、关闭文件 --回收文件描述符资源(close)
头文件:
#include <unistd.h>
函数原型:
int close(int fd);
函数参数:
fd ---》你要关闭哪个文件,将这个文件的文件描述符传递进来即可
函数返回值:
成功返回 0
失败 返回 -1
3、相关编程练习
(1)练习1:自己用编程实现,打开一个文件,并且关闭。如何判断 是否成功打开一个文件或者关闭文件,使用返回值进行判断。
注意: open函数 什么时候会打开失败??
1)你要打开的这个文件不存在
2)如果文件本身的权限不允许,那么操作权限不对,也会失败。
比如 1.txt -wx-wx-wx 没有可读权限,此时以O_RDONLY的方式打开会失败
//1-open.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
//打开当前目录下的1.txt
int fd = open("./1.txt", O_RDONLY);
if(fd != -1)//说明打开成功
{
printf("open 1.txt succuss\n");
}else{
printf("open 1.txt error\n");
}
printf("fd:%d\n",fd);
//关闭文件
int ret = close(fd);
if(ret != -1)
{
printf("close 1.txt succuss\n");
}else{
printf("close 1.txt error\n");
}
return 0;
}
gcc编译后的运行结果如下:
(2)打开多个文件,自己打印,看看文件描述符的值是多少???
//2-open.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
//打开当前目录下的1.txt
int fd1 = open("./1.txt", O_RDONLY);
if(fd1 != -1)//说明打开成功
{
printf("open 1.txt succuss\n");
}else{
printf("open 1.txt error\n");
}
int fd2 = open("./2.txt", O_RDONLY);
if(fd2 != -1)//说明打开成功
{
printf("open 1.txt succuss\n");
}else{
printf("open 1.txt error\n");
}
printf("fd1:%d\n",fd1); //fd1 = 3
printf("fd2:%d\n",fd2); //fd2 = 4
//关闭文件
int ret1 = close(fd1);
if(ret1 != -1)
{
printf("close 1.txt succuss\n");
}else{
printf("close 1.txt error\n");
}
//关闭文件
int ret2 = close(fd2);
if(ret2 != -1)
{
printf("close 1.txt succuss\n");
}else{
printf("close 1.txt error\n");
}
return 0;
}
编译运行,结果如下:
(3)实现打开文件时,当文件不存在时,去创建这个文件
//3-open.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
//打开当前目录下的a.txt,如果该文件不存在,则创建
//如果该文件已经存在,则清空里面的内容
int fd = open("./a.txt",O_RDWR|O_CREAT|O_TRUNC,0777);
if(fd == -1)
{
printf("open a.txt error");
return -1;
}
close(fd);
return 0;
}
编译运行,结果如下所示:
4、思考:为什么会是这个值?
下面文件描述符的分配会讲解这个值是怎么来的。
三、文件描述符的分配
1、什么是文件描述符?
文件描述符fd是open函数的返回值,当open()函数执行成功后,就会返回 一个非负、最小的、没有使用过的整数。
如:
3 = open("1.txt");
4 = open("2.txt");
结论:将来处理文件时,我们不需要提供文件的名字,只需要提供文件对应的文件描述符即可。
2、访问文件时 ,fd从 3开始分配,说明 0/1/2已经被占用,到底是谁用了?
其实在系统启动的时候,就会默认打开3个文件,分别是 “标准输入” “标准输出” "标准出错"
这些其实就是 宏定义 ,在/usr/include/unistd.h 头文件中:
/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */ “标准输入” ---》对象:键盘
#define STDOUT_FILENO 1 /* Standard output. */ “标准输出” ---》对象:屏幕
#define STDERR_FILENO 2 /* Standard error output. */ "标准出错" ---》对象:屏幕
可以理解为 : 0 = open("标准输入");
fd是 一个非负、最小的、没有使用过的整数。
结论:
打开一个文件,得到一个文件描述符
关闭一个文件,这个文件对应的文件描述符可以被别人使用
3、研究文件描述符的最大值
while(1)
{
int fd = open("./1.txt", O_RDONLY);
if(fd == -1)
{
printf("open error:%d\n",fd);
break;
}
printf("fd:%d\n",fd);
}
运行结果:
范围: 0-1023 //其中 0 1 2 是已经被系统占用了
记住:文件描述符的资源有限,打开 文件之后,记得一定要关闭。
四、open函数的拓展参数
int open(const char *pathname, int flags, mode_t mode);
flags ---》你要以什么方式打开这个文件
只能三选一
O_RDONLY readonly 只读
O_WRONLY writeonly 只写
O_RDWR. readwrite 可读可写
以下还有一些参数 可以跟上面使用 位或 | 并存
O_CREAT --》如果你打开的那个文件不存在,那么就会创建。
---》如果flags 有O_CREAT,那么第三个参数一定要填。
mode:文件的权限 0777 0666
---》如果flags 没有O_CREAT,第三参数 你填了没用。
比如:
//打开当前目录下的a.txt,如果该文件不存在,则创建
int fd = open("./a.txt",O_RDWR|O_CREAT,0777);
O_TRUNC ---》如果文件已经存在并且是一个普通文件,而且打开的方式必须是(O_WRONLY或者是O_RDWR)
那么这个文件 就会打开的时候会被清空
比如:
//打开当前目录下的a.txt,如果该文件不存在,则创建
//如果该文件已经存在,则清空里面的内容
int fd = open("./a.txt",O_RDWR|O_CREAT|O_TRUNC,0777);
O_APPEND---》 以追加的方式打开文件。也就是说,默认打开文件的时候,文件光标位置是在开头,
那么使用了这个宏O_APPEND,在打开文件的同时,把文件光标的位置偏移到文件的尾部。
五、文件数据的输出、输出
1、如何读取文件的数据?(read)
man 2 read
头文件:
#include <unistd.h>
函数原型:
ssize_t read(int fd, void *buf, size_t count);
函数作用:从文件fd中读取数据,存储缓冲区buf,读取 的大小为count
参数:
fd--->你要读取的那个文件的文件描述符
buf--> 读取的数据,存储在这里buf
count-->尝试读取的大小,以字节为单位
返回值:
成功返回 从文件中读取到的字节数
失败 返回 -1
验证: 打开一个文件之后,读取文件的数据,接着再次读取,这个时候是从头开始读 还是 说 接着 后面继续读取呢????
答案:继续读取。因为文件光标会进行偏移。
//4-read.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
int main()
{
//打开a.txt文件,从文件中读取数据
int fd = open("a.txt",O_RDONLY);
if(fd == -1)
{
printf("open error\n");
return -1;
}
char buf[1024];
int r_ret;
while(1)
{
bzero(buf,1024);//清空数组内存空间
r_ret = read(fd,buf,10);
if(r_ret == 0)//已经读完了
{
printf("\nfile read end\n");
break;
}
printf("r_ret:%d\n",r_ret);
printf("%s\n",buf);
}
close(fd);
return 0;
}
运行结果如下:
注意:
1)从任何的文件中读取数据出来,都是以字符串的形式存在的。
2)如果一个文件很大,可以写一个循环每次获取指定的字节数,当read返回值 为 0时,说明这个文件已经读完了,则退出。
2、如何将数据写入文件中?(write)
man 2 write
头文件:
#include <unistd.h>
函数原型:
ssize_t write(int fd, const void *buf, size_t count);
函数作用:将指定的数据buf 写入到指定的文件fd中,写入的数据为count字节
参数:
fd ---》你要写入哪个文件,将这个文件的文件描述符传递进来
buf ---》你要写入的数据
count--》你写入数据的大小
返回值:
成功返回 写入的字节数
失败返回 -1
注意:
1)有多少个字节的数据,第三个参数count就写多少。如果你写入的字节数count比真实的数据要大,那么文本的后面会有乱码。(文本不够,乱码来凑)
//5-write.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
int main()
{
//以可写的方式打开a.txt文件,如果这个文件不存在则新建,如果存在了则清空内容
//int fd = open("a.txt",O_WRONLY|O_CREAT|O_TRUNC,0777);
//以追加的方式打开文件,也就是文件光标的位置在文件的尾部
int fd = open("a.txt",O_WRONLY|O_APPEND);
if(fd == -1)
{
printf("open error\n");
return -1;
}
int w_ret;
char buf[ ] = "hello";
w_ret = write(fd,buf,strlen(buf));
strcpy(buf,"world");
w_ret = write(fd,buf,strlen(buf));
close(fd);
return 0;
}
运行结果:
验证:打开一个文件之后,(第一次)将数据写入到文本中,接着(第二次)再将其他数据写入到文本中
那么,第二次写入的数据,是从头开始写呢 还是接着后面写?? --接着后面写
int w_ret;
char buf[ ] = "hello";
w_ret = write(fd,buf,strlen(buf));
strcpy(buf,"world");
w_ret = write(fd,buf,strlen(buf));
结论: open函数打开文件时,文件定位都是在开头,文件定位(文件光标的位置)随着 读取/写入字节 而往后面进行偏移。
六、练习题
1、练习题1
写一个程序,实现以下功能:
注册:
从键盘上输入账号,保存在login.txt文本中
从键盘上输入密码,保存在passwd.txt文本中
登录:
输入账号的时候,验证与 login.txt文本 中的内容是否一样
如果不一样,则提示账号输入错误
输入密码的时候,验证与 passwd.txt文本 中的内容是否一样
如果不一样,则提示密码输入错误2
//test1.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <stdbool.h>
enum {
Register = 1,
Login = 2,
Exit = 3,
};
#define ACOUNTFILE "./login.txt"
#define PASSWDFILE "./passwd.txt"
//#define DEBUG
/*
1、写一个程序,实现以下功能:
注册:
从键盘上输入账号,保存在login.txt文本中
从键盘上输入密码,保存在passwd.txt文本中
登录:
输入账号的时候,验证与 login.txt文本 中的内容是否一样
如果不一样,则提示账号输入错误
输入密码的时候,验证与 passwd.txt文本 中的内容是否一样
如果不一样,则提示密码输入错误
如果不一样,则提示密码输入错误
*/
//注册功能
bool registerFunc()
{
//0、从键盘上获取注册的账号和密码
char registerAcount[256]={0};
printf("请注册账号:");
scanf("%s",registerAcount);
char registerPasswd[256]={0};
printf("请注册密码:");
scanf("%s",registerPasswd);
//1、先打开文本,如果文本不存在,则新建,如果存在了,清空
int loginfd = open(ACOUNTFILE,O_WRONLY|O_CREAT|O_TRUNC,0777);
if(loginfd == -1)
{
printf("open login.txt error\n");
return false;
}
int passwdfd = open(PASSWDFILE,O_WRONLY|O_CREAT|O_TRUNC,0777);
if(passwdfd == -1)
{
printf("open passwd.txt error\n");
return false;
}
//2、将刚才从键盘上输入的账号和密码写入文本中
write(loginfd,registerAcount,strlen(registerAcount));
write(passwdfd,registerPasswd,strlen(registerPasswd));
//3、关闭
close(loginfd);
close(passwdfd);
}
//登录功能
bool loginFunc()
{
//1、从键盘上获取输入的账号
char loginAcount[256]={0};
printf("请输入账号:");
scanf("%s",loginAcount);
//2、 打开存在login.txt文本,读取注册的账号
int loginfd = open(ACOUNTFILE,O_RDONLY);
if(loginfd == -1)
{
printf("open login.txt error\n");
return false;
}
//从文本中 读取注册的账号
char registerAcount[256]={0};
read(loginfd,registerAcount,256);
#ifdef DEBUG
printf("loginAcount:%s\n",loginAcount);
printf("registerAcount:%s\n",registerAcount);
#endif
//3、从键盘上获取的账号 和 文本中的账号 进行 比较,如果不相等,则提示账号错误
if(strcmp(loginAcount,registerAcount) != 0)
{
printf("你输入的账号错误,登录失败\n");
return false;
}
//1、从键盘上获取输入的密码
char loginPasswd[256]={0};
printf("请输入密码:");
scanf("%s",loginPasswd);
//2、 打开存在passwd.txt文本,读取注册的密码
int passwdfd = open(PASSWDFILE,O_RDONLY);
if(passwdfd == -1)
{
printf("open passwd.txt error\n");
return false;
}
//从文本中 读取注册的密码
char registerAasswd[256]={0};
read(passwdfd,registerAasswd,256);
//3、从键盘上获取的密码 和 文本中的密码 进行 比较,如果不相等,则提示账号错误
if(strcmp(loginPasswd,registerAasswd) != 0)
{
printf("你输入的密码错误,登录失败\n");
return false;
}
return true;
}
int main()
{
bool exitFlag = false;
while(1)
{
int mode=0;
printf("[1]注册 [2]:登录 [3]退出:");
scanf("%d",&mode);
#ifdef DEBUG
printf("mode:%d\n",mode);
#endif
switch(mode)
{
case Register:
registerFunc();
break;
case Login:
if(loginFunc())
printf("-----登录成功----\n");
else
printf("-----登录失败-----\n");
break;
case Exit:
exitFlag = true;
break;
}
if(exitFlag)
break;
}
return 0;
}
2、练习题2
学生信息结构体
struct student
{
char name[15];
int num;
char sex;
};
struct student lisi = {"lisi", 20, 'B'};
写两个接口:
1)保存
从键盘上获取一个学生的信息(结构体变量的数据),保存到info.txt文本
2)初始化读取
程序一开始运行的时候要进行一些初始化操作,这个时候从info.txt文本中读取数据,进行初始化
//test2.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#define INFOFILE "./info.txt"
enum {
Print = 1,
Change = 2,
Exit = 3,
};
/* 2、学生信息结构体
struct student
{
char name[15];
int age;
char sex;
};
struct student lisi = {"lisi", 20, 'B'};
写两个接口:
1)保存
从键盘上获取一个学生的信息(结构体变量的数据),保存到info.txt文本
2)初始化读取
程序一开始运行的时候要进行一些初始化操作,
这个时候从info.txt文本中读取数据,进行初始化 */
struct student
{
char name[256];
int age;
char sex;
};
struct student lisi;
bool saveInfo();
bool initData();
void printFunc()
{
printf("name:%s age:%d sex:%c\n",lisi.name,lisi.age,lisi.sex);
}
//修改功能
void changeFunc()
{
printf("请输入姓名:");
scanf("%s",lisi.name);
printf("请输入年龄:");
scanf("%d",&lisi.age);
//吸收换行符 // 20\n
getchar();
printf("请输入性别:");
scanf("%c",&lisi.sex);
//保存
saveInfo();
}
int main()
{
bool exitFlag = false;
//从文本中加载学生的信息
initData();
while(1)
{
int mode=0;
printf("[1]显示 [2]:修改 [3]退出:");
int ret = scanf("%d",&mode); //scanf 成功返回 获取到的变量的个数
if(ret != 1)
{
//清空输入缓冲区
while(getchar() != '\n');
continue;
}
switch(mode)
{
case Print:
printFunc();
break;
case Change:
changeFunc();
break;
case Exit:
exitFlag = true;
break;
}
if(exitFlag)
break;
}
return 0;
}
//保存学生信息
bool saveInfo()
{
//打开信息文本,如果存在了清空,如果不存在则创建
int fd = open(INFOFILE,O_WRONLY|O_CREAT|O_TRUNC,0777);
if(fd == -1)
{
printf("open %s error\n",INFOFILE);
return false;
}
write(fd,&lisi,sizeof(struct student));
close(fd);
}
//初始化显示
bool initData()
{
int fd = open(INFOFILE,O_RDONLY);
if(fd == -1)
{
printf("open %s error\n",INFOFILE);
return false;
}
read(fd, &lisi, sizeof(struct student));
close(fd);
}
3、练习题3
实现复制一个文件
类似于 ./mycp old_file new_file
//test3.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#define INFOFILE "./info.txt"
#define MALLOCSIZE (1024*1024*10)
/* 实现复制一个文件
类似于 ./mycp old_file new_file */
/*
getFileSize:得到文件的大小
参数:
fd-->文件描述符
返回值:
返回文件的大小,以字节为单位
*/
int getFileSize(int fd)
{
int fileSize =0;
//1、先保存此时文件的光标位置
int pos = lseek(fd,0,SEEK_CUR);//current
//2、将文件设置到末尾 ,该lseek函数返回值就是文件的大小
fileSize = lseek(fd,0,SEEK_END);
//3、恢复文件之前的光标位置(还原)
lseek(fd,pos,SEEK_SET);
return fileSize;
}
int main(int argc,char*argv[]) //./tes3 1.txt 2.txt
{
float readByteCount=0;//记录读取的字节数
if(argc<3)
{
printf("你输入的参数太少了,请把源文件 和 拷贝文件名传递进来\n");
return -1;
}
//1、以可读的方式打开源文件 1.txt argv[1]
int srcfd = open(argv[1],O_RDONLY);
if(srcfd == -1)
{
printf("open %s error\n",argv[1]);
return -1;
}
//2、以可写的方式打开目标文件 2.txt ,如果目标文件不存在则新建,如果存在则清空
int destfd = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0777);
if(destfd == -1)
{
printf("open %s error\n",argv[2]);
return -1;
}
//申请内存空间存储读取的数据
char *buf = malloc(MALLOCSIZE);//申请1M的空间
if(buf == NULL)
{
printf("malloc error\n");
return -1;
}
//获取源文件的大小
int srcfileSize = getFileSize(srcfd);
printf("srcfileSize:%d\n",srcfileSize);
while(1)
{
//先清空
bzero(buf,MALLOCSIZE);
//3、读取源文件的数据
int r_byte = read(srcfd,buf,MALLOCSIZE);
if(r_byte == 0)//读取完毕
{
printf("%s file read end\n",argv[1]);
break;
}
//累加读取到的字节数
readByteCount +=r_byte;
//4、将读取到的源文件数据 写入到 目标文件中
write(destfd,buf,r_byte);
printf("已读取:%fM 进度:%f%%\n",readByteCount/1024/1024,readByteCount/srcfileSize*100); // 1M = 1024kb 1kb = 1024字节
}
//5、写完了,关闭两个文件
close(srcfd);
close(destfd);
//释放内存空间
free(buf);
return 0;
}