文件IO(文件IO三)

4 篇文章 0 订阅
3 篇文章 0 订阅
本文详细介绍了文件IO与标准IO的区别,重点讲解了文件描述符的使用,包括open函数、close函数、read/write函数以及非读写操作如lseek、fcntl和stat等。此外,还提到了目录操作和文件权限测试等相关函数,深入解析了文件操作的各种细节。
摘要由CSDN通过智能技术生成

一.文件IO

1.和标准IO的比较

IO类型标准IO文件IO
代表打开的文件文件流指针(FILE *)文件描述符(fd)
打开文件fopen函数open函数
关闭文件fclose函数close函数
读写操作fgetc/fputc fgets/fputs fread/fwrite fprintf/fscanfread/write
非读写操作fseek ftell rewindlseek dup dup2 fcntl stat unlink umask …

2.文件描述符

标准IO中使用文件流指针代表打开的文件,文件IO中使用文件描述符来代表打开的文件,文件描述符本身是一个非负整数,文件描述符对应一个结构体(struct file)

file结构体内部保存了文件打开的各种信息,文件描述符和file结构体的对应关系保存在文件表中,每启动一个程序就会有一个文件表,通过描述符可以找到对应的file结构。

文件描述符从0开始分配,0,1和2已经被系统占用吗,分别代表 标准输入 标准输出和标准错误。分配的描述符从3开始,从小到大分配,描述符可以重复使用

3.打开文件 --------- 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);
参数:

    pathname:要打开的文件的路径

    flags:打开标志

        必选标志:O_RDONLY(只读) O_WRONLY(只写) O_RDWR(读写)

        可选标志:

                O_CREAT:创建标志,打开的文件不存在则创建

                O_EXCL:必须创建标志,和 O_CREAT 联用,文件存在则报错

                O_TRUNC:截断标志,在打开文件时清空文件内容

                O_APPEND:追加标志,以追加的方式打开文件

        注:多个标志之间用|连接

    mode:创建时使用,代表文件的权限(使用三位8进制表示,比如 0644 0755)

成功返回新的文件描述符,失败返回-1

fopen(path,"r"); --- open(path,O_RDONLY);

fopen(path,"r+"); --- open(path,O_RDWR);

fopen(path,"w"); --- open(path,O_WRONLY|O_CREAT|O_TRUNC,0666);

fopen(path,"w+");--- open(path,O_RDWR|O_CREAT|O_TRUNC,0666);

fopen(path,"a"); --- open(path,O_WRONLY|O_CREAT|O_APPEND,0666);

fopen(path,"a+"); --- open(path,O_RDWR|O_CREAT|O_APPEND,0666);
open("a.txt",O_RDWR|O_CREAT|O_EXCL,0644);

4.关闭文件 -------- close函数

#include <unistd.h>
int close(int fg);

传入要关闭的文件描述符,close和open应该一一对应

5.读写操作 ----------- read/write函数

#include <unistd.h>
ssize_t read(int fd,void *buf,size_t count);

ssize_t write(int fd,const void *buf,size_t count);
参数:

    fd:要读/写的文件描述符

    buf:读/写的目标/来源地址

    count:希望读/写数据的大小(字节)

成功返回实际读/写数据的大小,返回0表示读到末尾/什么都没写,出错返回-1

read(0,buf,99);//标准输入---键盘
write(1,"hello\n",6);//标准输出---屏幕
write(2,"welcome\n",8);//错误输出---屏幕

6.非读写操作

(1)移动文件读写位置 --------- lseek函数

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函数和fseek几乎一样,只有两个区别,第一个是lseek使用文件描述符代表文件,第二个是lseek成功返回移动后的位置离文件开头的距离

lseek(fd,0SEEK_END);

(2)复制文件描述符 ---------- dup/dup2函数

dup/dup2函数用于复制文件描述符,但是不复制文件表。

dup函数由系统选定新的描述符的值(系统会选择最小的未使用的描述符值来使用),dup2函数手动指定新的描述符值,如果指定的值已经被使用,先将描述符关闭再使用(不安全)

