Linux_文件IO

1.文件IO

文件IO:是通过系统调用实现的,只要调用文件IO的函数接口就会从用户空间进入到内核空间,系统调用的效率比较低,因为没有缓冲区的概念。文件IO常见的接口open,read,write,close,lseek等

1.1什么是文件描述符

文件描述符:文件描述符本身是一个大于等于0的整数,使用open打开文件之后返回文件描述符,以后在操作文件的时候通过这个文件描述符完成。在一个正在执行的程序中文件描述的范围是0-1023(1024个),可以通过ulimit -a查看限制,这个限制值是可以修改的(ulimit -n 2048)。文件描述符的分配原则是最小未使用。在一个正在执行的程序中默认已经有三个文件描述符了

ulimit -a
open files            (-n) 1024

0:标准输入文件描述符

1:标准输出文件描述符

2:标准错误文件描述符

#include <head.h>

int main(int argc,const char * argv[])
{
    //在标准IO的文件指针结构体中其实默认包含文件描述符_fileno
    printf("%d\n",stdin->_fileno); //标准输入文件描述符 0
    printf("%d\n",stdout->_fileno); //标准输出文件描述符 1
    printf("%d\n",stderr->_fileno); //标准出错文件描述符 2
    return 0;
}

1.2open函数的使用

#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_APPEND   以追加的方式打开文件
        O_CREAT    创建文件,必须填写第三个参数,第三个参数代表创建这个文件的权限
        O_TRUNC    清空文件
        O_EXCL     确保文件存在,跟O_CREAT结合使用,如果文件存在就返回EEXIST已存在错误,
        			如果文件不存在正常创建
    @mode:只有在第二个flags参数写O_CREAT的时候才需要填充,第三个参数代表创建文件的权限
           mode & (~umask)   //umask的值0002            umask 0000
        					  ~umask=0664               ~umask=0666
           文件的最大权限是0666   mode & 0664           mode & 0666
返回值:成功返回文件描述符,失败返回-1置位错误码    
标准IO文件IO含义
“r”O_RDONLY只读的方式打开文件
“r+”O_RDWR以读写的方式打开文件
“w”O_WRONLY|O_CREAT|O_TRUNC,0664以只写的方式打开文件,如果文件存在就清空,如果文件不存在就创建
“w+”O_RDWR|O_CREAT|O_TRUNC,0664以读写的方式打开文件,如果文件存在就清空,如果文件不存在就创建
“a”O_WRONLY|O_APPEND|O_CREAT,0664以追加(结尾写)的方式打开文件,如果文件不存在就创建文件
“a+”O_RDWR|O_APPEND|O_CREAT,0664以追加(结尾写,开头读)的方式打开文件,如果文件不存在就创建文件。

1.2.1open函数的使用(w+)

head.h

#ifndef __HEAD_H__
#define __HEAD_H__

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
                                                                                           
#define PRINT_ERR(msg) do{ \
        perror(msg);\
        return -1;\
    }while(0)

#endif
#include <head.h>

int main(int argc,const char * argv[])
{
    int fd;
    fd = open("hello.c",O_RDWR|O_CREAT|O_TRUNC,0666);
    if(fd == -1)
        PRINT_ERR("open error");
        

    return 0;
}

1.2.2open函数的使用(O_EXCL)

#include <head.h>

int main(int argc, const char *argv[])
{
    int fd;
    //读写的方式打开文件,如果文件不存在就创建文件
    //如果文件存在就报文件已存在的错误EEXIST
    fd = open("hello.c", O_RDWR | O_CREAT | O_EXCL, 0666);
    if (fd == -1)
    {
        if (errno == EEXIST)
        {
            //文件已经存在,不需要创建,直接打开
            printf("文件已经存在,不需要创建,直接打开\n");
            fd = open("hello.c", O_RDWR, 0666);
        }
        else
        {
            PRINT_ERR("open error");
        }
    }

    return 0;
}

1.3close函数的使用

#include <unistd.h>
int close(int fd);
功能:关闭文件
参数:
    @fd:文件描述符
