文件IO
I :input 输入,从文件中读取数据到内存
O:output 输出,把数据写入到文件
Linux系统IO 和 c语言标准IO
1、linux系统IO
1.1 简介
linux操作系统把对文件的操作封装成了多个函数,统称为linux系统IO。
文件描述符(File descirptor) 是一个用来访问文件的抽象化概念
文件描述符是一个正整数(进程文件表项下标),一个被打开的文件对应着一个文件描述符
linux中,每个进程(暂时理解程序)都会自动打开3个文件:
功能 文件描述符 对应的宏
标准输入文件 从输入设备(鼠标,键盘等)中获取数据 0 STDIN_FILENO
标准输出文件 输出数据到输出设备(屏幕等) 1 STDOUT_FILENO
标准出错文件 输出错误信息到输出设备(屏幕等) 2 STDERR_FILENO
这个宏定义在头文件 /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. */
1.2 如何学习系统函数
ubuntu中,把所有系统函数都做了说明手册,我们需要学会去查询这个手册
man -f 标识符 //查询该标识符在手册中的所有功能简介
以 strcpy,printf为例
man 数字 标识符 //进入到对应手册
比如:
man printf //如果该标识符有多页,不加数字默认进入最前面那页
man 1 printf //进入printf手册第一页
man 3 printf //进入printf手册第三页
man strcpy
man 3 strcpy
可以安装中文手册
sudo apt install manpages-zh //安装
sudo apt remove manpages-zh //卸载
直接百度
1.3 具体函数
1.3.1 打开文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//使用open函数,需要包含这三个头文件
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
//为什么有两个open函数,不会重复定义吗?
//一个是用宏函数实现的,一个是普通函数
pathname :文件的路径名(包含路径的文件名)
flags:打开该文件的方式,有很多种
O_RDONLY, O_WRONLY 或 O_RDWR 必选项,三选一
(只读 read only 只写write only 读写 read write)
以下是可选项,0项或多项,和上面的标识用 | 连接
O_CREAT:如果文件不存在,则先创建,如果文件存在则直接打开
O_EXCL:该标识一般和 O_CREAT配合使用,如果文件存在,则报错
O_TRUNC:如果文件存在且是普通文件,并且有可写权限,那么打开文件时会把文件的内容清空
O_APPEND:追加方式打开。如果没有该标志,打开文件后,读写指针(光标)在文件开头;如果有该标志
读写指针(光标)在文件末尾。
O_NONBLOCK 或 O_NDELAY :以非阻塞的方式打开
mode:该参数只有在第二个参数有 O_CREAT 标志时才有效
用来指定创建文件后文件的权限,有两种指定方式:
(1)用宏指定
S_IRWXU
00700 允许 文件 的 属主 读 , 写 和 执行 文件
S_IRUSR (S_IREAD)
00400 允许 文件 的 属主 读 文件
.....
(2)用八进制数字指定
0777 // 111 111 111
0764 // 111 110 100
....
返回值:
失败了返回-1,同时errno被设置
成功返回文件描述符
(可以打开目录,因为目录也是文件,只是只能以只读的方式打开目录)
errno是系统中定义的一个全局变量,是一个整数,不同的值表示不同的系统错误,一般叫做错误码,当发生某个系统错
误时,系统自动把errno设置为对应的错误码,并且提供了相关函数来解析这个错误码:
perror strerror
printf("打开%s失败:%s\n","3.txt",strerror(errno));
perror("打开失败");
1.3.2 关闭文件
#include <unistd.h> int close(int fd); fd:要关闭的那个文件描述符 返回值: 失败返回-1,同时errno被设置 成功返回0 任何一个文件打开,操作完之后,必须要关闭。
1.3.3 读取文件内容
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
从文件描述符 fd 中读取 count 字节的数据并放入从 buf 开始的缓冲区中.
fd:文件描述符
buf:用来保存读取到的数据的内存首地址
count:要读取的字节数
返回值:
失败返回-1,同时errno被设置
成功返回实际读取到的字节数(返回0表示没有内容可读,一般是表示到了文件末尾了)
read函数进行读取,是从文件当前光标所在位置开始读的, 读取成功之后,光标自动往后偏移。 示例代码: 01测试代码.c 中的 test1函数和test2函数
1.3.4 往文件写入数据
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd:文件描述符
buf:要写入的数据的首地址
count:要写入的字节数
返回值:
失败返回-1,同时errno被设置
成功返回实际写入的字节数
write函数进行写入,从光标位置开始写入,写入成功后,光标自动往后偏移
示例代码:
01测试代码.c 中的 test3函数
1.3.5 定位光标
#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 以文件末尾位置为基准
如:
lseek(fd,10,SEEK_SET);//把光标定位到据文件开头10字节处
lseek(fd,0,SEEK_SET);//把光标定位到文件开头处
lseek(fd,0,SEEK_END);//把光标定位到文件末尾处
lseek(fd,5,SEEK_CUR);//把光标从当前位置往后移5字节
返回值:
失败返回-1,同时errno被设置
成功返回新光标距离文件开头的字节数
1.3.6 获取文件属性
这个结构体用来描述文件的属性/状态
struct stat {
dev_t st_dev; /* ID of device containing file */
文件的设备号
ino_t st_ino; /* Inode number */
Inode 号
mode_t st_mode; /* File type and mode */
文件的类型及权限,详细介绍在下面
nlink_t st_nlink; /* Number of hard links */
硬链接的数目
uid_t st_uid; /* User ID of owner */
用户ID
gid_t st_gid; /* Group ID of owner */
组用户ID
dev_t st_rdev; /* Device ID (if special file) */
设备号
off_t st_size; /* Total size, in bytes */
文件大小
blksize_t st_blksize; /* Block size for filesystem I/O */
块大小,一般来说一块是512字节
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
有多少块
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 */
最后修改文件状态的时间
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
st_mode成员详细介绍
该成员用来描述文件的类型及文件的权限
strucr stat sb;//假设sb已经保存了某个文件的属性,那么下面代码就是用来判断该文件是什么类型的文件
switch (sb.st_mode & S_IFMT) {
case S_IFBLK: printf("block device\n"); break;//块设备
case S_IFCHR: printf("character device\n"); break;//字符设备
case S_IFDIR: printf("directory\n"); break;//目录
case S_IFIFO: printf("FIFO/pipe\n"); break;//管道文件
case S_IFLNK: printf("symlink\n"); break;//符号链接/软链接文件
case S_IFREG: printf("regular file\n"); break;//普通文件
case S_IFSOCK: printf("socket\n"); break;//套接字文件
default: printf("unknown?\n"); break;
}
下面的代码就是判断该文件的权限:
if(sb.st_mode & S_IRUSR)
{
//条件成立,说明该文件用用户可读权限
}
if(sb.st_mode & S_IWUSR)
{
//条件成立,说明该文件用用户可写权限
}
......
详细代码可以参考 man 2 stat
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
pathname:文件路径名
statbuf:一个结构体变量的地址,stat函数执行成功后,该结构体就保存了文件的属性信息
返回值:
失败返回-1,同时errno被设置
成功返回0
int fstat(int fd, struct stat *statbuf);
和 stat函数功能完全一样,区别在于第一个参数是文件描述符
int lstat(const char *pathname, struct stat *statbuf);
和 stat函数功能基本一样,区别在于如果 pathname 文件为软链接文件时,
lstat获取的是软链接文件本身的属性信息
stat获取的是软链接指向的源文件的属性信息
示例代码: 02测试代码.c
作业: 1,分析下面代码的作用 char buf[20]; read(STDIN_FILENO,buf,10); write(STDOUT_FILENO,"hello",5); 2,实现复制普通文件的函数(该文件可能非常大) 成功返回0,失败返回-1 int copy_file(const char *src,const char *dest) { }
1.3.7 目录操作
(1)打开目录
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
name:目录的路径
DIR *fdopendir(int fd);
fd:目录的文件描述符(先用 open 打开,返回文件描述符,再用 opendir打开)
返回值:
失败返回 NULL
成功返回 DIR指针( 不需要关心DIR具体是什么,只需要知道后续对目录的操作要用到它)
(2)读取目录
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
dirp:opendir的返回值
目录中的内容(包括文件和子目录)有多少项是不固定的,readdir每次只读一项,需要循环调用readdir进行读取,
直到返回NULL(表示读取完毕)
返回值:
成功返回 struct dirent 结构体指针
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
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]; /* Null-terminated filename */
};
其中最重要的就是 d_name,就是读取到的 子目录名字或者文件名
The only fields in the dirent structure that are mandated by POSIX.1
are d_name and d_ino. The other fields are unstandardized, and not
present on all systems; see NOTES below for some further details.
只有 d_name 和 d_ino 是所有linux系统中都支持的成员,其他成员并不是所有系统中都存在。
为了代码有更好的兼容性,建议尽量不要使用其他成员。
(3)关闭目录
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
(4)创建目录
int mkdir(const char *pathname, mode_t mode);
1.3.8 其他函数
truncate
int truncate(const char *path, off_t length);
缩短/扩大 path文件 至 指定 length 长度
unlink
int unlink(const char *pathname);
删除指定文件
mkdir 创建目录
rmdir 删除空目录
remove 删除普通文件或者目录
chdir 改变工作路径,工作路径默认是运行程序的那个路径,程序中的相对路径都是相对工作路径而言的
int chdir(const char *path);
2、c语言标准IO
2.1 简介
系统IO是操作系统提供的函数,不同的操作系统提供的函数不一样。
而C语言标准IO是c语言标准库提供的,只要你用c语言进行开发,不管在什么系统中都可以使用。
c语言标准IO,用struct FILE类型来表示一个被打开的文件
三个特殊的 FILE 指针
/* Standard streams. */
extern struct FILE *stdin; /* Standard input stream. */
extern struct FILE *stdout; /* Standard output stream. */
extern struct FILE *stderr; /* Standard error output stream. */
stream 流
IO流 文件流 有缓冲区
标准IO中在合适的时机把缓冲区中的数据写入到文件中,合适的时机??
全缓冲:当缓冲区满了之后或者程序正常结束或者关闭文件才会写入到文件中。比如 fwrite
行缓冲:遇到‘\n’或者程序正常结束才会写入到文件中 ,比如 printf
注意,用 printf调试段错误时一定要换行
无缓冲:没有缓存直接输出,比如 stderr文件流, perror函数
2.2 文件操作相关函数
2.2.1 打开文件
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
path :要打开的文件的路径名
mode :打开方式,字符串
r 打开文本文件,用于读。流被定位于文件的开始。
r+ 打开文本文件,用于读写。流被定位于文件的开始。
w 将文件长度截短为零(清空),或者创建文本文件,用于写。流被定位于文件的开始。
w+ 打开文件,用于读写。如果文件不存在就创建它,否则将清空它。流被定
位于文件的开始。
a 打开文件,用于追加 (在文件尾写)。如果文件不存在就创建它。流被定
位于文件的末尾。
a+ 打开文件,用于追加 (在文件尾写)。如果文件不存在就创建它。读文件
的初始位置是文件的开始,但是写入总是被追加到文件的末尾。
返回值:
失败返回NULL,同时errno被设置
成功返回被打开文件的 FILE指针
FILE *fdopen(int fd, const char *mode);
fd:文件描述符。先open,再fopen
mode:和fopen一样
返回值:和fopen一样
2.2.2 关闭文件
#include <stdio.h>
int fclose(FILE *stream);
stream :fopen的返回值
返回值:
失败返回-1,同时errno被设置
成功返回0
2.2.3 读取文件内容
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
读取文本文本和二进制文件,并且想读多个字节都可以
ptr:用来保存读取到的数据的内存首地址
size:要读取的内容中单个元素的大小
nmemb:要读取的内容中元素的个数
读取的总字节数是 size*nmemb
stream:指定从哪个文件中读取
返回值:
失败返回-1,同时errno被设置
成功返回实际读到的元素个数
读取文本文件中的一个字符
getc/getchar/fgetc
int getchar(void);
从标准输入文件流(stdin)中读取一个字符
返回值:
失败返回-1
成功返回该字符的 ascii码
int fgetc(FILE *stream);
int getc(FILE *stream);
从stream指定的文件中读取一个字符
返回值:
失败返回-1
成功返回该字符的 ascii码
读取文本文件中的一行字符
char *gets(char *s);//容易越界,不建议使用
char *fgets(char *s, int size, FILE *stream);
s:用来保存字符串的那块内存的首地址
size:表示最多读 size-1个字节(最后一个字节给'\0')。
一般来说,传入s指向的那块内存的大小,可以防止越界
如果在size-1个字节前遇到换行符,也会结束
stream:指定从哪个文件中读取
返回值:
失败返回-1,同时errno被设置
成功返回字符串首地址(其实就是s)
2.2.4 往文件中写入数据
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);
ptr: 要写入的数据的首地址
size:要写入的内容中单个元素的大小
nmemb:要写入的内容中元素的个数
stream:指定写入到哪个文件中
返回值:
失败返回-1,同时errno被设置
成功返回实际写入的元素个数
往文件中写入一个字符
int putchar(int c);//把字符c写入标准输出流 stdout ,效果就是输出字符c
c:要写入的字符
失败返回-1
成功返回c
int putc(int c, FILE *stream);
int fputc(int c, FILE *stream);
c:要写入的字符
stream:指定写入到哪个文件流
失败返回-1
成功返回c
往文件中写入一行字符
int puts(const char *s);//写入到stdout,自动加一个 '\n'
int fputs(const char *s, FILE *stream);//输出到指定文件,不会加'\n'
2.2.5 定位光标
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
stream:指定文件流
offset:偏移量,以字节为单位,正数往后偏移,负数往前偏移
whence:偏移方式
SEEK_SET 以文件开头位置为基准
SEEK_CUR 以当前光标位置为基准
SEEK_END 以文件末尾位置为基准
返回值:
失败返回-1,同时errno被设置
成功返回0
long ftell(FILE *stream);
返回 stream文件中光标距离文件的位置
学习以下函数
truncate
int truncate(const char *path, off_t length);
缩短/扩大 path文件 至 指定 length 长度
unlink
int unlink(const char *pathname);
删除指定文件
mkdir 创建目录
rmdir 删除空目录
remove 删除普通文件或者目录
chdir 改变工作路径,工作路径默认是运行程序的那个路径,程序中的相对路径都是相对工作路径而言的
int chdir(const char *path);
feof 判断文件是否结束,一般在读取文件之后使用该函数