#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfs,int newfd); 
参数:

    oldfd:要复制的旧的描述符

    newfd:复制的新的文件描述符

成功返回新的描述符的值,失败返回-1

dup(fd1);
dup2(fd1,4);

(3)fcntl函数

fcntl函数实现了很多功能,包括复制文件描述符,获取/设置描述符状态信息 文件锁…,参数cmd决定了使用什么功能

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd,int cmd,.../*arg*/);
参数:

    fd:操作的文件描述符

    cmd:要进行的操作

        ①F_DUPFD --------- 复制文件描述符

        ②F_GETFL/F_SETFL ----- 获取/设置文件描述符的状态

        ③F_GETLK/F_SETLK/F_SETLKW ----- 文件锁

返回值和功能相关,失败返回-1

①复制文件描述符

这个时候第三个参数需要传入新的描述符的值,和dup2的区别在于不会强行关闭已使用的文件描述符,而是去寻找大于等于参数的最小未使用的值

fcntl(fd1,F_DUPFD,4);
②获取/设置文件描述符的状态

其实就是获取/设置文件描述符的标志,比如创建标志/权限标志…,设置时只能设置O_APPEND,获取时只能获取权限和O_APPEND,创建标志获取不到

fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags);

获取的结果通过返回值返回

③文件锁

当多个程序同时操作一个文件时,可能引发数据错乱,解决这个问题的方法包括使用 进程同步或者文件锁,文件锁就是当有一个程序在对文件进行操作的时候,对其他程序的读写操作进行限制。文件锁以下方法进行操作,再读写文件之前加锁,读写完成解锁。

加锁----->读写文件----->解锁

文件锁锁定的不是读写操作本身,锁定的是加锁的操作

文件锁分为读锁和写锁,读锁是一个共享锁,写锁是一个互斥锁

有一个程序加读锁,不允许其他程序加写锁,允许其他程序加读锁

有一个程序加写锁,既不允许其他程序加读锁,也不允许加写锁

F_SETLK/F_SETLKW用于给文件加锁/解锁,F_GETLK不使用用于获取锁,而是测试某个锁能不能加上,并不会真正加锁

使用文件锁,会用到第三个参数,第三个参数是锁的结构体:

struct flock
 {
	 ...
	short l_type;    /* 锁的类型: F_RDLCK(读锁),
	 						F_WRLCK(写锁), F_UNLCK(解锁) */
	 short l_whence;  /* 锁的位置的基准:
								SEEK_SET, SEEK_CUR, SEEK_END */
	off_t l_start;   /* 锁的起始位置相对于基准的偏移*/
	off_t l_len;     /* 锁定的长度(字节) */
	pid_t l_pid;     /* F_GETLK时使用,一般给-1
						(F_GETLK only) */
	 ...
};

加锁解锁的用法:

fcntl(fd,F_SETLK,&读锁);

read(fd....)

fcntl(fd,F_SETLK,&解锁);


fcntl(fd,F_SETLK,&写锁);

write(fd....)

fcntl(fd,F_SETLK,&解锁);

F_SETLK加锁时,加锁失败也立即返回,F_SETLKW加锁时,如果加不上锁,就会在原地睡眠等待

(4)stat函数 ------------ 获取文件的信息

stat函数通过传出参数传出文件的信息

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path,struct stat *buf);
int fstat(int fd,struct stat *buf);
int lstat(const char *path,struct stat *buf); 
参数:

    path:文件路径

    buf:传出参数,传出文件的信息
    
    struct stat
     {
        dev_t     st_dev;     /* 文件对应的设备ID */
        ino_t     st_ino;     /* inode值 */
        mode_t    st_mode;    /* 文件类型/文件权限 */
        nlink_t   st_nlink;   /* 链接数 */
        uid_t     st_uid;     /* 所有者ID */
        gid_t     st_gid;     /* 所有者组ID */
        dev_t     st_rdev;    /* 设备号(设备文件) */
        off_t     st_size;    /* 文件大小 */
        blksize_t st_blksize; /* 文件系统中块大小 */
        blkcnt_t  st_blocks;  /* 文件所占块数 */
        time_t    st_atime;   /* 最后访问时间 */
        time_t    st_mtime;   /* 最后修改时间 */
        time_t    st_ctime;   /* 最后信息修改时间 */
	  };

