文件IO[self-study]

113 阅读12分钟

标准C库IO函数与Linux系统IO函数

  • 标准C库IO函数相较于Linux系统IO函数,具有跨平台的优势,它可以针对不同的系统调用相应的API来实现同样的操作。(JAVA通过虚拟机实现跨平台)
  • 标准C库IO函数与Linux系统IO函数是调用与被调用的关系
  • 标准C库IO函数比Linux系统IO函数效率高(缓冲区的功劳)
  • 一般网络通信用Linux系统IO函数,对磁盘读写的时候用标准C库的IO函数

IO1.png 标准C库IO函数的核心在于缓冲区,如果直接用Linux系统内核的read和write函数,每次读写都要重新访问一次磁盘,访问磁盘需要花费很多时间,IO的缓冲区很大程度减少了对磁盘的访问次数,提高了read和write函数的使用效率

标准C库函数

IO2.png

FILE 结构体

文件指针FILE结构体代码如下

struct _IO_FILE
{
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
  char *_IO_read_ptr;	/* Current read pointer */
  char *_IO_read_end;	/* End of get area. */
  char *_IO_read_base;	/* Start of putback+get area. */
  char *_IO_write_base;	/* Start of put area. */
  char *_IO_write_ptr;	/* Current put pointer. */
  char *_IO_write_end;	/* End of put area. */
  char *_IO_buf_base;	/* Start of reserve area. */
  char *_IO_buf_end;	/* End of reserve area. */

  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;//文件描述符
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it's too small.  */

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

补充知识

虚拟地址空间

虚拟空间.png

文件描述符

文件描述符.png

Linux系统IO函数

IO3.png 不同page存储不同手册:

man.png

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);

int open(const char *pathname, int flags, mode_t mode);
/*
        参数:
            - pathname:要创建的文件的路径
            - flags:对文件的操作权限和其他的设置
                - 必选项:O_RDONLY,  O_WRONLY, O_RDWR  这三个之间是互斥的
                - 可选项:O_CREAT 文件不存在,创建新文件
            - mode:八进制的数,表示创建出的新的文件的操作权限,比如:0775
            最终的权限是:mode & ~umask
            0777   ->   111111111
        &   0775   ->   111111101
        ----------------------------
                        111111101
        按位与:0和任何数都为0
        umask的作用就是抹去某些权限。

        flags参数是一个int类型的数据,占4个字节,32位。
        flags 32个位,每一位就是一个标志位。
*/

open有两个函数,但是这并不是函数重载。mode_t mode是一个可选参数 前者主要用于打开已有文件 后者主要用于创建一个新的文件,因为是创建新文件,所以我们还要设置各个用户组对这个文件的权限

如果发生错误,我们可以使用perror函数打印发生的错误

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {

    // 打开一个文件
    int fd = open("a.txt", O_RDONLY);

    if(fd == -1) {
        perror("open");
    }
    // 读写操作

    // 关闭
    close(fd);

    return 0;
}

对比前者,这个open多了个mode_t mode参数用来设置文件权限 采用3位8进制来表示不同用户/用户组对这个文件的权限

rwx 每个位上的1表示可用0表示不可用

r 读权限

w 写权限

x 执行权限

但是我们直接设置权限可能发生不合理的情况,因此再传入mode参数后,还需要进一步处理 最终的值为mode & ~umask umask用于抹去一些不合理的权限设置,我们可以直接输入umask查看当前用户的umask

#root用户默认是0022,普通用户默认是 0002

read&write

使用read和write函数实现文件拷贝

/*  
    #include <unistd.h>
    ssize_t read(int fd, void *buf, size_t count);
        参数:
            - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
            - buf:需要读取数据存放的地方,数组的地址(传出参数)
            - count:指定的数组的大小
        返回值:
            - 成功:
                >0: 返回实际的读取到的字节数
                =0:文件已经读取完了
            - 失败:-1 ,并且设置errno

    #include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count);
        参数:
            - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
            - buf:要往磁盘写入的数据,数据
            - count:要写的数据的实际的大小
        返回值:
            成功:实际写入的字节数
            失败:返回-1,并设置errno
*/
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {

    // 1.通过open打开english.txt文件
    int srcfd = open("english.txt", O_RDONLY);
    if(srcfd == -1) {
        perror("open");
        return -1;
    }

    // 2.创建一个新的文件(拷贝文件)
    int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
    if(destfd == -1) {
        perror("open");
        return -1;
    }

    // 3.频繁的读写操作
    char buf[1024] = {0};
    int len = 0;
    while((len = read(srcfd, buf, sizeof(buf))) > 0) {
        write(destfd, buf, len);
    }

    // 4.关闭文件
    close(destfd);
    close(srcfd);


    return 0;
}
lseek

