Linux学习笔记6 文件操作——文件描述符

基于文件描述符的文件操作

进程一启动,内核就打开了三个描述符,0(标准输入 STDIN),1(标准输出STDOUT), 2(标准错误输出STDERR)。Linux用整形数做文件操作,因此称为文件描述符,文件描述符是一个较小的整数(0~1023)。

内核——文件描述符

内核为进程维护一个已打开文件的记录表,文件描述符代表记录表里的一项,通过描述符和操作函数,即可实现文件操作。
常用基于文件描述符的函数有:open(打开)、creat(创建)、close (关闭)、read(读取)、write(写入)、ftruncate(改变文件大小)、lseek(定位)、fsync (同步)、fstat(获取文件状态)、fchmod(权限)、flock(加锁)、fcntl(控制文件属性)、 dup(复制)、dup2、select

常用函数

  • 打开文件
    int open(const char *pathname, int flags); //文件名 打开方式
    int open(const char *pathname, int flags, mode_t mode); //文件名,打开方式,权限

flags和Mode都是一组掩码的合成纸,flag表示打开方式,mode表示访问权限
以下为flags:

掩码作用
O_RDONLY以只的方式打开
O_WRONLY以只的方式打开
O_RDWR读写的方式打开
O_CREAT若文件不存在则创建文件
O_EXCL若文件已存在则创建失败返回已存在
O_TRUNC若文件存在,将长度截为0
O_APPEND追加的方式打开文件,每次调用write时,文件指针自动移到文件尾。(一般用于多进程写同一个文件)
O_NONBLOCK非阻塞的方式打开,无论有没有数据读取或者等待,都会立刻返回进程。
O_SYNC同步打开文件,当数据被真正写入**物理设备(磁盘)**后才返回
  • 读写文件
    ssize_t read(int fd, void *buf, size_t count); //文件描述符,缓冲区,长度
    ssize_t write(int fd, const void *buf, size_t count); //同上
    以下为文件指针和文件描述符转换的两个接口:
    int fileno(FILE *stream); //将文件指针FILE *fp转换成文件描述符fd
    FILE *fdopen(int fd, const char *mode); //将文件描述符fd转换成文件指针fp
#include <func.h>

int main(int argc,char *argv[])
{
    FILE *fp=fopen(argv[1],"rb+");
    if(NULL==fp)
    {   
        perror("fopen");
        return -1; 

    }   
    int fd=fileno(fp);//将文件指针转换为文件描述符fd
    printf("fd=%d\n",fd);                                                         
    int arr[3]={1,2,3};
    //char buf[128]="hello";
    //write(3,buf,strlen(buf));
    write(fd,arr,sizeof(arr));
    //close(fp);
    return 0;
}
               
  • 改变文件大小
    int ftruncate(int fd, off_t length);
    函数 ftruncate 会将参数 fd 指定的文件大小改为参数 length 指定的大小。参数 fd 为已打开的文件描述词,而且必须是以写入模式打开的文件。如果原来的文件大小比参数 length大,则超过的部分会被删去。函数执行成功则返回 0,失败返回-1。
#include <func.h>                                                                                             

void change(int fd)//可以传入fd而不用传入地址,因为fd描述的是一个打开的文件对>
{
    ftruncate(fd,1);//将文件大小改为1个字节


}

int main(int argc,char *argv[])
{
    int fd=open(argv[1],O_RDWR);
    if(-1==fd)
    {   
        perror("open");
        return -1; 

    }   
    ftruncate(fd,4);//将文件大小改成4个字节
    change(fd);
    close(fd);
    return 0;
}

  • 文件定位
    off_t lseek(int fd, off_t offset, int whence); //fd为文件描述符
    whence可以是以下三个值:
    SEEK_SET 从文件头开始计算
    SEEK_CUR 从当前指针开始计算
    SEEK_END 从文件尾开始计算
    注:文件空洞:假设file中写入hello,此时用 lseek(fd,1000,SEEK_SET) 则会偏移1000个字节,
    hello与末尾之间的“0”称之为文件空洞。
#include <func.h>

int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,2);
    int fd=open(argv[1],O_RDWR);
    if(-1==fd)
    {   
        perror("open");
        return -1;    

    }   
    int ret;                                                                                                  
    ret=lseek(fd,1000,SEEK_SET);//文件空洞
    printf("ret=%d\n",ret);
    write(fd,"1",1);
    close(fd);
    return 0;

}