返回值:成功返回0,失败返回-1置位错误码
#include <head.h>

int main(int argc, const char *argv[])
{
    int fd;
    //读写的方式打开文件,如果文件不存在就创建文件
    //如果文件存在就报文件已存在的错误EEXIST
    fd = open("hello.c", O_RDWR | O_CREAT | O_EXCL, 0666);
    if (fd == -1)
    {
        if (errno == EEXIST)
        {
            //文件已经存在,不需要创建,直接打开
            printf("文件已经存在,不需要创建,直接打开\n");
            fd = open("hello.c", O_RDWR, 0666);
        }
        else
        {
            PRINT_ERR("open error");
        }
    }
	 printf("fd = %d\n",fd);  //这里的fd是3
    
    //每一个正在执行的程序都有0-1023这1024个文件描述符
    //关闭这个程序中的文件描述符,不影响其他程序的文件描述符。
    close(fd);
    return 0;
}

1.4read函数的使用

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据到buf中
参数:
    @fd:文件描述符
    @buf:保存读取到的数据的首地址
    @count:读取的大小(单位是字节)
返回值:成功返回读取到的字符的个数,0表示读取到了结尾
        失败返回-1,置位错误码

1.5write函数的使用

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:将buf中的数据写入到文件中
参数:
    @fd:文件描述符
    @buf:写数据的首地址
    @count:写的大小(单位字节)
返回值:成功返回写入字符的个数,失败返回-1置位错误码

1.5.1使用read/write实现文件的拷贝

#include <head.h>

int main(int argc,const char * argv[])
{
    int sfd,dfd;
    char buf[128];
    int ret;
    if(argc != 3){
        //如果错误,向2标准出错文件描述符中写入错误的信息
        write(2,"input error,try again\n",strlen("input error,try again\n"));
        write(2,"usage:./a.out srcfile destfile\n",strlen("usage:./a.out srcfile destfile\n"));
        return -1;
    }
    if((sfd = open(argv[1],O_RDONLY))==-1)
        PRINT_ERR("open src error");

    if((dfd = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0664))==-1)
        PRINT_ERR("open src error");
    
    //当read读取到数据大于0的时候循环写,如果小于等于0的时候
    //要退出循环
    while((ret = read(sfd,buf,sizeof(buf)))>0){
        write(dfd,buf,ret);
    }

    close(sfd);
    close(dfd);
    
    return 0;
}

2.文件IO中lseek函数

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);
功能:修改文件中光标的位置
参数:
	@fd:文件描述符
    @offset:偏移
        	>0向后偏移
        	=0不偏移
        	<0向前偏移
    @whence:从哪开始偏移
        	SEEK_SET:从开头开始
        	SEEK_CUR:从当前位置
        	SEEK_END:从结尾开始
 返回值:成功返回光标当前位置到开头的字节数
         失败返回(off_t)-1置位错误码  lseek=fseek+ftell
#include <head.h>

int main(int argc,const char * argv[])
{
    int fd;

    if((fd = open("./milaoshu.bmp",O_RDONLY))==-1)
        PRINT_ERR("open error");

    //将光标定位到文件的结尾,返回结尾到开头的位置
    //这个结果就是图片大小
    printf("img size = %ld\n",lseek(fd,0,SEEK_END));

    close(fd);    
    return 0;
}

练习:使用文件IO对图片打马赛克

#include <head.h>
#include <stdlib.h>

typedef struct{
    unsigned int img_size; //图片的大小
    unsigned int img_width;//图片的宽
    unsigned int img_high; //图片的高
    unsigned short img_bitcount; //一个像素点占用的bit(24bit)
}image_info_t;

typedef struct{
    unsigned char b;
    unsigned char g;
    unsigned char r;
}point_t;

void show_image_info(image_info_t *info)
{

    printf("size = %d,width = %d,high = %d,bitcount = %d\n",
    info->img_size,info->img_width,info->img_high,info->img_bitcount);
}