成功返回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?(Not in POSIX.1-1996.)
S_ISSOCK(m) socket?(Not in POSIX.1-1996.)

②获取文件权限 ----- 取st_mode的低3位八进制位

stat中的时间都是time_t类型,可以将其转换成 年月日星期时分秒 格式,转换时候用ctime函数

#include <time.h>
time_t time(time_t *t);

char *ctime(const time_t *timep);

注:如果文件是一个软链接,stat获取的是软链接指向的文件的信息,lstat获取的是软链接本身的信息

7.其他非读写操作函数

(1)access函数 ------------ 测试文件权限和文件是否存在

#include <unistd.h>
int access(const char *pathname,int mode);
参数:

    pathename:文件路径

    mode:测试的内容

        R_OK:读权限

        W_OK:写权限

        X_OK:执行权限

        F_OK:是否存在

返回0表示有这个权限/文件存在,返回-1表示没有这个权限/文件不存在,出错

(2)chmod函数 ------------ 修改权限

#include <sys/stat.h>
int chmod(const char *path,mode_t mode);
参数:

    pathe:文件路径

    mode:权限

与命令用法相同

(3)remove函数 ----------- 删除文件和空目录

#include <stdio.h>
int remove(const char *pathname);
unlink函数 ------------ 删除文件

rmdir函数 ------------- 删除空目录

与命令用法相同

(4)mkdir函数 ----------- 创建目录

与命令用法相同

(5)truncate函数 --------- 指定文件大小

#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path,off_t length); 

将path文件指定大小为length

(6)rename函数 ---------- 文件改名

#include <stdio.h>
int rename(const char *oldpath,const char *newpath);

(7)chdir函数 ---------- 切换工作目录

#include <unistd.h>
int chdir(const  char *path);

(8)getcwd函数 -------- 获取当前目录

#include <unistd.h>
int *getcwd(char *buf,size_t size);

(9)umask函数 --------- 修改系统的权限屏蔽字

为了文件访问的安全,系统会有一个文件的权限屏蔽字,权限屏蔽字中记录的权限不会出现在创建的文件中,通过命令/函数 umask 可以查询/修改权限屏蔽字

#include <sys/stat.h>
#include <sys/types.h>
mode_t umask(mode_t mask);

传入新的权限屏蔽字,返回旧的权限屏蔽字

8.读目录操作

打开目录 ---------> 读取目录 --------> 关闭目录

使用目录操作函数需要包含 dirent.h 头文件

(1)打开目录 -------- opendir函数

#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
参数:

    name:目录的路径

成功返回目录流指针(DIR *),失败返回NULL

(2)关闭目录 ----------- closedir函数

#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
参数:

        dirp:目录流指针

传入目录流指针,closedir和opendir一一对应

(3)读取目录 ------------- readdir函数

该函数每被调用一次会读取到目录的一个子项,直到目录子项全部被读取

#include <dirent.h>
struct dirent *readdir(DIR *dirp);
参数:

    dirp:目录流指针

    struct dirent 
    {
        ino_t          d_ino;       /* inode number */
        off_t          d_off;       /* offset to the next dirent */
        unsigned short d_reclen;    /* length of this record */
        unsigned char  d_type;      /* 子项文件类型 */
        char           d_name[256]; /* 子项的文件名 */
    };

成功返回一个代表子项的dirent结构,目录读完出错返回-1

d_type的取值:

DT_BLK      This is a block device.

DT_CHR      This is a character device.

DT_DIR      This is a directory.

DT_FIFO     This is a named pipe (FIFO).

DT_LNK      This is a symbolic link.

DT_REG      This is a regular file.

DT_SOCK     This is a UNIX domain socket.

DT_UNKNOWN  The file type could not be determined.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java.L

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值