控制文件指针,实现众多功能

  • 移动文件指针到文件头

lseek(fd, 0, SEEK_SET);

  • 获取当前文件指针的位置

lseek(fd, 0, SEEK_CUR);

  • 获取文件的长度

lseek(fd, 0, SEEK_END);

  • 拓展文件的长度,当前文件10b,增加了100个字节,最后位110b

lseek(fd, 100, SEEK_END);

//所属头文件
#include <sys/types.h>
#include <unistd.h>

//函数原型
off_t lseek(int fd, off_t offset, int whence);
/*  
    标准C库的函数
    #include <stdio.h>
    int fseek(FILE *stream, long offset, int whence);

    Linux系统函数
    #include <sys/types.h>
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
        参数:
            - fd:文件描述符,通过open得到的,通过这个fd操作某个文件
            - offset:偏移量
            - whence:
                SEEK_SET
                    设置文件指针的偏移量
                SEEK_CUR
                    设置偏移量:当前位置 + 第二个参数offset的值
                SEEK_END
                    设置偏移量:文件大小 + 第二个参数offset的值
        返回值:返回文件指针的位置


    作用:
        1.移动文件指针到文件头
        lseek(fd, 0, SEEK_SET);

        2.获取当前文件指针的位置
        lseek(fd, 0, SEEK_CUR);

        3.获取文件长度
        lseek(fd, 0, SEEK_END);

        4.拓展文件的长度,当前文件10b, 110b, 增加了100个字节
        lseek(fd, 100, SEEK_END)
        注意:需要写一次数据

*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {

    int fd = open("hello.txt", O_RDWR);

    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 扩展文件的长度
    int ret = lseek(fd, 100, SEEK_END);
    if(ret == -1) {
        perror("lseek");
        return -1;
    }

    // 写入一个空数据
    write(fd, " ", 1);

    // 关闭文件
    close(fd);

    return 0;
}

文件属性操作函数

access
/*
    #include <unistd.h>
    int access(const char *pathname, int mode);
        作用:判断某个文件是否有某个权限,或者判断文件是否存在
        参数:
            - pathname: 判断的文件路径
            - mode:
                R_OK: 判断是否有读权限
                W_OK: 判断是否有写权限
                X_OK: 判断是否有执行权限
                F_OK: 判断文件是否存在
        返回值:成功返回0, 失败返回-1
*/


#include<unistd.h>
#include<stdio.h>

int main(){

	int ret = access("./a.txt",R_OK);
	if(ret == -1){
		perror("access");
	}
	else printf("a.txt can read!\n");


	ret = access("./b.txt",W_OK);
	if(ret == -1){
		perror("access");
	}
	else printf("b.txt can write!\n");

	ret = access("./c.txt",X_OK);
	if(ret == -1){
		perror("access");
	}
	else printf("c.txt can X!\n");

	ret = access("./d.txt",F_OK);
	if(ret == -1){
		perror("access");
	}
	else printf("d.txt exited!\n");




	return 0;

}
chmod
/*
    #include <sys/stat.h>
    int chmod(const char *pathname, mode_t mode);
        修改文件的权限
        参数:
            - pathname: 需要修改的文件的路径
            - mode:需要修改的权限值,八进制的数
        返回值:成功返回0,失败返回-1

*/


#include<sys/stat.h>
#include<stdio.h>

void if_printf(int val){

	if(val == 0)  printf("a.txt can");
	else printf("a.txt can not");
}

int main(){

	int ret = chmod("./a.txt",0222);
	if(ret == -1){
		perror("chmod");
		// return -1;
	}
	else{
		int ret1 = access("./a.txt",R_OK);
		if_printf(ret1);
		printf("read\n");

		ret1 = access("./a.txt",W_OK);
		if_printf(ret1);
		printf("write\n");

		ret1 = access("./a.txt",X_OK);
		if_printf(ret1);
		printf("X\n");

	}

	return 0;

}
chown

使用下面两个命令可以查询用户和组的id

vim /etc/passwd
vim /etc/group

chown也就是change owner改变拥有者和用户组

int chown(const char *pathname, uid_t owner, gid_t group);
truncate
/*
    #include <unistd.h>
    #include <sys/types.h>
    int truncate(const char *path, off_t length);
        作用:缩减或者扩展文件的尺寸至指定的大小
        参数:
            - path: 需要修改的文件的路径
            - length: 需要最终文件变成的大小
        返回值:
            成功返回0, 失败返回-1
*/