void get_image_info(int fd,image_info_t *info)
{
    lseek(fd,2,SEEK_SET);
    read(fd,&info->img_size,4);

    lseek(fd,18,SEEK_SET);
    read(fd,&info->img_width,4);
    read(fd,&info->img_high,4); 

    lseek(fd,2,SEEK_CUR);
    read(fd,&info->img_bitcount,2); 
}

void copy_image_file(int sfd,int dfd)
{
    int ret;
    char buf[1024] = {0};
    
    while((ret = read(sfd,buf,sizeof(buf)))>0){
        write(dfd,buf,ret);
    }
    
    return;
}
void set_image_mosaic(int fd,image_info_t *info,int x,int y)
{
    int i,j,k,w;
    point_t color = {0,0,0xff};
    char *buffer = (char *)malloc((info->img_width)*(info->img_high)*3);
    //1.将图像读取回来
    lseek(fd,54,SEEK_SET);
    read(fd,buffer,(info->img_size-54));
    //2.修改
    //i:整体的高/10
    //j:整体的宽除以10
    //k:块的高
    //w:块的宽
    for(i=0;i<info->img_high/y;i++){
        for(j=0;j<info->img_width/x;j++){
            //读取小方块中最左上角的像素点
            color = *(point_t *)(buffer+(j*3*x)+(i*y*info->img_width*3));
            for(k=0;k<y;k++){
                for(w=0;w<x;w++){
                      *(point_t*)(buffer+w*3+(k*info->img_width*3)+
                      (j*3*x)+(i*y*info->img_width*3)) = color;
                }
            }
        }
    }

    //3.重新将图像写回去
    lseek(fd,54,SEEK_SET);
    write(fd,buffer,(info->img_size-54));
    free(buffer);
}
int main(int argc, char const *argv[])
{
    int sfd,dfd;
    int size;
    image_info_t info;
    char new_name[20] = {0};

    if(argc != 2){
        fprintf(stderr,"input error,try again\n");
        fprintf(stderr,"usage:./a.out xxxx.bmp\n");
        return -1;
    }
    //1.打开文件并拷贝文件 milaoshu.bmp
    if((sfd = open(argv[1],O_RDONLY))==-1)
        PRINT_ERR("open error");
    
    //构造一个新图片的字符串  new_milaoshu.bmp
    snprintf(new_name,sizeof(new_name),"new_%s",argv[1]);

    //打开新图片,如果不存在就创建,如果存在就清空
    if((dfd = open(new_name,O_RDWR|O_CREAT|O_TRUNC,0664))==-1)
        PRINT_ERR("open error");

    //图片的拷贝,将milaoshu.bmp-->new_milaoshu.bmp    
    copy_image_file(sfd,dfd);

    //2.获取图片前54个字节中有用的信息
    get_image_info(dfd,&info);

    show_image_info(&info);

    //3.尝试打马赛克
    //10,10:代表的是打马赛克每个小方块的大小
    //10*3= 30
    //10  = 10行
    set_image_mosaic(dfd,&info,10,10);

    //4.关闭源文件和目标文件
    close(sfd);   
    close(dfd); 
    return 0;
}

3.文件描述符拷贝

3.1文件描述符的直接赋值

使用变量直接赋值方式拷贝文件描述符,其实文件描述符并没有得到拷贝,只是在程序中多了一个变量,这两个变量对应的是同一个文件描述符。通过文件描述符操作文件两者是相互影响的。

#include <head.h>

int main(int argc,const char * argv[])
{
    int fd,fd1;
    char buf[10];
    if((fd = open("./hello.txt",O_RDWR|O_APPEND|O_CREAT,0664))==-1)
        PRINT_ERR("open error");

    write(fd,"helloworld\n",strlen("helloworld\n"));
    //文件描述符分配的规则是最小为使用原则所以fd是3
    //fd1也是3
    fd1=fd;

    write(fd1,"12345678\n",strlen("12345678\n"));
    printf("fd = %d,fd1 = %d\n",fd,fd1);

    lseek(fd,3,SEEK_SET);

    //因为fd1和fd对应的都是3这个整数值,所以通过fd修改的
    //光标的位置一定会影响到fd1
    read(fd1,buf,sizeof(buf));
    printf("buf = %s\n",buf);

    if(close(fd))
        PRINT_ERR("close fd error");
    if(close(fd1)) //这次关闭文件描述符会出错,因为在程序中只有一个文件描述符是3
        PRINT_ERR("close fd1 error");
    return 0;
}

