【Linux】分析缓冲区,刷新机制,FILE

news2024/11/18 2:36:52

文章目录

  • 一、Linux的缓冲区
    • (一) 用户层缓冲区
    • (二) 内核层缓冲区(Kernel Buffer Cache)
        • 验证buffer增加和减少
        • 释放缓存
  • 二、缓冲区的刷新策略
    • (一) 用户层缓冲区刷新策略
    • (二) 内核层缓冲区刷新策略
  • 三、探究缓冲区常见问题的产生
    • (一) 由于缺失换行符导致内容没有按预期呈现
      • 1、实验设计
      • 2、原理分析
    • (二) 由于提前close(fd)导致内容无法呈现
      • 1、实验设计
      • 2、原理分析
    • (三) dup重定向不改变缓冲区刷新方式
      • 1、实验设计
      • 2、原理分析
        • (1) 用例子阐述原理
        • (2) FILE发生读写时才分配得缓冲区
        • (3) 探究FILE刷新策略何时被指定
    • (四) fork前没有清空缓冲区
  • 四、总结
      • 本文做了以下工作或得出以下结论
      • 之后的工作
      • 知识补充


一、Linux的缓冲区

在学习中我们会经常遇到两个缓冲区概念,一个是用户层的缓冲区,另一个是内核层的缓冲区。本文主要讨论用户层缓冲区的知识点以及不同的坑

(一) 用户层缓冲区

标准IO库自带缓冲区,像stdinstdoutstderr这些都是FILE*文件流,FILE*指向一个FILE结构体,结构体包含了缓冲区基地址和末尾地址,还封装了fd

FILE结构体关键代码如下:

struct _IO_FILE {
    int _flags;
    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. */    
    struct _IO_marker *_markers;      
    struct _IO_FILE *_chain;      
    int _fileno;    /* fd */
};

设置用户层缓冲区的目的和好处:为了减少read,write等系统调用的次数,从而减少用户态和内核态的切换次数,降低系统的开销

(二) 内核层缓冲区(Kernel Buffer Cache)

内核层缓冲区为buffercache,它们位于内核空间,被所有进程可见。buffer和cache是内存的不同的体现,它们搭建了CPU和磁盘快速交互的桥梁

  • buffer存储暂未写入到磁盘的数据,积攒到一定量后写入磁盘,可以降低和磁盘IO的频率
  • cache实现数据预读的功能:可以暂时存储来自磁盘的数据,提高这部分数据重用性,使得OS无需频繁访问磁盘

设置内核缓冲区的好处:内核缓冲区数据不写回磁盘也能被其它进程读取,在这点的作用上和磁盘存储文件无异,直接读取内核缓冲区的数据,带来了读写的高效性

验证buffer增加和减少

增加buffer
对上述命令的解释如下:

 读取/dev/zero文件时,它会提供无限的空字符nul,一个常见的用法是产生一个特定大小的空白文件
 创建一个1000M的txt文件,其内容为空:
	 dd if=/dev/zero of=test.txt count=10M bs=100
	 if:输入文件,默认为标准输入
	 of:输出文件名,默认为标准输出
 	 bs:块大小,同时设置读入/输出的块大小为bytes个字节
 	 count:块个数

释放缓存

在书房缓存前先指向sync讲缓存的数据写到磁盘避免数据丢失,随后输入
echo 3 >/proc/sys/vm/drop_caches释放slab和页缓存
在这里插入图片描述

二、缓冲区的刷新策略

(一) 用户层缓冲区刷新策略

缓冲区有三种类型对应三种刷新缓冲区的方式:

  1. 全缓冲
    当填满标准I/O缓存后才进行实际I/O操作,如将数据从用户层缓冲区拷贝到内核缓冲区。全缓冲的典型代表是对磁盘文件的读写

  2. 行缓冲
    当输入和输出中遇到换行符时才执行实际I/O操作,典型代表是标准输入stdin和标准输出stdout

  3. 无缓冲
    不对数据进行缓冲,直接进行I/O,如标准错误stderr就是无缓冲刷新