#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>
int main(){
	int fd = open("./a.txt",O_RDWR);
	if(fd == -1){
		perror("open");
		return -1;
	}

	int ret1 = lseek(fd,0,SEEK_END);
	if(ret1 == -1){
	perror("lseek");
	}else printf("a.txt size is = %d\n",ret1);

	int ret = truncate("./a.txt",5);
	if(ret == -1){
	perror("truncate");
	return -1;
	}else {
		ret1 = lseek(fd,0,SEEK_END);
		printf("a.txt size now is = %d\n",ret1);
	}
	return 0;
}

目录操作函数

mkdir
/*
    #include <sys/stat.h>
    #include <sys/types.h>
    int mkdir(const char *pathname, mode_t mode);
        作用:创建一个目录
        参数:
            pathname: 创建的目录的路径
            mode: 权限,八进制的数
        返回值:
            成功返回0, 失败返回-1
*/

#include<sys/stat.h>
#include<sys/types.h>
#include<stdio.h>

int main(){

	int ret = mkdir("new_mkdir",0777);

	if(ret == -1){
		perror("mkdir");
		return -1;
	}else printf("new_mkdir create!\n");

	return 0;
}
rmdir
/*
    #include <stdio.h>
    int rename(const char *oldpath, const char *newpath);
    
    作用:更改目录名

*/

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main(){
	int ret = rmdir("./new_mkdir");

	if(ret == -1){
		perror("rmdir");
		return -1;
	}else printf("empty new_mkdir is deleted!\n");

	return 0;
}
chdir & getcwd

chdir修改进程的工作目录,类似shell当中的cd getcwd:类似shell中的pwd,获取当前的工作目录

/*

    #include <unistd.h>
    int chdir(const char *path);
        作用:修改进程的工作目录
            比如在/home/nowcoder 启动了一个可执行程序a.out, 进程的工作目录 /home/nowcoder
        参数:
            path : 需要修改的工作目录

    #include <unistd.h>
    char *getcwd(char *buf, size_t size);
        作用:获取当前工作目录
        参数:
            - buf : 存储的路径,指向的是一个数组(传出参数)
            - size: 数组的大小
        返回值:
            返回的指向的一块内存,这个数据就是第一个参数

*/


#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<stdio.h>
#include<fcntl.h>

int main(){

	char buff[256];
	getcwd(buff,sizeof(buff));
	printf("now the dir is: %s\n",buff);

	int ret = chdir("../13_chdir_getcwd_new");
	if(ret == -1){
		perror("chdir");
		return -1;
	}else printf("change success!\n");

	int fd = open("chdir.txt",O_CREAT|O_RDWR,0664);
	if(fd == -1){
		perror("open");
		return -1;
	}

	close(fd);

	char buff1[256];
	getcwd(buff1,sizeof(buff1));
	printf("now the dir is:%s\n",buff1);

	return 0;
}

目录遍历函数

万物皆文件,目录也可以看作是一个文件 对于一个目录我们也有打开,读取,关闭的相关函数,他们分别为

  • opendir 打开目录
  • readdir 读取目录
  • closedir 关闭目录
统计目录下文件数量

首先,我们需要判断输入参数是否合法,如果合法进入功能函数。 因为一个目录下还有目录,所以我们需要有个递归。 然后我们要一个个统计文件,所以我们函数里要有循环,那么这个函数的基本结构就是下面这样的

首先打开目录

进入循环,循环出去条件为读取到末尾 循环内部一个判断分支 如果是目录,就递归这个函数 如果是文件,计数器加1

最后关闭目录

返回计数器

/*
    // 打开一个目录
    #include <sys/types.h>
    #include <dirent.h>
    DIR *opendir(const char *name);
        参数:
            - name: 需要打开的目录的名称
        返回值:
            DIR * 类型,理解为目录流
            错误返回NULL


    // 读取目录中的数据
    #include <dirent.h>
    struct dirent *readdir(DIR *dirp);
        - 参数:dirp是opendir返回的结果,后面有其中具体内容
        - 返回值:
            struct dirent,代表读取到的文件的信息
            读取到了末尾或者失败了,返回NULL

    // 关闭目录
    #include <sys/types.h>
    #include <dirent.h>
    int closedir(DIR *dirp);
*/
#include<iostream>
#include<sys/types.h>
#include<dirent.h>
#include<cstdio>
#include<cstring>
#include<stdlib.h>
using namespace std;
int getFileNum(const char * path){

	DIR * dir = opendir(path);
	if(dir == NULL){
		perror("opendir");
		return 0;
	}

	struct dirent *ptr;

	int total = 0;

	while((ptr = readdir(dir)) != NULL){
		char * dname = ptr->d_name;

		if(strcmp(dname,".") == 0 || strcmp(dname,"..") == 0){
			continue;
		}

		if(ptr->d_type == DT_DIR){
			char newpath[256];
			sprintf(newpath, "%s/%s", path, dname);
			total += getFileNum(newpath);
		}

		if(ptr->d_type == DT_REG){
			total++;
		}

	}

	closedir(dir);

	return total;
}