3.2文件描述符拷贝之dup函数

int dup(int oldfd);
功能:拷贝oldfd得到一个新的fd
参数:
    @oldfd:旧的文件描述符
返回值:成功返回新的文件描述符,失败返回-1置位错误码
/*
dup()系统调用创建文件描述符oldfd的副本,使用编号最低的未使用文件描述符作为新描述符。
成功返回后,旧的和新的文件描述符可以互换使用。它们引用相同的打开文件描述(参见open(2)),
因此共享文件偏移量和文件状态标志;例如,如果通过在其中一个文件描述符上使用lseek(2)修改
文件偏移量,那么另一个文件描述符的偏移量也会改变。这两个文件描述符不共享文件描述符标
志(close-on-exec标志)。close-on-exec标志(FD_CLOEXEC;请参阅fcntl(2)),以了解重复描述
符是关闭的。
*/
#include <head.h>

int main(int argc,const char * argv[])
{
    int fd,fd1;
    char wbuf[10]="hello123\n";
    char rbuf[10];
    if((fd = open("./hello.txt",O_RDWR|O_TRUNC|O_CREAT,0664))==-1)
        PRINT_ERR("open error");
    
    printf("fd = %d\n",fd);

    //dup函数会拷贝生成一个新的文件描述符,
    //新旧文件描述符都可以同时操作同一个文件
    //这两个文件描述符共用同一个光标,lseek修改其中一个光标的位置
    //另外一个文件描述符对应的光标也会该表
    //新拷贝出来的文件描述符和旧的文件描述符数值是不同的
    fd1=dup(fd);
    printf("fd1 = %d\n",fd1);

    write(fd,wbuf,strlen(wbuf));

    lseek(fd,0,SEEK_SET);

    read(fd1,rbuf,sizeof(rbuf));
    printf("rbuf = %s\n",rbuf);
    
    if(close(fd))
        PRINT_ERR("close fd error");
    if(close(fd1)) 
        PRINT_ERR("close fd1 error");
    return 0;
}

3.3使用open打开多次同一个文件

使用open没打开一次文件,就会产生一个文件描述符,这些文件描述符之间没有任何的关系,也不共用光标,所以对一个文件描述光标的修改,不会影响另外一个。

#include <head.h>

int main(int argc,const char * argv[])
{
    int fd,fd1;
    char wbuf[10]="hello123\n";
    char rbuf[10];

    if((fd = open("./hello.txt",O_RDWR))==-1)
        PRINT_ERR("open error");
    if((fd1 = open("./hello.txt",O_RDWR))==-1)
        PRINT_ERR("open error");

    lseek(fd,11,SEEK_SET);
    read(fd,rbuf,sizeof(rbuf));
    printf("rbuf = %s\n",rbuf);

    read(fd1,rbuf,sizeof(rbuf));
    printf("rbuf = %s\n",rbuf);


    if(close(fd))
        PRINT_ERR("close fd error");
    if(close(fd1)) 
        PRINT_ERR("close fd1 error");
    return 0;
}

3.4文件描述符拷贝之dup2函数

int dup2(int oldfd, int newfd);
功能:将oldfd文件描述符拷贝成新的文件描述符,没有使用最小未分配原则,
    如果newfd之前是打开的,就关闭这个newfd。
参数:
    @oldfd:旧的文件描述符
    @newfd:新的文件描述符
返回值:成功返回新的文件描述符,失败返回-1置位错误码 
#include <head.h>

