- 文件IO简介
- 什么是文件IO?
==> Linux 下一切都是文件!
Linux环境下,Linux把所有的资源统一当成文件来进行对待,那么在Linux环境下要使用各种设备,那就需要对对应的设备问价进行访问。但是在操作系统的管理下,用户是不允许直接对文件进行操作。Linux系统给开发者预留了一些对文件操作的函数接口,这些函数接口就是文件IO。
文件IO其实就是对文件操作的一些函数接口(API)
- 文件IO分类
- 系统IO
==> 功能由系统内核直接提供。(功能更为原始,更为接近底层),使用时通常在对底层设备文件进行访问的时候使用系统IO。
==> open , read, write, close, lseek, mmap ....
- 标准IO
==> 功能由标准库提供。(底层调用类系统IO)
==> 带缓冲的IO,处理速度比系统IO要快!
==> 在应用层对于文件文件的处理时通常采用标准IO
==> fopen , fread, fwrite, fclose, fseek, fputs, fgets ...
- 相关函数
- open() --> 打开或者创建文件 --> man 2 open
open, creat - open and possibly create a file or device
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> //头文件(在程序中使用open函数需要声明头文件)
int open(const char *pathname, int flags);
==> pathname : 路径名 (需要打开的文件的路径名)
==> flags : 标志 (打开文件之后的权限!)
O_RDONLY, //只读权限
O_WRONLY, //只写权限
O_RDWR. //读写权限
==> 返回值: 成功返回一个新的文件描述符,失败返回-1.
//文件描述符:对文件操作的一把钥匙(本质是一个大于等于0的整数)(数组的下标)
//硬盘:一栋大楼
//文件:大楼里面的房间
//操作系统:大楼管理员
// open函数 :找大楼管理员拿钥匙
// close函数 :把钥匙还给管理员
==> 例子: 使用open函数,打开当前路径下的一个文件1.txt. 如果成功,打印一下文件描述符的值。
==> 为什么文件描述符的值是3?
==> 程序在运行时,会默认打开3个文件描述符
0 标准输入 STDIN_FILENO 键盘
1 标准输出 STDOUT_FILENO 屏幕
2 标准出错 STDERR_FILENO 屏幕
==> 这些定义是在 /usr/include/unistd.h
==> 一个程序中,文件打开的数量是不是无限的? (一个程序中同一时刻打开的文件数量是有限制的!)
==> 测试:对一个文件进行循环打开,直到打开出错就停止!
==> Linux下程序同时打开文件的数量上限 1024
==> 如果想要无限的打开文件,那么对于不再需要使用的文件要及时的关闭(close)
==> 注意:在文件操作的时候,对于一些不再需要使用的文件来说,需要及时的关闭它,否则可能造成程序打不开新的文件!
==> open函数在打开文件的时候,可以添加参数,实现文件不存在的情况下可以创建文件并且打开!
int open(const char *pathname, int flags, mode_t mode);
==> 如果要创建文件,需要在第二个参数中添加 O_CREAT,那么就需要对第三个参数进行赋值,给它新建的文件的权限
==> 例子: 打开当前路径下的文件2.txt, 如果文件不存在则创建他,并且把文件权限设置为对所有用户都是可读可写不可执行。
2,read() 从文件中读取数据 --> man 2 read
NAME
read - read from a file descriptor
SYNOPSIS
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
==> fd : 需要读取数据的文件描述符
==> buf : 存放数据的缓冲区( void * :任意类型指针 )
==> count : 想要一次读取的字节数!
返回值:成功返回实际读取的字节数,0代表读取到文件末尾,失败返回-1
==》例子:从文件open.c 中读取数据并且打印.
--> 1) 打开文件 open.c
--> 2) 读取文件数据
--> 3) 关闭文件 open.c
练习1:先把read.c 自己去动手敲一遍,熟悉一下read函数的用法。
==> 然后修改代码,实现把open.c文件的所有内容全部输出!
==> 思考:要把一个文件中的所有数据都读取出来,应该怎么读取?
解决方案:
·把read函数的缓冲区设置的大一点!
·读文件内容进行循环读取!直到读取到文件末尾时结束!
==> 示例代码:
1,write函数
NAME
write - write to a file descriptor
SYNOPSIS
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
==> fd : 需要写入的文件的文件描述符
==> buf : 想要写入到文件内容的缓冲区首地址(可以写入任意类型数据)
==> count : 想要写入的字节数!
返回值: 成功返回实际写入的字节数(0代表没有写入东西),失败返回 -1
Write函数使用案例:
write写入字符串
Write写入整型,浮点型...
Write写入结构体
- lseek函数 --> 偏移文件指针
NAME
lseek - reposition read/write file offset
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
==> fd : 文件描述符
==> offset : 偏移的字节数 (正数:往后偏移,负数:往前偏移)
==> whence : 偏移的位置!
SEEK_SET : 从文件开头开始偏移
SEEK_CUR : 从当前位置开始偏移
SEEK_END : 从末尾开始偏移
返回值:成功返回偏移指针之后距离文件开头的偏移量,失败返回-1;
例如:
偏移到文件开头第三个字节: lseek(fd, 3, SEEK_SET);
偏移到文件末尾:lseek(fd, 0, SEEK_END);
偏移到文件倒数第2个字节: lseek(fd, -2, SEEK_END);
==> 例子: 使用lseek函数偏移文件指针,跳过文件1.txt 的前个字符,把后面的内容全部读取。
- 什么是标准IO?
标准IO是由POSIX库提供的一套文件IO,这套文件IO对系统IO进行封装调用,使功能更加丰富,调用更为简单,方便应用层的程序进行调用。简单来说,标准IO就是调用系统IO实现文件IO操作的一套函数接口!
1,系统IO与标准IO之间的关系
底层:系统IO --> open
应用层:标准IO --> fopen
- 标准IO的相关函数
- fopen()
==> man 3 fopen
NAME
fopen, fdopen, freopen - stream open functions
SYNOPSIS
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
==> path : 打开的文件的路径
==> mode : 打开的方式
--> “r” : 以只读权限打开文件,文件指针指向文件开头!
-->”r+” : 以读写权限打开文件,文件指针指向文件开头!
-->”w” : 如果文件不存在,那就创建文件。如果文件存在,那就先清空文件,然后以只写的权限打开文件,文件指针指向文件开头
-->”w+” : 如果文件不存在,那就创建文件。如果文件存在,那就先清空文件,然后以读写的权限打开文件,文件指针指向文件开头
-->”a” : 以追加的形式打开文件,如果文件不存在则创建,打开之后文件指针指向文件末尾。
-->”a+” : 读写打开文件,如果文件不存在则创建,文件的读指针指向文件开头(读取数据会从文件开头开始读),文件的写指针指向文件末尾(往文件里面写数据是追加写入)。
返回值: 成功返回FILE * 指针,失败返回NULL
- fclose() -->
SYNOPSIS
#include <stdio.h>
int fclose(FILE *stream);
==> 例子:打开一个文件”1.txt”,如果文件不存在则创建,存在则清空!
练习1:使用fopen函数实现功能:以追加形式打开一个文件 2.txt,如果文件不存在则创建,打开成功输出:”open file success!” .
==>程序运行时,默认打开3个文件流
0 stdin :标准输入 --> 键盘
1 stdout :标准输出 --> 屏幕
2 stderr :标准出错 --> 屏幕
==> 文件流定义在 stdio.h --> /usr/include/stdio.h
- fread()/fwrite() ==> 读写文件
NAME
fread, fwrite - binary stream input/output
SYNOPSIS
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
==> ptr : 存放 读取/写入 数据的缓冲区的首地址
==> size : 读取/写入 数据的每一块的大小!单位字节
==> nmemb : 读取/写入 的数据块的块数
==> stream : 文件流指针
返回值:成功返回实际读取的数据块数量,读取完毕或者失败返回0.可以通过函数feof(3) and ferror(3)判断是失败还是读取到文件末尾。
==> 例子2: 以只写方式打开文件1.txt, 写入”helloworld” 字符串到文件中.
==》例子3:以只读方式打开文件1.txt,将里面的内容读取出来。
- fseek() --> 偏移文件指针
NAME
fgetpos, fseek, fsetpos, ftell, rewind - reposition a stream
SYNOPSIS
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream); //返回文件指针相对于文件开头的偏移量
==> stream : 文件流指针
==> offset : 偏移的字节数
==> whence : 偏移的位置
SEEK_SET : 文件开头
SEEK_CUR : 当前指针位置
SEEK_END : 文件末尾
返回值: 成功返回0,失败返回-1
==> 例子1:使用标准IO相关函数,实现读取文件 fwrite.c 的最后10个字符!。
·打开文件 fopen
·偏移文件指针到倒数第10个字符
·读10个字符 --> 打印
·关闭文件
==> 例子2:使用相关函数,计算一个文件的大小。
- 打开文件
- 把文件指针偏移到文件末尾
- 计算当前文件指针相对于文件开头的偏移量(文件大小)
- 关闭文件
标准输入输出缓冲区
为了让低速的输入输出设备与高速的用户程序能够协调工作,降低输入输出设备的读写次数,计算机在内存中预留了一定的内存空间,用来暂时保存输入输出的数据,这部分预留的空间就是缓冲区!
缓冲区分为三类!
全缓冲:在输入输出的时候只有缓冲区满了才会进行真正的输入输出操作!全缓冲的缓冲区大小是由限制的!缓冲区满了就会清空缓冲区!
行缓冲:在输入输出的时候,如果缓冲区满了或者是碰到换行符 ‘\n’ 此时就会刷新缓冲区!Printf(); scanf(); getchar(); fgets() ....
不带缓冲:数据必须立即进行输入输出!
==> 画图分析输入输出的时候的缓冲区。
==> 验证输出缓冲区!
总结:printf函数想要输出数据显示在屏幕上
- 程序结束,输出缓冲区的数据会被全部刷新
- 缓冲区满,缓冲区填满了那么所有的数据都会被刷新出来
- 缓冲区中碰到 ‘\n’ , 缓冲区的数据碰到 ‘\n’就会把数据立即刷新!
==> 不带缓冲区 ==>
标准输入 : stdin --> 带缓冲区
标准输出 : stdout --> 带缓冲区
标准出错 : stderr --> 不带缓冲区 --> perror()
==> 把数据输出到标准出错 ==> fputs() : 把数据输出到某一个文件流指针
==> 例如:把数据输出到标准出错
int fputs(const char *s, FILE *stream);
==> s : 输出的字符串
==> stream : 输出到哪一个文件流指针 ==> stderr
把”helloworld”写入到标准出错 fputs(“helloworld”, stderr);
标准IO : 带缓冲的IO --> 处理大量数据的文件读写时效率比系统IO高!
系统IO : 不带缓冲的IO --> 只要有数据就会立即处理!
- 文件属性获取
- stat函数 --> 获取文件属性
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *buf);
==> pathname : 文件路径名
==> buf : 结构体指针 --> stat结构体
返回值:成功返回0; 失败返回-1;
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */ //文件类型
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */ //文件大小
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* time of last access */
struct timespec st_mtim; /* time of last modification */
struct timespec st_ctim; /* time of last status change */
};
==> 例子1:使用stat函数获取一个文件的大小。
==> 例子2:使用stat函数获取文件的属性和大小。
==> Linux下的一切都是文件:
S_IFSOCK 0140000 socket //套接字文件
S_IFLNK 0120000 symbolic link //链接文件 --> 软链接+硬链接
S_IFREG 0100000 regular file //普通文件
S_IFBLK 0060000 block device //块设备文件 --> 硬盘 ...(存储类)
S_IFDIR 0040000 directory //目录文件 (文件夹)
S_IFCHR 0020000 character device //字符设备 (设备驱动)
S_IFIFO 0010000 FIFO //管道文件
练习1:设计程序,实现如下功能、
==> 执行程序格式为 ./stat <文件名>
==> 检测传入的文件名的类型,如果是普通文件,那就把文件的名字和大小输出!然后把文件的内容读取并且显示 (使用标准IO实现)
==> 如果是目录文件,那就打印”文件 xxx 是一个目录文件!”
- access函数 --> 检测文件是否具有某种权限
SYNOPSIS
#include <unistd.h>
int access(const char *pathname, int mode);
==> pathname :文件路径名
==> mode : 检测的文件权限
F_OK : 文件是否存在
R_OK :文件是否可读
W_OK : 文件是否可写
X_OK :文件是否可执行
返回值:成功(具有这种权限时)返回0,失败(不具有这种权限)返回-1
==> 例子1: 使用access函数检测文件是否存在!
==》练习2:使用access函数去测试一个文件的权限
==> 例如: 1.c是存在,可读,可写,不可执行
==> ./test2 1.c
==> 执行结果 : 输出”文件 1.c 存在 可读 可写 不可执行”
- 目录IO
目录IO:对目录文件进行IO操作。
1, opendir() --> 打开目录文件
SYNOPSIS
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
==> name : 需要打开的目录的名字
返回值: 成功返回DIR * 目录流指针,失败返回NULL
2, readdir() --> 读取目录里面的信息 (一次只能读取一个文件的信息)
SYNOPSIS
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
==> dirp : 目录流指针
返回值: 成功返回一个文件信息结构体指针,失败或者到目录末尾返回NULL
On Linux, the dirent structure is defined as follows:
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported
by all filesystem types */
char d_name[256]; /* filename *///文件名长度不能超过 255个字符
};
3, closedir() --> 关闭目录流指针
SYNOPSIS
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
==> 例子1: 设计程序,读取指定目录下的所有文件的信息,打印文件名。
练习3:参考readdir.c ,实现对一个目录下的文件信息进行检索,打印所有的文件名。除了 “.” 和 “..” 。
思路:每次从目录中读取到一个文件信息之后,先判断文件名是不是 “.” 或者”..”如果是,那就不打印,否则就打印!
==> readdir的文件类型查看:
==> 例子2: 使用相关函数对一个指定的目录进行检索。并且打印文件是普通文件还是目录文件,还是其他文件。
练习4:设计程序,检索指定目录,记录这个目录下的普通文件数量和目录文件的数量。
==> ./readdir1 test_dir/
==> 输出信息: 目录 test_dir 中一共有 5个普通文件,2个目录文件!
==> 例子3:使用相关函数对一个指定目录进行检索,只打印其中某一种格式的文件,比如只检索BMP文件。
思路:bmp文件的特点? ==> xxxx.bmp ==> bmp文件的文件名后4个字符是 “.bmp”
==> 实现方案:
检索文件的时候,对文件名进行判断,如果文件名是 “.bmp”结尾的文件,那就输出,否则就不输出!
==> 如何判断文件名后4位是不是 .bmp ?