缓冲区何时会被刷呢&刷新方法:

  1. 调用exit()进程结束时会刷新缓冲区,return会自动调用exit(),注意_exit()不会刷新缓冲区
  2. 缓冲区满了也会被刷新出来
  3. 可通过fflush强制将缓冲流中的数据复制到内核缓冲区中
  4. 流被关闭时也会被刷出来,如调用fclose函数
  5. 行缓冲遇见'\n'会被刷新出来

(二) 内核层缓冲区刷新策略

Linux以页作为高速缓存的单位,因此刷新内核缓冲区即对页的管理,操作系统会基于LRU 算法回收文件页和匿名页,当缓冲区内容被修改则变为脏页,其数据在合适的时间将会被写到磁盘中去,以保证高速缓存中的数据和磁盘中的数据是一致的。此外:可以通过sync命令可以将内存中的数据写入到硬盘中

三、探究缓冲区常见问题的产生

(一) 由于缺失换行符导致内容没有按预期呈现

1、实验设计

编写一段代码(见下),预期是先输出“hello world”,再sleep3秒

#include <stdio.h>    
#include <unistd.h>    
    
int main()    
{    
    printf("hello world");    
    sleep(3);                                                                          
    return 0;    
} 

运行结果如下:

程序运行先sleep了
随后才打印语句
随后才打印“hello world”

2、原理分析

当我们调用printf函数往显示器打印字符串时,采用的是行刷新模式,printf底层调用stdout这个流文件,当遇到\nstdout能立马刷新FILE结构体维护的缓冲区。而上面代码并没有携带\n,故hello world这个语句一直停留再FILE维护的缓冲区中,直到最后return 0;语句调用exit函数,exit执行清理缓冲区的操作,hello world才刷新到屏幕,此时已经到程序末尾,故会出现先sleep才打印字符串的现象

(二) 由于提前close(fd)导致内容无法呈现

1、实验设计

编写一段代码(见下),先以写权限打开txt文档,然后利用dup2将1号fd标准输出重定向到txt文档,最后向txt文档写入Hello 1,结束后关闭打开的文件

#include <stdio.h>    
#include <stdlib.h>                                                                    
#include <unistd.h>    
    
int main()    
{    
    FILE *pfd = fopen("text.txt","w");    
    int fd = fileno(pfd);    
    if (fd<0){    
        perror("open error\n");    
        exit(1);    
    }    
    dup2(fd,1);    
    printf("Hello 1\n");    
    fclose(pfd);    
    close(1);    
    return 0;    
}   

运行结果如下:
写入txt失败
实现结果显示Hello 1并没有成功写入到txt文档中去,txt文档大小为0

2、原理分析

 分析代码可知,重定向后printf语句是往txt文档写入,那么此时采用的是全缓冲刷新printf调用后内容一直存在FILE的缓冲区当中,当遇到exit或者fflush或者缓冲区满的时候才刷新,而在return语句前close(1),那么return时调用exit刷新FILE的缓冲区时,拿到FILE封装的fd后,发现fd对应的文件被关闭,无法刷新缓冲区,导致txt内容为空
解决方法:可以在close(fd)前用fclose(stdout)fflush(stdout)提前刷新出来

Q:有读者可能会疑问,在close(1)之前执行了fclose(pfd),即关闭了txt的文件流,那么缓冲区的内容应该被刷新了啊,txt应该有内容啊?

A:要注意分清文件流,向txt文档写入内容是printf函数,故而字符串语句保存在stdout这个文件流的FILE结构体中,所以在close(1)之前关闭了txt的文件流pfd并不能将stdoutFILE结构体中缓冲区内容刷新出去,pfdstdout文件流是互相独立的


Q:有读者可能继续追问,在fclose(pfd)之前执行了dup2(fd,1),即1号fd指向txt的fd,那么fclose(pfd)应该也能刷新1号fd,那么stdoutFILE的内容应该会被刷新到文档中啊?

A:首先对于“ 那么fclose(pfd)应该也能刷新1号fd ”这句话是错误的,因为pfdFILE结构体封装的文件描述符一直都是txt的fd,并不会因为重定向了而改变。其次对于“ xxx刷新1号fd,那么stdout的FILE的内容应该会被刷新到文档中 ”也是错的,fd是内核层概念,在本文探讨内容之内对fd的操作是不会影响FILE结构体的,即对内核层fd操作不能刷新用户层的FILE结构体的缓冲区,但是你刷新用户层的FILE结构体的缓冲区能影响到fd对应的文件,因为FILE结构体封装了fd