int main(int argc, char **argv){

	int num = getFileNum(argv[1]);

	printf("普通文件的个数为:%d\n",num);



	return 0;
}
struct dirent 结构体

dirent.png

文件描述符相关函数

dup
/*
    #include <unistd.h>
    int dup(int oldfd);
        作用:复制一个新的文件描述符
        fd=3, int fd1 = dup(fd),
        fd指向的是a.txt, fd1也是指向a.txt
        从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符


*/

#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>

int main(){

	int fd = open("a.txt", O_RDWR | O_CREAT, 0664);

	if(fd == -1){
		perror("open");
		return -1;
	}

	int fd1 = dup(fd);

	if(fd1 == -1){
		perror("dup");
		return -1;
	}

	printf("fd : %d, fd1 : %d\n", fd, fd1);

	close(fd);

	char *str = "hello world!\n";

	int ret = write(fd1, str, strlen(str));

	if(ret == -1){
		perror("write");
		return -1;
	}

	close(fd1);

	return 0;

}
dup2

重定向文件描述符 两个文件a,b,两个文件描述符fd,fd1 原来fd指向a,fd1指向b 使用dup2之后可以吧fd1指向a,以后fd1读写操作就是对a文件的操作

dup2.png

/*
    #include <unistd.h>
    int dup2(int oldfd, int newfd);
        作用:重定向文件描述符
        oldfd 指向 a.txt, newfd 指向 b.txt
        调用函数成功后:newfd 和 b.txt 做close, newfd 指向了 a.txt
        oldfd 必须是一个有效的文件描述符
        oldfd和newfd值相同,相当于什么都没有做
*/

#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<cstring>

int main(){

	int fd = open("b.txt", O_RDWR);
	if(fd == -1){
		perror("open");
		return -1;
	}

	int fd1 = open("c.txt", O_RDWR);
	if(fd1 == -1){
		perror("open");
		return -1;
	}

	printf("fd: %d. fd1: %d\n",fd,fd1);

	int ret = dup2(fd,fd1);
	if(ret == -1){
		perror("dup2");
		// return -1;
	}else printf("exchang success!\n");

	char *str  = "hello,dup2!\n";
	int len = write(fd1, str, strlen(str));

	if(len == -1){
		perror("write");
		return -1;
	}

	printf("fd: %d, fd1: %d, ret: %d\n",fd,fd1,ret);

	close(fd);
	close(fd1);

	return 0;
}
fcntl
/*

    #include <unistd.h>
    #include <fcntl.h>

    int fcntl(int fd, int cmd, ...);
    参数:
        fd : 表示需要操作的文件描述符
        cmd: 表示对文件描述符进行如何操作
            - F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
                int ret = fcntl(fd, F_DUPFD);

            - F_GETFL : 获取指定的文件描述符文件状态flag
              获取的flag和我们通过open函数传递的flag是一个东西。

            - F_SETFL : 设置文件描述符文件状态flag
              必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
              可选性:O_APPEND, O_NONBLOCK
                O_APPEND 表示追加数据
                NONBLOK 设置成非阻塞
        
        阻塞和非阻塞:描述的是函数调用的行为。
        阻塞: 阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回干不完不准回来
		非阻塞:非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
*/
F_DUPFD
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>

int main(int argc, char ** argv){
	int fd = open("test.txt",O_RDWR);
	if(fd == -1){
		perror("open");
		return -1;
	}

	int ret = fcntl(fd, F_DUPFD);
	if(ret == -1){
		perror("fcntl");
		return -1;
	}

	close(fd);
	
	char buff[1024] = {0};
	int len = read(ret,buff,sizeof(buff));
	if(len == -1){
		perror("read");
		return -1;
	}
	
	printf("%s",buff);
	close(ret);
	return 0;

}
F_GETFL & F_SETFL
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>

//F_GETFL  F_SETFL
int main(){


	int fd = open("test.txt", O_RDWR);
	if(fd == -1){
		perror("open");
		return -1;
	}

	int flag = fcntl(fd, F_GETFL);
	if(flag == -1){
		perror("fcntl");
		return -1;
	}
	flag |= O_APPEND;

	int ret = fcntl(fd, F_SETFL, flag);
	if(ret == -1){
		perror("fcntl");
		return -1;
	}

	char *str = "hello\n";
	write(fd, str, strlen(str));

	close(fd);
}