文件空洞

  • 获取文件信息
    int fstat(int fd, struct stat *buf); //文件描述符 stat结构体指针
    stat结构体

  •  struct stat {
            dev_t     st_dev;     /* ID of device containing file */   //设备,返回设备描述符,没有设备则返回0
            ino_t     st_ino;     /* inode number */   //文件inode信息
            mode_t    st_mode;    /* protection */  //文件类型
            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; /* blocksize for filesystem I/O */  //块大小,LINUX下每一块为512B
            blkcnt_t  st_blocks;  /* number of 512B blocks allocated */  //块数
            time_t    st_atime;   /* time of last access */   //最后访问时间
            time_t    st_mtime;   /* time of last modification */  //最后修改时间
            time_t    st_ctime;   /* time of last status change */  //最后权限修改时间
        };
    
  • 文件描述符的复制
    系统调用函数dup和dup2可以实现米文件描述符的复制,常用于重定向进程的STDIN(0),STDOUT(1),STDERR(2)
    int dup(int oldfd);
    int dup2(int oldfd, int newfd);

#include <func.h>

int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,2);
    int fd=open(argv[1],O_RDWR);//此时fd在内核结构体指针数组为3
    if(-1==fd)
    {   
        perror("open");
        return -1; 

    }   
    int fd1=dup(fd);//成功例子,此时fd1在内核结构体指针数组为4
    //int fd1=fd;//失败例子,fd无法直接赋值给fd1,需要dup
    printf("fd1=%d\n",fd1);//for test fd1在内核结构体指针数组下标为4,因此打印出来为4                         
    close(fd);
    char buf[128]={0};
    int ret=read(fd1,buf,sizeof(buf));
    if(-1==ret)
    {   
        perror("read");
        return -1;                                                                                            
    }   
    printf("ret=%d,buf=%s\n",ret,buf);
    return 0;
}

失败例子的原理:
int fd1=fd;
close(fd);
read(fd,buf,sizeof(buf);
perror(“read”);
一旦close(fd)后,3的结构体指针被free,fd1无法指向文件对象,因此执行后显示bad file descriptor
内核原理如下图所示:

失败案例内核原理

dup不同,dup会在结构体指针数组中复制出3号的复制品4号fd1,指向相同的文件对象( int fd1=dup(fd) ),具体内核原理如下图:

成功案例内核原理

因此可知,文件描述符的复制是指用另外一个文件描述符指向同一个打开的文件,它完全不同于直接给文件描述符变量赋值。两个描述符( fd和fd1 )共享同一个数据结构,需注意,dup 返回新的文件描述符是没有使用的文件描述符的最小编号
由于dup 返回新的文件描述符是没有使用的文件描述符的最小编号,则可以进行重定向标准输出
假设将标准输出1关闭(close(1)),再使用 dup ,则 fd1 的值为1;

#include <func.h>
//    重定向标准输出
int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,2);
    int fd=open(argv[1],O_RDWR);
    if(-1==fd)
    {   
        perror("open");
        return -1; 
    }   
    printf("\n");//刷新标准输入缓冲区
    close(1);//关闭标准输入缓冲区
    int fd1=dup(fd);//此时fd1的值则为1(没有使用的文件描述符的最小编号为1)
    printf("fd1=%d\n",fd1);//由于标准输入缓冲区关闭了,printf会写到文件中
    close(fd);
    printf("I am mark2 \n");//测试是否会写到文件里                                                            
    return 0;
}

dup会自动去找没有使用的文件描述符的最小编号,当要指定一个文件描述符的位置复制时,则可以使用dup2(fd,1) //则会复制 fd 并将 fd1 的文件描述符设置为1,具体代码如下:

#include <func.h>
//    重定向标准输出
int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,2);
    int fd=open(argv[1],O_RDWR);
    if(-1==fd)
    {   
        perror("open");
        return -1; 
    }   
    printf("\n");//刷新标准输入缓冲区
    int fd1=dup2(fd,1);//指定1为fd1的文件描述符编号,dup2会自动close(1)                                       
    printf("fd1=%d\n",fd1);//由于标准输入缓冲区关闭了,printf会写到文件中
    close(fd);                                                                                                
    printf("you cant see me\n");//测试是否会写到文件里
    return 0;
}



  • 内存映射文件(mmap)
    void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
    mmap将一个文件(以页为单位),映射进内存。降低cpu对文件读取的干预,提高性能。
    使用场景:当存在多进程或者多线程对同一个文件进行操作——mmap
    mmap参数:
    star:一般填写NULL意为由操作系统帮助我们寻找一块空闲的地址
    length:文件长度,采用缺页异常设计
    prot:内存权限,一般只用PROT_READ(页内容可读),PROT_WRITE(页内容可写),采用按位或的操作。
    flags:MAP_SHARED 与其他所有映射此对象的进程共享映射空间(即多进程共享一个文件)
    fd:文件描述符
    offset:只能是4K的整数倍。 一般不偏移,填写0

代码如下:

#include <func.h>

int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,2);//参数设置
    int fd=open(argv[1],O_RDWR);//以读写的方式打开文件描述符
    if(-1==fd)
    {   
        perror("open");
        return -1; 

    }   
    char *p; 
    struct stat buf;//定义一个文件状态结构体
    fstat(fd,&buf);//获取文件大小
    p=mmap(NULL,buf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if((char *)-1==p)
    {   
        perror("mmap");
        return -1; 

    }   
    strcpy(p,"HELLOWORLD");
    munmap(p,buf.st_size);//回写到磁盘中                                      
    close(fd);
    return 0;

}
           

int munmap(void *addr, size_t length); //写回磁盘的接口
int msync(void *addr, size_t length, int flags); //同步写入磁盘的接口

Q:mmap和read,write有什么区别,为什么要使用mmap?
A:read,write会进行数据的多(两)次拷贝,而mmap为零拷贝。read和write要进行多次的数据搬移,而mmap不需要,因此性能较read和write较好。

以下为两次数据搬移(即两次拷贝)
读写的两次拷贝

以下为mmap原理图:
mmap原理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
文件上传是Web开发中常见的功能之一,Java中也提供了多种方式来实现文件上传。其中,一种常用的方式是通过Apache的commons-fileupload组件来实现文件上传。 以下是实现文件上传的步骤: 1.在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency> ``` 2.在前端页面中添加文件上传表单: ```html <form method="post" enctype="multipart/form-data" action="upload"> <input type="file" name="file"> <input type="submit" value="Upload"> </form> ``` 3.在后台Java代码中处理上传文件: ```java // 创建一个DiskFileItemFactory对象,用于解析上传的文件 DiskFileItemFactory factory = new DiskFileItemFactory(); // 设置缓冲区大小,如果上传的文件大于缓冲区大小,则先将文件保存到临时文件中,再进行处理 factory.setSizeThreshold(1024 * 1024); // 创建一个ServletFileUpload对象,用于解析上传的文件 ServletFileUpload upload = new ServletFileUpload(factory); // 设置上传文件的大小限制,这里设置为10MB upload.setFileSizeMax(10 * 1024 * 1024); // 解析上传的文件,得到一个FileItem的List集合 List<FileItem> items = upload.parseRequest(request); // 遍历FileItem的List集合,处理上传的文件 for (FileItem item : items) { // 判断当前FileItem是否为上传的文件 if (!item.isFormField()) { // 获取上传文件文件名 String fileName = item.getName(); // 创建一个File对象,用于保存上传的文件 File file = new File("D:/uploads/" + fileName); // 将上传的文件保存到指定的目录中 item.write(file); } } ``` 以上代码中,首先创建了一个DiskFileItemFactory对象,用于解析上传的文件。然后设置了缓冲区大小和上传文件的大小限制。接着创建一个ServletFileUpload对象,用于解析上传的文件。最后遍历FileItem的List集合,判断当前FileItem是否为上传的文件,如果是,则获取文件名,创建一个File对象,将上传的文件保存到指定的目录中。 4.文件上传完成后,可以给用户一个提示信息,例如: ```java response.getWriter().write("File uploaded successfully!"); ``` 以上就是使用Apache的commons-fileupload组件实现文件上传的步骤。需要注意的是,文件上传可能会带来安全隐患,因此在处理上传的文件时,需要进行严格的校验和过滤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值