int main(int argc,const char * argv[])
{
    int fd;
    char buffer[] = "helloworld\n";

    if((fd = open("./hello.txt",O_RDWR))==-1)
        PRINT_ERR("open error");

    //功能:将文件描述符2拷贝成fd,fd是3,由于fd对应之前
    //有打开的文件,他会将这个文件关闭,以后使用文件描述符2和3
    //都是向标准错误中输出
    // printf("fd = %d\n",fd);
    // dup2(2,fd);
    // write(fd,buffer,strlen(buffer));
    // write(2,"1234567\n",strlen("1234567\n"));
    // printf("fd = %d\n",fd);


    //将fd拷贝成0 1 2,会先关闭0 1 2这三个文件描述符
    //以后在向0 1 2 中输入信息,就相当于向文件中输入信息
    //如下文件描述符拷贝的意思是:比如在开发服务器的时候
    //服务器的执行会脱离某一个终端,以后服务器的输入,输出,错误
    //这些信息都会被放入到这个open打开的文件中(日志文件)
    dup2(fd,0);
    dup2(fd,1);
    dup2(fd,2);

    printf("i am test dup2 func .....\n");
    fflush(stdout);
    //lseek(fd,0,SEEK_SET);
    // 0 1 2 fd这个4个文件描述符都是在向文件中读写内容,共用同一个光标
    write(2,"i am test 123456789\n",strlen("i am test 123456789\n"));

    return 0;
}

4.文件状态获取(stat)

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

int stat(const char *pathname, struct stat *statbuf);
功能:获取文件的属性信息
参数:
    @pathname:文件的路径和文件的名
    @statbuf:获取到的属性信息的结构体
          struct stat {
               dev_t     st_dev;         //磁盘的设备号 (设备号是操作系统识别硬件设备的编号)
               ino_t     st_ino;         //inode(ls -i) 文件系统识别文件的唯一编号
               mode_t    st_mode;        //文件的类型和文件的权限 (man 7 inode)
              
			   //文件类型的获取方式
              		S_IFMT     0170000    //文件类型的掩码
               //if((st_mode & S_IFMT)== S_IFREG) <==> S_ISREG(st_mode) 就是普通文件
                   S_IFSOCK   0140000   socket         //S_ISSOCK(m)
                   S_IFLNK    0120000   symbolic link  //S_ISLNK(m)
                   S_IFREG    0100000   regular file   //S_ISREG(m)
                   S_IFBLK    0060000   block device   // S_ISBLK(m)
                   S_IFDIR    0040000   directory       // S_ISDIR(m)
                   S_IFCHR    0020000   character device //S_ISCHR(m)
                   S_IFIFO    0010000   FIFO             //S_ISFIFO(m)
                //文件权限的获取方式
                   st_mode & 0777  ====>获取到的结果就是文件的权限
                   
               nlink_t   st_nlink;       //文件的硬链接数
               uid_t     st_uid;         //用户的id
               gid_t     st_gid;         //组的id号
               dev_t     st_rdev;        //如果是设备文件,显示的是文件的设备号 /dev/input/mouse0 
              																13<<20|32;							
               off_t     st_size;        //文件的大小,单位是字节
               blksize_t st_blksize;     //每一个block的大小 (512字节)
               blkcnt_t  st_blocks;      //block的个数
               struct timespec st_atim;  //最后一次访问的时间
               struct timespec st_mtim;  //最后一次修改的时间
               struct timespec st_ctim;  //最后一次文件状态改变的时间
           };
返回值:成功返回0,失败返回-1置位错误码

4.1获取文件的属性信息实例

#include <head.h>

int main(int argc,const char * argv[])
{
    struct stat st;
    
    if(stat("./hello.txt",&st)==-1)
        PRINT_ERR("get file stat error");

    printf("type = %#o,mode = %#o,size=%ld,inode=%ld\n",
    st.st_mode&S_IFMT,st.st_mode& 0777,st.st_size,st.st_ino);

    return 0;
}

在这里插入图片描述

4.2获取文件状态的练习