(三) dup重定向不改变缓冲区刷新方式

1、实验设计

在上面代码基础上在dup2前加入一行printf("Hello 0\n")代码见下),根据上面的分析txt文档的内容应该为空,终端输出Hello 0

#include <stdio.h>    
#include <stdlib.h>    
#include <unistd.h>    
    
int main()    
{    
    FILE *pfd = fopen("text.txt","w");    
    int fd = fileno(pfd);    
    if (fd<0){    
        perror("open error\n");    
        exit(1);    
    }    
    printf("Hello 0\n");  //增加一行代码                                                             
    dup2(fd,1);    
    printf("Hello 1\n");    
    fclose(pfd);    
    close(1);    
    return 0;    
}

运行结果如下:
在这里插入图片描述
实验结果显示,终端确实输出了Hello 0,但是txt文档居然出现了Hello 1,根据上面第(二)点分析应该是空才对,究竟是为什么呢,难道是上面的分析错了?

2、原理分析

首先上面第(二)点分析没错,此处出现这个诡异现象是由其它知识点造成的,直接给出结论:

  • FILE结构体获得时,里面的fd被填充,但是缓冲区还没有被分配,且缓冲刷新方式还没指定
  • 只有当FILE真正发生读写,如printf到屏幕,fwrite到文件,此时FILE才真正分配得缓冲区,且缓冲刷新方式被永久指定
  • dup重定向无法改变FILE的缓冲刷新方式

基于这些结论对上述诡异现象进行解释:
当程序执行到printf("Hello 0\n");时,发生向屏幕写的行为,FILE结构体的缓冲区还被分配,缓冲刷新方式被指定为行缓冲刷新,之后即使dup2重定向,stdout这个FILE结构体一直是行缓冲刷新,刷新方式不会被更改,故而在执行printf("Hello 1\n");时内容直接以行缓冲刷新的方式刷新到txt里,在close(fd)前就已经刷新了内容,所以最终txt里有"Hello 1"


以下对上述结论深入理解和验证

(1) 用例子阐述原理

如何理解缓冲刷新方式被永久指定,举个例子:
当调用printf往屏幕输出信息,此时stdout这个FILE流封装了1号fd,缓冲区被分配,缓冲区刷新方式被指定为行缓冲刷新。后续我们打开了一个txt文件,设其fd=3,我们调用dup2(3,1)后再调用printfprintf仍是行刷新到txt文件内,并不会因为txt是文件而更换为全缓冲刷新,因为FILE的缓冲区刷新方式只能被被指定一次

(2) FILE发生读写时才分配得缓冲区

验证:当FILE结构体获得时其缓冲区还没有被分配,当FILE发生读写时才分配得缓冲区
代码如下,参考深究标准IO的缓存:

#include <stdlib.h>                                                                    
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>          
                                   
int main()
{                 
    char buf[24];     
    FILE *myfile = stdin;
    printf("before reading\n");
    printf("myfile base %p\n", myfile);
    printf("read buffer base %p\n", myfile->_IO_read_base);
    printf("read buffer length %ld\n", myfile->_IO_read_end - myfile->_IO_read_base);
    printf("write buffer base %p\n", myfile->_IO_write_base);                            printf("write buffer length %ld\n", myfile->_IO_write_end - myfile->_IO_write_base);                                  printf("buf buffer base %p\n", myfile->_IO_buf_base);    printf("buf buffer length %ld\n", myfile->_IO_buf_end - myfile->_IO_buf_base);
                                                                                     
    printf("\n");                                                                     
    fgets(buf, 24, myfile);//read                                 
    printf("after reading\n");                           
    printf("read buffer base %p\n", myfile->_IO_read_base);                       
    printf("read buffer length %ld\n", myfile->_IO_read_end - myfile->_IO_read_base);
    printf("write buffer base %p\n", myfile->_IO_write_base);
    printf("write buffer length %ld\n", myfile->_IO_write_end - myfile->_IO_write_base);
    printf("buf buffer base %p\n", myfile->_IO_buf_base);
    printf("buf buffer length %ld\n", myfile->_IO_buf_end - myfile->_IO_buf_base);
    return 0;                                                                        
}

