文件io简单了解
一般在开发中,不会讲大量数据保存在代码中,而是存储在文件中,程序运行时,对文件的数据进行读取和写入,对硬件来说,操作系统也会将其看做文件,只不过需要操作对应的驱动文件来实现数据的写入和读取。
(1)文件io的分类:
一个是系统io、另一个是标准io
系统io:
标准io:
系统io直接操作数据,标准io将数据打包给io缓冲区后再操作。
(2)系统io和标准io的区别
首先都能够打开文件、读取文件、写入文件、关闭文件、文件相关操作
区别:函数名不同,头文件不同;
相同点:两种操作方式都适用于操作文件的;
不同点:
系统io:在操作系统层面,由系统提供,可直接调用系统中的库的函数接口;
不带io缓冲区的操作;
标准io:在库函数层面,有标准C库提供,可直接调用标准库函数的函数接口;
带io缓冲区的操作;
关系:标准io函数,其本质也是调用系统io,只不过对系统io进一步进行封装;
(3)优缺点
系统io:由系统提供,有基本的操作函数。(简洁精练)
优点:简洁单一、稳定,不需要消耗过多的系统资源;
缺点:对功能性要求较高的程序,操作更加繁琐;
标准io:由标准C库提供,拥有多样化的操作函数(功能多样)
优点:复杂操作更为方便,函数接口更加多样化;
缺点:消耗系统资源更多,不适用于简单的功能;
2.Linux中一共存在七种文件类型
名称 | 缩写符号 | 创建命令 | 代码打开方式 | 代码关闭方式 |
普通文件 | - | touch | open/fopen | close/fclose |
目录文件 | d | mkdir | opendir | close |
网络文件 | s | socket | socket | close |
链接文件 | l | ln | open/fopen | close/fclose |
管道文件 | p | mkfifo | open/fopen | close/fclose |
字符设备文件 | c | mknod | 驱动的接口 open | close |
块设备文件 | b | mknod | 驱动的接口 open | close |
备注:设备文件可以被创建,但是如果没有对应的硬件或者驱动文件的支持,那么它只是单纯的占用内存,不起任何作用,也无法做任何操作。
3.文件描述符
在操作文件的时候,并不是直接操作文件,而是给文件一个标号,或者叫引用对象,取名叫做文件描述符,在虚拟文件系统中,有一个文件类型的结构体,其中有一个fd array
1024是系统默认的文件操作安全范围,可以修改。
ulimit -n 更改之后的值
文件描述符中,几个特殊的标号:
第一个下标为0的文件描述符,默认为键盘输入,对应的宏为STDIN;
第二个下标为1的文件描述符,默认为屏幕输出,对应的宏为STDOUT;
第三个下标为2的文件描述符,默认为屏幕输出,对应的宏为STDERR;
对应的函数分别为:scanf() printf() perror()
程序运行时,第一个打开的文件描述符号从3开始
4.相关的文件操作函数接口
系统函数 man 2,标准库函数 man 3
第一组:系统io操作函数
1.新建和打开文件的函数 open
该函数有两个功能:打开文件/创建文件
//头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//函数原型
int open(const char *pathname, int flags);
//函数参数
const char *pathname //文件对象的访问路径
int flags //
O_RDONLY //只读
O_WRONLY //只写
O_RDWR //读写权限
O_CREAT //创建权限
O_EXCL //和新建权限配合,如果文件存在就返回错误信息,用来检测文件是否存在
O_TRUNC //和新建权限配合,如果文件存在就将文件原本内容全部丢弃,文件大小为0
O_NOCTTY //默认
O_APPEND //文件内容追加写入,主要用来写文件(保护之前的内容不被覆盖)
返回值:
成功:返回一个新的文件描述符;
失败:-1;
文件描述符:默认起始为3,一般最大申请1023,总数1024;
另一种open的创建
//函数原型
int open(const char *pathname, int flags, mode_t mode);
//函数参数
const char *pathname //文件对象的访问路径
int flags //文件对象的操作权限
mode_t mode //设置文件的使用权限(宏对应的八进制)
O_RDONLY //只读
O_WRONLY //只写
O_RDWR //读写权限
O_CREAT //创建权限
O_EXCL //和新建权限配合,如果文件存在就返回错误信息,用来检测文件是否存在
O_TRUNC //和新建权限配合,如果文件存在就将文件原本内容全部丢弃,文件大小为0
O_NOCTTY //默认
O_APPEND //文件内容追加写入,主要用来写文件(保护之前的内容不被覆盖)
返回值:
成功:返回一个新的文件描述符;
失败:-1;
文件描述符:默认起始为3,一般最大申请1023,总数1024;
相关宏(八进制)
创建者:
S_IRWXU 00700 user (file owner) has read, write, and execute permission
S_IRUSR 00400 user has read permission
S_IWUSR 00200 user has write permission
S_IXUSR 00100 user has execute permission
同组用户:
S_IRWXG 00070 group has read, write, and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
其他用户:
S_IRWXO 00007 others have read, write, and execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
2.读取文件内容 read
//头文件
#include <unistd.h>
//函数原型
ssize_t read(int fd, void *buf, size_t count);
//函数参数
int fd, //由open函数返回的文件描述符对象
void *buf //读取数据存放的缓冲区
size_t count //计算读取字节的大小(字节)想要读取的字节数
返回值:
成功:返回实际读取的内容字节数
失败:-1
3.将数据内容写入文件 write
//头文件
#include <unistd.h>
//函数原型
ssize_t write(int fd, const void *buf, size_t count);
//函数参数
int fd; //需要写入的文件描述符
const void *buf; //写入的数据来自该缓冲区
size_t count //控制写入的字节数大小(字节)
返回值:
成功:返回写入内容的字节数
失败:-1
4.关闭文件函数 close
//头文件
#include <unistd.h>
//函数原型
int close(int fd);
//函数参数
int fd; //需要关闭的文件对象
注意:如果创建了文件,并向文件中写入了数据,如果没有关闭close,则数据可能无法保存。
5.文件的偏移(光标)占位符的移动 lseek
//头文件
#include <sys/types.h>
#include <unistd.h>
//函数原型
off_t lseek(int fd, off_t offset,int whence)
//函数参数
int fd; //文件操作对象(也就是生成的文件描述符)
off_t offset; //要偏移的字节数
int whence; //偏移的基准点
偏移的基准点:
SEEK_SET //文件的开头位置
SEEK_CUR //文件的当前位置
SEEK_END //文件的末位位置
返回值:
成功:返回偏移过的字节数
失败:-1
注意:可以让偏移的基准点配合,可以计算出文件内容的字节数大小
6.文件的属性 stat
//头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
//函数原型
int stat(const char *pathname, struct stat *buf);
//函数原型
const char *pathname; //需要查看的文件的路径
struct stat *buf; //文件属性的结构体指针
文件属性结构体成员(封装)
struct stat
{
dev_t st_dev; /* 文件存储的设备号 */ 系统自动分配
ino_t st_ino; /* 索引号 文件的身份证 */
mode_t st_mode; /* 文件的类型和权限 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 创建者用户ID */
gid_t st_gid; /* 同组用户ID */
dev_t st_rdev; /* 特殊设备ID */ 驱动文件
off_t st_size; /* 文件大小 */
blksize_t st_blksize; /* 文件所用数据块的数目 */
blkcnt_t st_blocks; /* 写入数据建议值 */
struct timespec st_atim; /* 最后访问文件的时间 */
struct timespec st_mtim; /* 最后修改文件的时间 */
struct timespec st_ctim; /* 最后修改属性的时间 */
}
返回值:
成功:0
失败:-1
st_mode 可以帮助我们判断文件的类型
文件类型:
S_ISREG(m) is it a regular file // 普通文件
S_ISDIR(m) directory // 目录文件
S_ISCHR(m) character device // 字符设备文件
S_ISBLK(m) block device // 块设备文件
S_ISFIFO(m) FIFO (named pipe有名管道) // 管道文件
S_ISLNK(m) symbolic link // 链接文件
S_ISSOCK(m) socket // 网络文件
应用方式:
stat(pathname, &sb); // 先获得文件属性结构体指针
if (S_ISREG(sb.st_mode))
{ // 然后使用带参宏去判断成员st_mode,返回为真,则为普通文件
/* Handle regular file */
}
除了使用带参宏,还可以使用以下的内容去判断文件的类型
S_IFMT 0170000 bit mask for the file type bit field 总权限文件掩码
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 管道文件
应用方式:
stat(pathname, &sb); // 先获得文件属性结构体
if ((sb.st_mode & S_IFMT) == S_IFREG)
{ // 做位与运算,如果结果和某一个宏相同即得到文件类型
/* Handle regular file */
}
获取文件的权限:
S_ISUID 04000 set-user-ID bit // 创建者ID
S_ISGID 02000 set-group-ID bit (see below) // 同组用户ID
S_ISVTX 01000 sticky bit (see below) // 其他用户ID
创建者-这个组的权限
S_IRWXU 00700 owner has read, write, and execute permission
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IXUSR 00100 owner has execute permission
同组用户
S_IRWXG 00070 group has read, write, and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
其他用户
S_IRWXO 00007 others (not in group) have read, write, and
execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
7.判断文件是否存在 access
//头文件
#include <unistd.h>
//函数原型
int access(const char *pathname, int mode)
//函数参数
const char *pathname; //需要判断的文件的路径
int mode; //附加条件:R_OK 读权限 W_OK 写权限 X_OK 执行权限 F_OK 文件是否存在
返回值:
成功:0
失败:-1
8.文件重定向 dup 重定向文件描述符
//头文件
#include <unistd.h>
//函数原型
int dup(int oldfd);
int dup2(int oldfd, int newfd);
//函数参数
int oldfd; //需要重定向的旧文件描述符
int newfd; //指向新的重定向文件描述符
返回值:
成功:返回新的文件描述符(说明已改变文件描述符指向)
失败:返回-1
此外:
函数dup和dup2提供了复制文件描述符的功能。他们通常用于stdin,stdout或进程的stderr的重定向。一般来说,普通输出函数(如:printf),默认是将某信息写入到文件描述符为1的文件中,普通输入函数都默认从文件描述符为0的文件中读取数据。因此重定向操作实际上是关闭某个标准输入输出设备(文件描述符为0、1、2),而将另一个打开的普通文件的文件描述符设置为0、1、2.
输入重定向:关闭标准输入设备,打开(或复制)某普通文件,使其文件描述符为0.
输出重定向:关闭标准输出设备,打开(或复制)某普通文件,使其文件描述符为1.
第二组:标准io
1.打开函数 fopen
//头文件
#include <stdio.h>
//函数原型
FILE *fopen(const char *pathname, const char *mode);
//函数参数
const char *pathname; //打开文件的路径
const char *mode; //打开文件后文件以怎样的权限展示
附加:
r 以只读的方式打开文件
r+ 以读写的方式打开文件
w 以可写的方式打开文件,如果文件存在就清空,不存在就创建
w+ 以读写的方式打开文件,如果文件存在就清空,不存在就创建
a 以可写的方式打开文件,如果存在就追加,不存在就创建,EOF符保留
a+ 以读写的方式打开文件,如果存在就追加,不存在就创建,EOF符不保留
返回值:
成功:返回一个文件流指针
失败:返回NULL
具体可以参考大佬的fopen函数及读写权限;
和另一位大佬的 文件描述符与文件流指针以及重定向;
2.关闭函数 fclose
//头文件
#include <stdio.h>
//函数原型
int fclose(FILE *stream);
//函数参数
FILE *stream //文件流指针
返回值:
成功:返回0
失败:返回EOF(头文件中EOF被定义为-1)
3.读写函数 fread fwrite
//头文件
#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);
//函数参数
void *ptr //读写数据存储的缓冲区位置
size_t size, //每一块存储区的大小(字节)
size_t nmemb //打算使用多少块操作数据(和calloc类似)
FILE *stream //操作对象的文件流指针
返回值:
成功:返回读取或者写入的完整的数据块的个数
失败:返回0或者其他小于0的数据
注意:返回值是读取的完整的数据块的个数,就算最后一次读取的数据没有达到一个数据块的长度,数据还是会被读取
4.标准io文件属性 feof
//头文件
#include <stdio.h>
//函数原型1
void clearerr(FILE *stream); // 清除文件的错误码
int feof(FILE *stream); // 检查光标是否到了文件的末尾
返回值:
成功:非零的时候就表示到达了文件的末尾
失败:为零的时候就是没有找到文件末尾位置
int ferror(FILE *stream); // 检查文件操作过程中的出错编码
返回值:
成功:为零的时候说明没有错误
失败:非零的时候,表示遇到错误
int fileno(FILE *stream); // 检查文件描述符是多少
=================================================================
//函数参数
FILE *stream //文件流指针
feof的缺陷:就算文件光标移动到了文件末尾,那也需要再读一次,才能获得非零值
在C里,只有当文件位置指针到了文件末尾,然后再发生读/写操作时,标志位才会被置为含有_IOEOF。
5.标准io中文件偏移 fseek
//头文件
#include <stdio.h>
//函数原型
int fseek(FILE *stream, long offset, int whence);
//函数参数
FILE *stream //偏移对象,文件流指针
long offset //偏移量 字节
int whence //基准点
{
SEEK_SET 文件的起始位置
SEEK_CUR 文件的当前位置
SEEK_END 文件的末尾位置
}
返回值:
成功:0
失败:-1
注意:fseek与lseek返回值不相同
long ftell(FILE *stream); // 返回当前文件位置,距离文件开头处的长度
注意:ftell只做计算,不做偏移
void rewind(FILE *stream); 等价于(void) fseek(stream, 0L, SEEK_SET),将偏移量偏移到文件开头处
6.标准化的输入函数----字符和字符串
//头文件
#include <stdio.h>
//从文件里面获取一个字符
int fgetc(FILE *stream);
//从文件中读取size-1长度的字符串,存放到s为首地址的内存中,或者读到换行符时结束,
char *fgets(char *s, int size, FILE *stream); //返回值与s相同,都指向存储字符串的缓冲区,如果失败,返回NULL
//从文件中获取一个字节 (fgetc和getc的区别:fgetc是函数,getc是带参宏)
int getc(FILE *stream);
//获取缓冲区的一个字符
int getchar(void);
//获取缓冲区中的一个字符串
char *gets(char *s); // 因为经常会导致内存溢出,所以已经停用
==============================================================
函数参数:
FILE *stream 文件流指针
int size 大小(字节)
char *s char *类型的指针,指向存储字符串的空间
7.标准化输出函数----字符和字符串
//头文件
#include <stdio.h>
//输出一个字符到文件中
int fputc(int c, FILE *stream);
//输出一个字符串到文件中
int fputs(const char *s, FILE *stream);
//输出一个字符到文件中,fputc是函数,putc是宏定义
int putc(int c, FILE *stream);
//输出一个字符
int putchar(int c);
// 输出一个字符串
int puts(const char *s);
函数参数:
int c //字符或者ASCII码值
const char *s //字符串数据
FILE *stream //文件流指针
8.格式化输出----可变参函数
//头文件
#include <stdio.h>
//格式化输出到屏幕(stdout)上
int printf(const char *format, ...);
//格式化输出到文件中(标准IO)
int fprintf(FILE *stream, const char *format, ...);
//格式化输出到文件中(系统IO)
int dprintf(int fd, const char *format, ...);
//组合字符串,将格式化的组合之后的字符串输出到str指向的内存中
int sprintf(char *str, const char *format, ...);
//组合字符串,将格式化的组合之后的size-1个字符串输出到str指向的内存中
int snprintf(char *str, size_t size, const char *format, ...);
//函数参数
const char *format //格式化符号 %d %s %c
FILE *stream //文件流指针
int fd //文件描述符
char *str //str存储字符串的位置
size_t size //输出大小
CSDN-Ada助手: 非常感谢您分享这篇有关链表环的判断的博客。您的思路非常清晰,让我对链表环的判断有了更深入的了解。除了快慢指针之外,我发现还有一种叫做“哈希表”的方法可以判断链表中是否有环。同时,我们在实际应用中也需要考虑链表的各种操作,例如链表的插入、删除、反转等。希望您可以继续分享更多实用的知识和技能,让我们一起成长进步。 如何写出更高质量的博客,请看该博主的分享:https://blog.csdn.net/lmy_520/article/details/128686434?utm_source=csdn_ai_ada_blog_reply2 如果您持续创作,完成第三篇博客,并且质量分达到 80 分以上,在评论区就有机会获得红包奖励哦!
CSDN-Ada助手: 恭喜你写了第三篇博客,主题也很不错。对于文件io的简单了解,我也从中受益匪浅。希望你能继续坚持创作,分享更多有价值的内容给大家。下一步的创作建议,可以考虑深入探讨某个文件io相关的主题,或者结合实际案例进行分析,让读者更好地理解和应用。期待你的下一篇文章! CSDN 正在通过评论红包奖励优秀博客,请看红包流:https://bbs.csdn.net/?type=4&header=0&utm_source=csdn_ai_ada_blog_reply3,我们会奖励持续创作和学习的博主,请看:https://bbs.csdn.net/forums/csdnnews?typeId=116148&utm_source=csdn_ai_ada_blog_reply3
CSDN-Ada助手: 非常感谢用户分享了这篇关于文件流指针和文件标识符的博客!这篇博客讲解得非常清晰,让读者能够更好地理解和应用这些概念。同时,也非常欣赏用户持续创作的精神,相信这样的坚持一定会收获更多的成果。 建议下一步可以探讨一些具体的实际应用场景,如何利用文件流指针和文件标识符来实现一些功能。希望用户能够继续保持创作的热情,为大家带来更多优质的内容! CSDN 会根据你创作的前四篇博客的质量,给予优秀的博主博客红包奖励。请关注 https://bbs.csdn.net/forums/csdnnews?typeId=116148&utm_source=csdn_ai_ada_blog_reply4 看奖励名单。
CSDN-Ada助手: 算法 技能树或许可以帮到你:https://edu.csdn.net/skill/algorithm?utm_source=AI_act_algorithm