要求:请向程序输入一个文件名,将这个文件的类型(文件,目录…),权限,文件的inode号等信息输出

#include <head.h>

int main(int argc, const char *argv[])
{
    struct stat st;

    if (argc != 2)
    {
        fprintf(stderr, "input error,tryagain\n");
        fprintf(stderr, "usage:./a.out pathname\n");
        return -1;
    }
    if (stat(argv[1], &st) == -1)
        PRINT_ERR("get file stat error");

    printf("%#o\n",st.st_mode & S_IFMT);
    switch (st.st_mode & S_IFMT)
    {
    case 0140000:
        printf("这是套接字文件\n");
        break;
    case 0120000:
        printf("软连接文件\n");
        break;
    case 0100000:
        printf("这是普通文件\n");
        break;
    case 0060000:
        printf("这是块设备文件\n");
        break;
    case 0040000:
        printf("这是目录文件\n");
        break;
    case 0020000:
        printf("这是字符设备文件\n");
        break;
    case 0010000:
        printf("这是管道文件\n");
        break;
    default:
        printf("文件类型错误...\n");
        return -1;
    }

    printf("mode = %#o,size=%ld,inode=%ld\n",
           st.st_mode & 0777, st.st_size, st.st_ino);

    return 0;
}

5.对目录的操作

5.1打开目录(opendir)

#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);
功能:打开一个目录
参数:
    @name:目录名
返回值:成功返回目录的指针,失败返回NULL置位错误码

5.2读取目录下的内容(readdir)

struct dirent *readdir(DIR *dirp);
功能:读取目录下的内容
参数:
    @dirp:目录指针
返回值:成功返回结构体指针,失败返回或者到目录结尾返回NULL,通过errno来区分

struct dirent {
   ino_t          d_ino;       //inode号
   off_t          d_off;       //偏移
   unsigned short d_reclen;    //结构体大小
   unsigned char  d_type;      //文件的类型
   char           d_name[256]; //文件名
};   

5.3关闭目录(closedir)

int closedir(DIR *dirp);
功能:关闭目录
参数:
    @dirp:目录指针
返回值:成功返回0,失败返回-1置位错误码

5.4目录操作的实例

#include <head.h>
#include <dirent.h>

int main(int argc,const char * argv[])
{
    DIR* dir;
    struct dirent *dt;
    if((dir = opendir("./"))==NULL)
        PRINT_ERR("opendir error");
    errno=0;

    while((dt=readdir(dir))!=NULL){
        printf("name=%-15s,type=%#o,struct_size=%d,inode=%ld\n",
        dt->d_name,dt->d_type,dt->d_reclen,dt->d_ino);
    }
    if(errno){
        PRINT_ERR("read dir error");
    }

    closedir(dir);

    return 0;
}

5.5编写类似下载文件的实例

在程序中指定一个服务的目录,在程序执行的时候输入文件名,如果文件存在打印这个文件的信息,如果文件不存在打印文件不存在(重新输入)。
//文件的类型
char d_name[256]; //文件名
};


## 5.3关闭目录(closedir)

```c
int closedir(DIR *dirp);
功能:关闭目录
参数:
    @dirp:目录指针
返回值:成功返回0,失败返回-1置位错误码

5.4目录操作的实例

#include <head.h>
#include <dirent.h>

int main(int argc,const char * argv[])
{
    DIR* dir;
    struct dirent *dt;
    if((dir = opendir("./"))==NULL)
        PRINT_ERR("opendir error");
    errno=0;

    while((dt=readdir(dir))!=NULL){
        printf("name=%-15s,type=%#o,struct_size=%d,inode=%ld\n",
        dt->d_name,dt->d_type,dt->d_reclen,dt->d_ino);
    }
    if(errno){
        PRINT_ERR("read dir error");
    }

    closedir(dir);

    return 0;
}

5.5编写类似下载文件的实例

在程序中指定一个服务的目录,在程序执行的时候输入文件名,如果文件存在打印这个文件的信息,如果文件不存在打印文件不存在(重新输入)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值