结果如下:
在这里插入图片描述
从实验结果可以看到,还没发生读取时缓冲区地址还没分配,在读入hello后,缓冲区被分配,大小为1024Bytes

(3) 探究FILE刷新策略何时被指定

验证:当FILE结构体获得时其缓冲刷新策略还没有被指定,当FILE发生读写时首次且永久指定刷新策略
首先指出在FILE结构体里的_flags变量的作用相当于位图,它的某些位表示了缓冲区刷新方式

对【 (二) 1、实验设计】中的代码,即还没增加printf("Hello 0\n");的代码进行调试
在这里插入图片描述
可以看到,当代码执行完printf("Hello 1\n");_flags值改变,具体而言是低8位到低15位从0x20变为0x28。接下来我们深入printf函数看看到底执行了什么导致标志位改变
在这里插入图片描述
_flags按位与_IO_CURRENTLY_PUTTING后,_flags这个位图某些位发生以下变化

0x20:0010 0000
0x28:0010 1000

综上:【 (二) 1、实验设计】中的代码,重定向后执行printfstdout采取的是全缓冲刷新,深入调试查看源代码发现,_flags_IO_CURRENTLY_PUTTING标志位被设置,表示缓冲区内容被设置,但是没有出现对_flags行缓冲标志位的设置


那么要对_flags设置行缓冲应该设置什么标志位呢?接下来对【 (三) 1、实验设计】中的代码,即增加printf("Hello 0\n");的代码进行调试
在这里插入图片描述
可以看到,当代码执行完printf("Hello 0\n");,即stdout首次发生读写后_flags值改变,具体而言是低8位到低15位从0x20变为0x22。继续深入printf函数看看到底执行了什么导致标志位改变
在这里插入图片描述
_flags按位与_IO_LINE_BUF后,_flags这个位图某些位发生以下变化

0x20:0010 0000
0x22:0010 0010

故而当FILE结构体是执行行缓冲刷新策略时,_flags位图的_IO_LINE_BUF标志位被设置为1
此外,在设置_IO_LINE_BUF标志位后,由于缓冲区有内容了,所以_IO_CURRENTLY_PUTTING标志位也会被设置,故最终执行完printf("Hello 0\n");_flags8位到低15位从0x20变为0x2a

0x20:0010 0000
0x2a:0010 1010

此外笔者还对setvbuf函数进行测试,该函数能指定了文件缓冲的模式,函数原型如下:

int setvbuf(FILE *stream, char *buffer, int mode, size_t size)

关于函数的细节可见这个网站的说明:函数用法
经过设置不同的参数,分别进行gdb调试,再结合上述调试成果,最终得到对_flags位图中有关于文件缓冲模式相关的位探究清楚了,结论见下图:

在这里插入图片描述

(四) fork前没有清空缓冲区

fork前用户层缓冲区仍有数据,在fork后父子进程的缓冲区都保留这些数据,故而当fork后执行输出时,会出现有些内容重复输出了两次。对此建议读者对用户层缓冲区的刷新机制有所了解,当不确定缓冲区内容是否刷新出去时可以调用fflush函数强制刷新

四、总结

本文做了以下工作或得出以下结论

  1. 系统区分了用户层和内核层缓冲区,指出两者不同之处和特点
  2. 归纳了用户层缓冲区的三种刷新策略/文件缓冲的模式
  3. 分析了用户层缓冲区引起的常见问题
  4. 在源码层面分析了FILE结构体,尤其是关于缓冲区和_flags位图,总结了刷新策略在_flags位图上的体现

之后的工作

  • 尝试从系统数据结构角度分析一个进程运行到结束,printf从调用到输出的流程,预想到的知识点有以下:
    • task_struct,内存布局,页表
    • files_structfd_arraystruct fileinode,文件引用次数,VFS
  • 分析对比联系FILEstruct fileinodefd之间关系,如何逐层调用

知识补充

