文件IO基本概念:
1、什么是文件IO?
文件:数据的载体,linux下一切皆文件
IO:input/output 输入/输出
linux下的七大类文件:(在linux终端可以通过ls -l来查看)
普通文件 - (1.txt 2.bmp 3.jpg 4.mp3 5.mp4、、、、)
目录文件 d
链接文件 l
管道文件 p
套接字文件 s
块设备文件 b
字符设备文件 c
所谓的文件IO指的就是对文件进行输入(写入)/输出(读取)的操作
2、如何实现对文件的读取和写入?
根据函数接口可以区分为两种方式
系统IO:系统提供函数接口
标准IO:标准C库提供函数接口
3、这两种IO有什么区别?
在文件处理方面:
系统IO:按字节处理,没有缓冲区
标准IO:按块处理,有缓冲区(例如:printf和scanf以及perror)
作用的对象:
系统IO:访问除目录文件的所有文件
标准IO:只能访问普通文件
4、目录文件为什么特殊?
因为linux下的文件之间的关系是索引关系,目录文件虽然对应着windows下的文件夹
但它和文件夹有本质的区别,文件夹是包含的关系,文件夹里面包含了文件,系统打开文件夹,找到里面的文件
而目录是索引关系,,目录里面记载了文件的索引信息,系统通过目录里保存的文件索引去找到文件进行操作
所以linux专门给目录文件设计了一套函数:目录IO
5、对文件的操作有哪些方式?
创建、打开、读取、写入、清空、关闭、、、、、
系统IO
1、打开文件: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);
参数:
const char *pathname:要打开的文件的路径名
int flags:以什么方式打开文件
必须包含以下三个的其中一,且只能有一个
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 可读可写
除了上面的,还可以继续添加其他选项
O_APPEND 以追加的方式打开
O_TRUNC 清空文件内容(前置条件:文件存在并且是普通文件且可写)
O_CREAT 如果文件不存在就创建(如果添加了这个选项,就要补充第三个参数:mode)
mode_t mode:如果要创建文件的话,给的权限值,按八进制给(一般给0777)
linux下的所有文件都有三组权限:文件所有者、所有者同组成员、不同组成员对文件的操作权限
rwx rwx rwx 对应三种不同成员对该文件的读、写、执行的权限,有字母表示可以按这种方式操作,没有就是不能
例如:rw-rw-rw- 表示所有成员对该文件可读可写但不可执行
一般权限值按二进制来看,1表示该位权限开启,0表示该位权限关闭,上面这个权限可以看做:110110110
操作文件权限时就按照8进制来操作,因为一组权限是三个,三个二进制刚好表示一个八进制
linux创建不同文件时会用初始权限值-umask掩码值来赋予被创建文件的权限
例如:普通文件是0666-0002=0664转换成八进制就是:110 110 100(rw- rw- r–)
目录文件是0777-0002=0774转换成八进制就是:111 111 100(rwx rwx r–)
返回值:
成功:新的文件描述符
失败:-1
什么是文件描述符?
在xlinux下打开的文件会用一个结构体来描述,而我们不能直接访问这个结构体
所以设计了一个结构体指针数组,里面的每一个成员都可以保存一个文件结构体的地址
所谓的文件描述符指的就是这个数组中"未被使用的最小的"下标,下标的取值范围是:0 ~ 1023
每个进程都最多可以同时打开1024个文件,但每个进程在一开始就会打开三个文件
标准输入(STDIN_FILENO)、标准输出(STDOUT_FILENO)、标准出错(STDERR_FILENO)
在打开文件后,后续的所有操作都是根据文件描述符来进行,这个文件描述符就代表这个文件
思考:如果打开文件时选择读/写的方式,但文件没有读写的权限,会如何?
结果:打开失败,因为open里的参数只是决定打开文件后以什么方式去操作,实际还要根据文件可支持的操作来决定
思考:如果文件不想继续使用,如何回收他的资源?
通过关闭文件来实现,关闭文件既是回收资源,也是回收文件描述符的操作
被回收的文件描述符将被分配给下一个被打开的文件
2、关闭文件:close
头文件:
#include <unistd.h>
函数原型:
int close(int fd);
参数:
int fd:要关闭的文件的文件描述符
返回值:
成功:0
失败:-1
3、读取文件:read
头文件:
#include <unistd.h>
函数原型:
ssize_t read(int fd, void *buf, size_t count);
参数:
int fd:要读取的文件的描述符
void *buf:保存读取到的数据的内存的地址
size_t count:一次要读取的字节数
从fd描述的文件中读取count个字节的数据存放到buf中
返回值:
成功:实际读取的字节数(返回0:从文件末尾开始读)
失败:-1
思考:如果文件内只有10字节,但count设置为20,结果如何?
结果:返回值为10,打印内容也是10字节
总结:read的期望读取字节数和实际读取字节数不一定相同,要根据文件中剩余可读取字节来决定
4、写入文件:write
头文件:
#include <unistd.h>
函数原型:
ssize_t write(int fd, const void *buf, size_t count);
参数:
int fd:要写入的文件描述符
const void *buf:存放要写入数据的内存的地址
size_t count:一次要写入的字节数
将buf中的count个字节数据写入fd描述的文件中
返回值:
成功:写入的字节数
失败:-1
思考:如果buf里只有10字节,但设置count为20,结果如何?
结果:返回值为20,文件里的内容除了那10个字节还有一些乱码或NULL
总结:write的期望写入字节数如果超过实际可写入字节,会强制写一些空数据到文件中
这些空数据在某些编辑器里无法识别,或者会被识别成乱码或空格
思考:有以下几种情况:
①先写入10字节数据,再读取10字节数据
结果:写入10字节成功,但没有读取到内容,read的返回值为0
②文件中有10字节数据,先读取5字节,再读取5字节
结果:分别读取了前5个字节和后5个字节数据
③文件中有10字节数据,先读取5字节,关闭文件再打开重新读取5字节
结果:两次读取的内容都是文件的前5个字节
④文件中有10字节数据,先读取5字节,再写入5字节,再读取5字节
结果:第一次读取成功,第二次读取为0,并且文件中原本数据的后面5字节变成了写入的5字节
原因:当我们打开文件后,对文件的操作默认是从文件开头进行的
因为打开的文件默认光标位于文件开头,随着我们对文件的操作开始移动
写入数据是按覆盖式来写入,如果这个位置已经有数据了,就覆盖掉
5、移动光标:lseek
头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
off_t lseek(int fd, off_t offset, int whence);
参数:
int fd:文件描述符
off_t offset:偏移量
>0:向后偏移
=0:停留在whence所在位置
<0:向前偏移
int whence:位置
SEEK_SET 文件开头位置
SEEK_CUR 光标当前位置
SEEK_END 文件末尾
将fd描述的文件中的光标从whence所在位置开始偏移offset个字节
返回值:
成功:当前光标距离文件开头的字节数
失败:-1
思考:lseek的返回值有什么意义?
将光标移动到文件末尾时就可以得到文件开头到末尾的字节数,也就是文件的大小
注意:在使用lseek求出文件大小后,记得将光标移动回去
6、虚拟映射:mmap
申请一块虚拟内存,将其映射向一块物理内存
头文件:
#include <sys/mman.h>
函数原型:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数:
void *addr:要申请的内存的起始地址,填NULL让系统内核来分配内存
size_t length:要申请的虚拟内存的大小
int prot:对虚拟内存的操作方式
PROT_READ 可读
PROT_WRITE 可写
int flags:虚拟内存的更新影响
MAP_SHARED 同步到物理内存
MAP_PRIVATE 只作用于虚拟内存
int fd:文件描述符
off_t offset:偏移量,设置为0
返回值:
成功:虚拟内存的地址
失败:MAP_FAILED
注意:使用完以后记得释放虚拟内存
函数:
int munmap(void *addr, size_t length);
参数:
void *addr:虚拟内存的地址
size_t length:要释放的大小
思考:如果文件只有读的权限,且是按只读的方式打开,那么映射时选择可读可写的方式来映射会这样?
结果:直接连映射都做不到
练习
复制一个文件到另一个文件
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#define EVERY_SIZE 1024
void copy(char *a,char *b)
{
int fb1=open(a,O_RDWR);//打开文件1
int fb2=open(b,O_RDWR);//打开文件2
while(fb1==-1||fb2==-1)
{
if(fb1==-1)
{
printf("file1打开失败,重新创建file1\n");
fb1=open(a,O_RDWR|O_CREAT);
}
if(fb2==-1)
{
printf("file2打开失败,重新创建file2\n");
fb2=open(b,O_RDWR|O_CREAT);
}
}
long size = lseek(fb1,0,SEEK_END);//计算文件1的大小
char *p = (char * )malloc(EVERY_SIZE);//在堆空间里申请的内存
lseek(fb1,0,SEEK_SET);//光标移到开头
int wte =0,red;
while(1)
{
lseek(fb1,0,SEEK_SET);
lseek(fb2,0,SEEK_SET);
while(red = read(fb1,p,EVERY_SIZE))
{
write(fb2,p,red);
}
char *str1=(char * )malloc(size),*str2=(char * )malloc(size);
read(fb1,str1,size);
read(fb2,str2,size);
if(strcmp(str1,str2)==0)//判断两文件读取数据是否相同
{
printf("复制完毕!\n");
close(fb1);
close(fb2);
free(p);free(str1);free(str2);
return ;
}
}
}
int main()
{
char *a=malloc(10);
char *b=malloc(10);
printf("请输入您要拷贝的文件原本和副本: ");
scanf("%s %s",a,b);
copy(a,b);
free(a);free(b);
return 0;
}
结果