title: Linux下IO操作
date: 2019-07-16 15:08:55
tags: Linux
categories: Linux
在linux下一切皆文件,我们要想操作硬件即是对文件的操作。对文件的操作方法有即读写操作,在这里介绍标准IO的操作主要针对对象为普通文件。标准IO本质是一个标准库,其实现还是建立在系统调用的基础之上,对用户提供统一的接口,标准IO库在不同系统上调用各自系统的系统调用,而对用户的接口不变,这样,用户层编写的代码就可以移植到不同系统,而不做修改,并且不需要关心库内部如何实现。
一、相关概念的解释
1.标准IO库
本质是一个C库,是一组接口。标准IO本质是一个标准库,其实现还是建立在系统调用的基础之上,对用户提供统一的接口,标准IO库在不同系统上调用各自系统的系统调用,而对用户的接口不变,这样,用户层编写的代码就可以移植到不同系统,而不做修改,并且不需要关心库内部如何实现。系统调用其实是一组接口,通过这组接口可以与内核进行方便交互,然后控制进程调度,内存管理,文件操作等。
2.流的含义
当在调用 标准IO 中的接口 来打开文件,操作系统会产生一个结构体指针(与打开文件进行关联),操作这个结构体指针,就在操作相应文件,把这个结构体指针称作 指针流。 ----> FILE *
3.缓冲区
①概念
当我们在调用标准IO的函数的时候,操作系统从用户态切换到内核态,然后在执行相应的命令,执行完毕之后,将结果返回到用户态,这个时候又得从内核态切换到用户态,这样来回切换,加大了操作系统的开销,为了减少这种情况的发生,则设立缓冲机制,直接将所要操作的内容先读到缓冲区当中,再需要的的时候从缓冲区读到内核当中。减少了用户态与内核直接切换的情况。
使用缓冲区的目的: 减少系统的开销,提高系统的运行效率
②分类
1.全缓冲:当缓冲区写满,或者遇到 \n,或者强制性刷新缓冲区(fflush),缓冲的内容会被输出 。
2.行缓冲:按行读写,当缓冲区遇到 \n 或强制性刷新(fflush),会刷新缓冲区的内容 。
3.不缓冲:错误码 不经过缓冲区 ,直接打印到终端 。
二、标准IO相关函数
1.fopen打开/创建文件
函数原型 | FILE * fopen(const char *path, const char *mode) |
---|---|
参数1 | path:要打开的文件路径 |
参数2 | mode:以什么方式打开 r 以读的方式打开文件,文件必须是存在的 r+ 以读写的方式来打开文件,文件必须存在 w 以写的方式打开文件,如果文件不存在,则创建,如果文件存在,清0。 w+ 以读写的方式来打开文件,同w 一致。 a 以追加的方式来打开文件 ,(文件的末尾位置) a+ 以追加的方式来打开文件(读写权限)(文件的末尾位置) |
返回值 | 成功:指向文件对象的指针流 失败:NULL,并设置error值 |
2.fclose关闭文件
函数原型 | int fclose(FILE *stream) |
---|---|
功能 | 关闭打开的文件 |
参数 | stream:指针流,即fopen函数的返回值 |
返回值 | 成功:0 失败: EOF |
3.feof判断文件是否结束
原型 | int feof(FILE *stream) |
---|---|
功能 | 判断文件操作是否结束(操作到文件的末尾位置) |
参数 | stream:指针流(要判断的文件) |
返回值 | 0: 文件未结束 非0:文件结束 |
常见使用方式 | if、while判断 |
4.fread按块读(常用)
原型 | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) |
---|---|
功能 | 将stream 指针指向的文件内容读到 ptr 缓冲区当中 一次读nmemb块,每块的大小是size |
参数 | ptr: 缓存区的首地址 size: 块的大小 nmemb:块数 stream: 指针流 |
返回值 | 成功:读到的块数 失败:负数 0:末尾位置 |
5.fwrite按块写(常用)
原型 | size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) |
---|---|
功能 | 将ptr中的内容写到stream中,一次写memb块,每块的大小size |
参数 | ptr: 缓冲区的首地址 size: 块的大小 memb: 块数 stream:指针流 |
返回值 | 成功:写的块数 失败:负数 |
6.小实验
小应用:
#include <stdio.h>
int main()
{
size_t ret;
//创建buf缓冲区
char buf[6] = {"hello"};
char buf_d[6] = {'0'};
//创建hello.data文件
FILE * p = fopen("hello.data","w+");
if(NULL == p)
{
printf("open is error\r\n");
return -1;
}
//向文件写内容
ret = fwrite(buf,5,1,p);
if(ret < 0)
{
printf("fwrite is error\r\n");
return -1;
}
//将文件的读写位置移动到起始位置
fseek(p,0,SEEK_SET);
//将文件中的读出 缓冲中
ret = fread(buf_d,5,1,p);
if(ret < 0)
{
printf("fread is error\r\n");
return -1;
}
printf("fread:%s\r\n",buf_d);
fclose(p);
return 0;
}
利用上面几个命令实现文件的拷贝:
将当前目录下的1.png图片拷贝一份放在当前目录下的2.png中
#include <stdio.h>
#include <string.h>
#define N 20
int main(void)
{
int ret;
char buf[N] = {'0'};
//源文件
FILE * p = fopen("./1.png","r+"); //1.png存在,读取它
FILE * q = fopen("./2.png","w+"); //2.png不存在,创建它
if(NULL == p|NULL == q)
{
printf("open is error\r\n");
return -1;
}
while(1)
{
ret = fread(buf,1,1,p);
if(ret < 0)
{
printf("fread is error\r\n");
return -1;
}
else if(ret == 0)
{
printf("read finshed \r\n");
break;
}
printf("reading...............\r\n");
ret = fwrite(buf,1,1,q);
if(ret < 0)
{
printf("fwrite is error\r\n");
return -1;
}
}
fclose(p);
fclose(q);
return 0;
}
7、fgetc按字节读
原型 | int fgetc(FILE *stream) |
---|---|
参数 | stream:指针流 |
返回值 | 成功:从unsigned char类型转换到 int型的字符 失败:如果 操作 失败或到达文件末尾,则返回 EOF: -1 |
8、fputc按字节写
原型 | int fputc(int c, FILE *stream) |
---|---|
参数 | c:fgetc的返回值 stream:指针流 |
返回值 | 成功:函数返回写入文件的字符的ASCII码值 失败:返回EOF(-1) |
9、fgets按行读
原型 | char *fgets(char *s, int size, FILE *stream) |
---|---|
功能 | 从 stream流中读取多至 size - 1 个字符, 保存在 s 指向的缓冲区 |
参数 | s : 缓冲区的首地址 size: size-1个字节 stream: 指针流 |
返回值 | 成功:char * 类型的指针 失败:NULL |
注意 | 读到 换行符(newline) 或 EOF 时操作结束, 如果读到的是换行符, 把换行符也保存在缓冲区 中. 函数将在 最后一个 字符后面添加一个 ’\0’ 字符. |
10、fputs按行写
原型 | int fputs(const char *s, FILE *stream) |
---|---|
参数 | s: 缓冲区的首地址 stream: 指针流 |
返回值 | 成功:>=0的值 失败:-1 |
11、fprintf格式化输出
函数原型 | int fprintf(FILE *stream, const char *format, …) |
---|---|
功能描述 | 按一定的格式输入内容到stream指针指向的文件中 |
参数 | stream:指针流 format: 格式(printf格式,比如:%d,%s等) …: 可变参数 |
返回值 | 成功:字节数 失败:负数并设置errno |
举例 | ret = fprintf(p,"%s %d",str,a); |
12、fscanf格式化输入
函数原型 | int fscanf(FILE *stream, const char *format, …) |
---|---|
参数 | stream:指针流 format: 格式(参照scanf格式) …: 可变参数 |
返回值 | 成功:参数的个数 失败:负数 |
13、fseek文件定位
函数原型 | int fseek(FILE *stream, long offset, int whence) |
---|---|
功能 | 改变文件读写位置 |
参数 | stream:指针流 offset:偏移量 whence:基准 SEEK_SET:从文件起始位置开始偏移 SEEK_CUR:从文件当前位置开始偏移 SEEK_END:从文件末尾位置开始偏移 |
返回值 | 成功:0 失败:-1 |
14、ftell获得文件流当前的读写位置
原型 | long ftell(FILE *stream) |
---|---|
相当于 | fseek(stream,OFFSET,SEEK_CUR) |
15、rewind复位文件指示器到文件头
原型 | void rewind(FILE *stream) |
---|---|
相当于 | fseek(stream,0,SEEK_SET) |
三、内核IO相关函数
1、open打开/创建文件
函数原型 | int open(const char *pathname,int flags,mode_tmode); |
---|---|
函数功能 | 若文件存在则打开,若文件不存在则创建 |
参数 | pathname:要打开文件的路径及名字 flags: O_RDONLY 以只读方式打开文件 O_WRONLY 以只写方式打开文件 O_RDWR 以可读写方式打开文件 O_CREAT 创建一个文件 O_TRUNC 打开文件,会把已经存在的内容删除 O_APPEND 追加方式打开文件 mode_tmode:创建文件的权限 |
返回值 | 成功:文件描述符,它是一个非负的正整数,即文件ID号 失败:-1 |
2、close关闭文件
函数原型 | int close(int fd) |
---|---|
函数功能 | 用于关闭一个被打开的的文件 |
参数 | fd:文件描述符 |
返回值 | 成功:0 失败:-1,并设置错误号 |
3、write写函数
函数原型 | ssize_t write(int fd, const void *buf, size_t nbyte); |
---|---|
参数 | fd: 向哪个文件中写(文件描述符来自open函数的返回值) buf: 缓冲区的首地址,即写的内容 nbyte:要写入文件指定的字节数 |
返回值 | 成功:实际写的字节数 失败:-1 |
4、read读函数
函数原型 | ssize_t read (int fd, void *buf, size_t count); |
---|---|
参数 | fd: 读哪个文件(文件描述符来自open函数的返回值) buf: 读缓冲区的首地址,把读到的文件放哪 count:要读多少个字节 |
返回值 | 成功:读取的字节数 失败:-1,并设置errno 如果在调read之前已到达文件末尾,则这次read返回0。 |
5、lseek调整读写位置指针
函数原型 | off_t lseek(int fd, off_t offset, int fromwhere) |
---|---|
参数 | fd: 文件描述符,要操作哪一个文件 offset: 偏移量,相对于第三个参数的偏移量,单位字节,可正可负 fromwhere:基准位置 SEEK_SET: 将读写位置指向文件头后再增加offset个位移量。 SEEK_CUR: 以目前的读写位置往后增加offset个位移量。 SEEK_END :将读写位置指向文件尾后再增加offset个位移量。 |
返回值 | 成功:返回目前的读写位置,也就是距离文件开头多少个字节。 失败:返回-1,errno 会存放错误代码 |
四、目录IO相关函数
1.opendir目录打开
函数原型 | DIR *opendir(const char *payhname) |
---|---|
参数 | payhname:打来的目录以及路径 |
返回值 | 成功:返回目录指针流 失败:返回NULL |
2.mkdir创建目录
函数原型 | int mkdir(const char * path,mode_t mode) |
---|---|
参数 | path:创建的目录文件路径 mode:该目录的访问权限 |
返回值 | 成功:0 失败:-1 |
3.closedir关闭目录
函数原型 | int closedir(DIR *dir) |
---|---|
参数 | dir:目录指针流 |
返回值 | 成功:0 失败:-1 |
4.readdir读取目录
读取到的是当前目录下的子文件和子目录的相关信息
linux中当前目录下的子文件和子文件夹以链表的形式存放,如果我们只调用一次这个函数只会读到链表的头,即当前目录下的一个文件/文件夹。因此我们要用while循环遍历当前目这个链表。来读取当前目录下所有子文件/目录的信息。
函数原型 | struct dirent* readdir(DIR* dir_handle); |
---|---|
参数 | dir_handle:目录指针流 |
返回值 | 成功:struct dirent结构体指针 失败:若在目录尾或出错返回NULL 此结构体应至少包含下列两个指针,该结构体定义在#include<dirent.h>中 struct dirent { ino_t d_ino; //inode号 char d_name[NAME_MAX+1]; //文件名 } |
5.重置读取目录的流指针
①、rewinddir重置读取目录的位置未开头
当前目录下的子文件和子目录以链表形式存放,即把指针流移到链表的头。
*void rewinddir(DIR dp)
参数:目录指针流
②、telldir获取当前流指针的位置
*long int telldir(DIR dir);
参数:目录指针流
返回值:返回值记录着一个目录流的当前位置。此返回值代表距离目录文件开头的偏移量返回值返回下个读取位置,有错误发生时返回-1。
③、seekdir设置目录流指针的读取位置
*void seekdir(DIR dirp,long int loc);
参数1:目录流指针
参数2:距离目录文件开头的偏移量