零拷贝技术
本文主要讨论了IO,在传统IO中,当有两个fd需要数据流动,如磁盘文件fd和网络文件fd通信,需要先将数据从磁盘拷贝到内核缓冲区,用户再调用系统接口read到用户层,然后再write到内核缓冲区,最后由内核缓冲区将数据刷新到网络文件fd。其过程冗长且拷贝频繁,该数据流动过程见下图橙色线,那么有没有更高效的IO方式呢?
在这里插入图片描述

答案是有的,通过零拷贝技术可以完成上图绿色线的数据流动,即数据只通过内核层就能到达对方fd,零拷贝技术有:sendfilemmap

对文章内容的总结图:
在这里插入图片描述

说明:此图部分素材来自网络,侵权删

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

相对于java,C++中的那些神奇语法

空指针还可以调用成员函数 #include <cstdio>class Person { public:void sayHello() {printf("hello!\n");} };int main() {auto * p new Person;p->sayHello();p nullptr;p->sayHello();return 0; }运行结果如下&#xff1a; hello! hello!进程已结…

【深入理解java虚拟机】JVM故障处理工具介绍

目录前言一、jps&#xff1a;虚拟机进程状况工具一、一 输出远程机器信息二、jstat&#xff1a;虚拟机统计信息监视工具三、jinfo&#xff1a; Java配置信息工具四、jmap&#xff1a; Java内存映像工具五、jhat&#xff1a;虚拟机堆转储快照分析工具六、jstack&#xff1a; Jav…

问:毁掉一个人,有多容易?答:年龄到了就可以

人到中年&#xff0c;有点难。 曾在虎扑论坛上看到一篇爆帖&#xff1a; 标题是《loser回忆录&#xff1a;一年前我月薪两万被人叫X总&#xff0c;如今在美团送外卖》&#xff0c;63万浏览量&#xff0c;回复也超过了2300条。 如题&#xff0c;帖子的主人公是一个35岁的男人…

node-sass安装失败解决方法

node-sass安装失败&#xff0c;提示如下&#xff1a; gyp verb check python checking for Python executable "python" in the PATH gyp verb which succeeded python D:Program FilesPython38python.EXE gyp ERR! configure error gyp ERR! stack Error: Command f…

nvidia显卡驱动、cuda、cudnn、tensorflow对应版本

1、下载显卡驱动 在nvidia官网下载驱动&#xff0c;驱动官网选择设备的驱动进行搜索下载即可&#xff0c;搜索时注意对应的操作系统 一般为安装NVIDIA Studio驱动版本&#xff0c;GeForce Game Ready适用于游戏玩家&#xff0c;下面是两个版本区别的官方解释 下载完的驱动会以…

关键路径 ← AOE网

【问题描述】 给定一个只有一个源点和一个汇点的有向图&#xff0c;要求求出所有的关键活动&#xff0c;并计算完成该工程至少需要多少时间。【输入格式】 第一行包含两个整数 n 和 m&#xff0c;表示顶点数和边数。 接下来 m 行&#xff0c;每行包含三个整数 u&#xff0c;v&a…

p2p开户银行审核模块功能实现

审核模块简介 用户提交开户申请后要等待审核通过才能审核成功 审核需要银行系统进行开户 使用flask框架搭建一个银行系统 用户提交审核 银行进行开户&#xff0c;在返回p2p后台通过审核 flask搭建测试银行系统 利用工厂模式搭建一个flask框架 app.py from flask import Fl…

程序员必看内容连续集之 Redis 03

目录 一、Spring整合Redis 二、注解式开发 一、Spring整合Redis ①项目的pom文件导入依赖并修改 <redis.version>2.9.0</redis.version> <redis.spring.version>1.7.1.RELEASE</redis.spring.version><dependency><groupId>redis.clien…

(附源码)计算机毕业设计SSM抗新冠肺炎药品进销存管理系统

&#xff08;附源码&#xff09;计算机毕业设计SSM抗新冠肺炎药品进销存管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 …

Synchronized锁的使用

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章是关于并发编程中Synchronized锁的用法知识记录&#xff0c;由于篇幅原因&#xff0c;核心原理知识下篇记录。 本篇文章记录的基础知识&#xff0c;适合在学Java的…

进程的基本概念(操作系统)

目录 一、程序的顺序执行及其特征 二、程序的并发执行及其特征 三、进程的特征与状态 1、进程的定义和特征 2、进程的三种基本状态 3、进程的三种基本状态的转换 4、 挂起状态 四、进程控制块&#xff08;PCB&#xff09; 1. PCB作用&#xff1a; PCB是进程存在的唯一…

【力扣】正则表达式匹配--回溯法解剖

题目&#xff1a;10.正则表达式匹配 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 . 和 * 的正则表达式匹配。 . 匹配任意单个字符 * 匹配零个或多个前面的那一个元素 所谓匹配&#xff0c;是要涵盖 整个 字符串 s的&#xff0c;而不是部分字符串。 示…

maven配置阿里镜像,解决IDEA配置maven恢复默认配置问题

文章目录1.三个IDEA配置说明2.拷贝与修改settings.xml2.1 找到IDEA的mave配置文件settings.xml位置2.2 拷贝IDEA的settings.xml到.m2目录下2.3 打开settings.xml&#xff0c;配置本地库LocalRepository的路径2.4 删除默认镜像配置&#xff0c;配置阿里镜像2.5 保存文件3.验证是…

面试时,MySQL这些基础知识你回答的出来吗?

目录 【一】前言 【二】MySQL的并发控制 【三】数据库的事务 【四】隔离级别 【五】死锁 【六】存储引擎 6.1 InnoDB存储引擎 6.2 MyISAM存储引擎 【七】总结 【一】前言 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB公司开发&#xff0c;属于Oracle…

一文就读懂RPC远程调用核心原理

rpc核心原理 什么是rpc&#xff1f; rpc的全称是Remote Procedure Call&#xff0c;即远程过程调用&#xff0c;是分布式系统的常用通信方法。 Remote&#xff0c;简单来说的话就是两个不同的服务之间&#xff0c;两个服务肯定是两个不同的进程。因此&#xff0c;我们就从跨进…

【微服务】微服务万字实战,带你了解工程原理

微服务实战1、前期准备1.1 技术选型1.2 模块设计1.3 微服务调用2、创建父工程3、创建基础模块3.1 导入依赖3.2 创建实体类4、创建用户微服务4.1 创建shop-user模块4.2 用户微服务启动类4.3 创建配置文件5、创建商品微服务5.1 创建shop_product模块5.2 商品微服务启动类5.3 创建…

刷题日记【第六篇】-笔试必刷题【最近公共祖先+求最大连续bit数+二进制插入+查找组成一个偶数最接近的两个素数】

目录 选择题模块 1.下面哪个标识符是合法的&#xff1f;&#xff08;D&#xff09; 2.以下描述正确的是&#xff08;B&#xff09; 3.下列程序的运行结果&#xff08;B&#xff09; 4.下列关于容器集合类的说法正确的是&#xff1f;&#xff08;C&#xff09; 5.ArrayList…

【MySQL进阶】深入理解InnoDB记录结构

【MySQL进阶】深入理解InnoDB记录结构 参考资料&#xff1a;《MySQL是怎么运行的&#xff1a;从根儿上理解MySQL》。 前言&#xff1a; 我们一般使用的MySQL关系型数据库&#xff0c;更是经典中的经典&#xff0c;虽说MySQL已经非常成熟&#xff0c;但对于MySQL的掌握程度&a…

腾讯Java888道高频面试真题笔记+Java面试宝典

这多半年你是否达到了你年初定的目标&#xff0c;今年企业招聘要求也是更加的严格&#xff0c;对于低学历,以及技术实力不过关的更是雪上加霜。也是由于种种缘由&#xff0c;从5月开始就一直有粉丝私信要博主整理一些干货来帮助他们提升下自己&#xff0c;为了响应粉丝要求&…

Non-Autoregressive Coarse-to-Fine Video Captioning【论文阅读】

Non-Autoregressive Coarse-to-Fine Video Captioning 发表&#xff1a;AAAI 2021idea&#xff1a;&#xff08;1&#xff09;针对推理阶段不能并行&#xff0c;推理效率低的问题使用一种双向解码&#xff08;在bert中不使用sequence mask&#xff09;。&#xff08;2&#xf…