基础IO-文件操作函数,文件描述符,理解缓冲区

news2024/11/19 5:28:17

image-20230128235826125

文章目录

  • 基础IO
    • 回顾c语言的文件操作函数
    • 操作系统的文件操作函数
      • open—打开文件
      • write—写入文件
      • read—读文件内容
      • 感性+现象理解文件
      • 文件描述符fd
        • 文件描述符的分配规则
      • 重定向
        • 重定向函数dup2
          • 输出重定向
          • 追加重定向
          • 输入重定向
        • 再次理解文件
    • 理解缓冲区
      • 感性理解缓冲区
    • 缓冲区刷新策略
      • 写一份缓冲区机制
        • mystdio.h
        • mystdio.c
          • 命令行清空文件: 【空格】 > 文件名
      • 内核缓冲区
        • fsync 写入函数
        • fsync 写入函数

基础IO

对电脑上的文件的理解:

1.文件=内容+属性,空文件也要在磁盘占空间

2.文件路径+文件名标识文件唯一性

3.对文件的操作即是对内容、属性的操作

4.如果没有指定文件路径,默认在当前路径进行文件访问

写代码时,运用各种文件函数接口对文件操作后,编译后形成二进制文件而没有运行时,对文件的操作没有被执行,那么 代码对文件的操作,本质上是进程对文件的操作

**而文件有被打开文件和未被打开文件之分,访问文件的前提是打开文件,所以对文件进行操作, ** 是进程对被打开文件进行操作!

回顾c语言的文件操作函数

回顾c语言学过的文件操作,操作文件首先要打开文件—要有个文件指针按相应方式打开

r按读方式打开文件,文件不存在则报错;
w按写方式打开文件,文件不存在则创建;若只打开文件然后关闭文件则清空文件内容
r+按读写方式打开文件,文件不存在则报错
w+按读写方式打开文件,文件不存在则创建,若只打开文件然后关闭文件则清空文件内容
a追加文本方式打开文件
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<string.h>
  4 #define FILE_NAME "gout.c"
  5 int main()
  6 {
  7  // FILE* fp=fopen(FILE_NAME,"w");//写方式打开
  8   FILE* fp=fopen(FILE_NAME,"r");//读方式打开
  9  if(NULL==fp)
 10  {
 11    perror("fopen fail!\n ");
 12    return 1;
 13  }
 14 
 15 char buff[64];
 16 while(fgets(buff,sizeof(buff)-1,fp)!=NULL)//把后者按规定大小读到前者里
 17 {
 18   buff[strlen(buff)-1]=0;//去掉自带的/n
 19   puts(buff);//按行打印
 20 }
 21 
 22 //int num=5;
 23 //while(num)
 24 //{fprintf(fp,"%s:%d\n","hello bug",num--);//fprintf-把后者按照规定方式写入.  25 //}
 26                                                                              
 26 
 27  fclose(fp);
 28 } 

除c语言外,其他语言也都有文件操作的函数,这些语言文件操作的库函数都是在操作系统系统调用文件操作函数之上包装的,所以下面来谈论操作系统的系统调用文件操作函数接口。

操作系统的文件操作函数

open—打开文件

open的第二个函数的第一个参数是文件,第二个参数是int flags(标记位)—以32个比特位传递选项,那么这些选项就不能重复。选项可以是O_RDONLY(只读),O_WRONLY(只写),O_RDWR(读写)O_CREAT(创建文件)O_TRUNC—覆盖原先文件内容,O_APPEND—追加内容,第三个文件是权限

返回值是文件描述符

image-20230124213916020

至于这32个比特怎么传递选项的呢?这里我用简单的方式呈现,通过宏定义一些参数,然后传递一些int参数即可知道要什么选项了,且标志位既有唯一性

image-20230124220436848

如果没有文件,底层的open函数是只读或只写的话,是不会创建文件的,需要传创建文件的标识符并且传入权限才能创建正常的文件

image-20230124222810680

如果不想要系统规定的文件初始权限—664,可以在子进程就传入想要的掩码来改变文件的权限

image-20230124223110923

image-20230124223332234

并且这里传的掩码是改的子进程的掩码,不会影响父进程

write—写入文件

第一个参数是文件,第二个参数是要写入的内容,第三个参数是有效内容的大小

image-20230124224726753

如果没有传O_TRUNC参数的话,在原先的内容上再写入会出问题!

image-20230124225534137

image-20230124230651737

read—读文件内容

第一个参数是要读的文件,第二个参数是读取到的文件类型和位置,第三个参数的内容大小,返回值是读取到的文件大小,单位是字节个数,如果函数失败返回-1

image-20230124230814272

image-20230126104357706

这里的读文件内容,无论是二进制文件、字符串、自定义类型都是类型void*,所以在读不同文件是需要自己添加细节。比如读取字符串需要在有效内容末尾添加"\0",若

image-20230124231554263

image-20230124231603399

感性+现象理解文件

文件操作即是进程和被打开文件的关系,进程可以打开多个文件,那么被打开的文件也要被操作系统管理起来。通过先描述再组织,操作系统为了管理对应的打开文件,必定要为文件创建对应的内核数据结构标识文件 struct file{ }->包含了文件的大部分属性

那是怎么管理的呢?

我先打开了5个文件,然后按顺序打印它们的文件描述符

image-20230124234710411

发现是从3开始,且是按照整数顺序打印,那么整数顺序的结构是什么呢?

image-20230124234746218

数组下标是也是按照整数顺序从0开始排列,有可能文件描述符的前三个分配给三个标准输入输出流

stdin->键盘,stdout->显示器,stderr->显示器

image-20230124235143280

image-20230124235159038

文件描述符fd

文件描述符本质是数组下标

上层通过传给进程文件描述符,进程的struct files_struct *files指针找到文件描述符表的文件描述符对应的下标数组,该数组的指针会访问到对应的文件,然后返回给上层!

image-20230125002826440

文件描述符的分配规则

这里我用系统调用文件函数创建一个文件,且把该文件描述符打印出来,可以看到该文件描述符是3即除去了012(stdin,stdout,stderr)三个标准输入输出流

image-20230126151158816

现在我把0(stdin)关闭或者把2(stderr)关闭,再运行,可以看到文件描述符会是0或1,可以看到两个标准输入流被关闭时,open打开文件时返回的文件描述符可以是0或2了,但是如果是1(stdout—标准输出流的话),会发现打印不出来

image-20230126151408927

image-20230126151531089

image-20230126151919129

当stdin被关闭后,新文件先被打开,然后在文件描述符表里从小到大按顺序寻找最小且没有被占用的fd此时0是没有被占用的,那么0处的指针就会指向新文件。

image-20230126152830376

但是当关闭stdout时,要打印的内容没有显示出来,而是被打印到了新文件里,且由于标准输入输出流的缓冲区机制和文件缓冲区机制不同,此时文件里并没有要打印到stdout的内容,要打印进到文件里需要强制刷新缓冲区。

image-20230126160406251

此时上层使用的fd是1不变,但是在内核中更改了fd对应的struct file*的地址,应该传入到相应的文件缺传入到了另一个文件上,这就是输出重定向

重定向

在操作系统中,有三种重定向,分别是输出重定向,追加重定向,输入重定向

>输出
>>追加
<输入

重定向函数dup2

int dup2(int oldfd,int newfd)

传入两个文件描述符,如果成功就返回newfd,失败就返回-1

image-20230126160559256

把oldfd里的内容(指针)拷贝到newfd里去,那么newfd( 指针)指向的内容就是oldfd指向的内容啦!

image-20230126161504521

输出重定向

把本来打印到stdout的内容打印到了soo.txt文件里

image-20230126162105541

image-20230126162212888

追加重定向

image-20230126163127169

可以看到多运行几次后文件的内容被追加了

image-20230126163159949

输入重定向

把本来键盘输入的东西打印到屏幕上,转变成把文件里的内容打印到屏幕上

image-20230126164144870

image-20230126164153637

image-20230126164200375

像重定向这样的工作是父进程向子进程提供信息,比如文件各类指针,父进程把文件描述符表拷贝一份给子进程,来进行操作。这些都在内核数据结构里,而进程替换是在用户空间,子进程对文件的操作不会影响到进程替换。当父进程和子进程都指向同一个文件,文件里会有一个引用计数,计数为2,父进程关闭该文件时,父进程的指针不指向该文件。该计数减减等于1,等到子进程也关闭文件(计数减减等于0),没有指针指向该文件时这个文件才会真正关闭。

image-20230126200226317

再次理解文件

各种外设都有自己的访问方式,但是在操作系统上都能统一成同一个struct file类型,上层通过操作系统的struct file的函数指针找到外设的驱动,在驱动里能找到对应的操作方法进行调用外设。而站在struct file上,一切的文件或者外设都是struct file,即在Linux上一切皆文件!

image-20230126203040708

理解缓冲区

同时调用库函数的打印和系统调用的调用,打印到屏幕上,和重定向到文件里内容都是和.c文件对应的

image-20230128110418648

但是在进程末尾fork创建子进程后,打印到屏幕上的内容是和之前一样,但是重定向到文件里却不一样了:先是一遍系统调用,然后是库函数打印两遍这是为啥呢?

image-20230128110515393

看到了现象,就去追根溯源

感性理解缓冲区

缓冲区是内存中的一部分

有些卷王过年不回家,回家不过年,哈哈哈开个玩笑~~~

那么在外地忙于生计的人儿没时间回家过年,与亲人两地相隔,母亲想寄一些吃的喝的给你,如果母亲只身负责把东西拿去给孩子,就会很占用母亲的时间且来回要好久,但是把东西给快递站,以打包快递方式发送给孩子,那么解放了母亲的时间,东西又很快的到孩子手上。

那么母亲就是进程,快递站就是缓冲区,你就是磁盘,缓冲区负责把进程的数据,传递到磁盘里!节省了进程进行数据io的时间!

image-20230128114048976

缓冲区刷新策略

1.立即刷新—>无缓冲 2.行刷新—>行缓存—比如显示器 3.缓冲区满—>全缓冲—比如磁盘文件

显示器是给人看的,一行一行打印到显示器给人看。等缓冲区满了,然后一次性全缓冲是效率最高的!

而总结成两者情况:一是用户强制刷新,比如fflush,二是进程退出—进行缓冲区刷新

缓冲区到底在哪呢?通过现象,库函数打印了两次,而系统系统调用打印了一次,说明缓冲区不在内核;fork之后父子数据发生写时拷贝,子进程也就有了同样的数据,即产生了两份数据。

综上,printf、fprintf、fputs自带缓冲区,且在用户级语言层,库函数在系统调用的上层,是对系统调用的封装,但是write没有,说明缓冲区是二次加上的,又因为是c,所以由c标准库提供。

用户级缓冲区存在FILE结构体中

库函数通过FILE*到内存找到对应的FILE结构体(用来存放文件的有关信息),结构体里就有缓冲区,文件描述符等等。找到后把相应内容写到里面的缓冲区,然后缓冲区里又封装了fd,在相应时候就会把缓冲区里的数据刷新到外设里!

最后得出现象的原因:有fork:外设是stdout时,在进行fork之前,三条库函数已经把数据按照行缓存的方式打印到屏幕上,此时到fork创建子进程时父进程缓冲区已经没有这部分数据了,子进程也就没有拷贝这部分数据;若是重定向到文件里:fork之前,库函数按照全缓存的方式打印到文件里。fork创建子进程的时候,缓冲区里还有数据,子进程也拷贝了一份数据,紧接着就是进程退出,父子进程退出时都要把进行缓冲区刷新,所以文件里库函数方式的就打印了两次。但是系统调用write只用了fd,没有用到FILE所以只打印了一次。

写一份缓冲区机制

mystdio.h

  1 #pragma once 
  2 
  3 #include<errno.h>
  4 #include<unistd.h>
  5 #include<sys/types.h>
  6 #include<sys/stat.h>
  7 #include<fcntl.h>
  8 #include<string.h>
  9 #include<stdlib.h>
 10 #include<assert.h>
 11 
 12 
 13 #define SIZE 1024    //大小
 14 #define SYNC_NOW  1  //无缓冲
 15 #define SYNC_LINE 2  //行缓存
 16 #define SYNC_FULL 4  //全缓存
 17 
 18 typedef struct _FILE{
 19 
 20 int flags;//刷新方式
 21 int fileno;//文件描述符
 22 char buffer[SIZE];//缓冲区
 23 int cap;//容量
 24 int size;//使用量
 25 
 26 }FILE_;
 27 
 28 FILE_ *fopen_(const char* path_name,const char *mode);//路径 权限
 29 void fwrite_(const void *ptr,int num,FILE_ *fp);//去向 大小 来源                                                     
 30 void fflush_(FILE_ *fp);
 31 void fclose_(FILE_ *fp);//来源

mystdio.c

 1 #include"mystdio.h"
  2                                                                                                                                                                                        
  3 FILE_ *fopen_(const char* path_name,const char *mode)//路径 权限
  4 {                                                               
  5   int flags=0;
  6   int Moded=0666;
  7   if(strcmp(mode,"r")==0)
  8   {                      
  9     flags|=O_RDONLY;
 10    }                
 11   else if(strcmp(mode,"w")==0)
 12   {                           
 13    flags|=(O_WRONLY|O_CREAT|O_TRUNC);
 14   }                                  
 15   else if(strcmp(mode,"a")==0)
 16   {                           
 17     flags|=(O_WRONLY|O_CREAT|O_APPEND);
 18   }else                                
 19   {     
 20     //TODO
 21   }       
 22   int fd=0;
 23   if(flags&O_RDONLY) fd=open(path_name,flags);
 24   else fd=open(path_name,flags,Moded);        
 25   if(fd<0)                            
 26   {       
 27     const char* err=strerror(errno);
 28     write(2,err,strlen(err));       
 29    return NULL;              
 30   }            
 31    
 32   FILE_ *fp=(FILE_*)malloc(sizeof(FILE_));
 33   assert(fp);// 失败就断言                
 34 //成功就初始化            
 35 fp->flags=SYNC_LINE;//默认行缓冲
 36 fp->fileno=fd;//文件描述符      
 37 fp->cap=SIZE;//容量       
 38 fp->size=0;//使用量
 39 memset(fp->buffer,0,SIZE);//缓冲区初始化为0
 40 return fp;//成功就返回FILE*指针            
 41 }                              
 42 
 43                                            
 44 void fwrite_(const void *ptr,int num,FILE_ *fp)//去向 大小 来源
 45 {                                                                                                                                                                                      
 46    //1.写到缓冲区里
 47    memcpy(fp->buffer+fp->size,ptr,num);//des,sor,size 
 48    fp->size+=num;
 49    //2.判断是否刷新了
 50    if(fp->flags&SYNC_NOW)
 51    {
 52      //写到文件里
 53      write(fp->fileno,fp->buffer,fp->size);
 54      fp->size=0;
 55    }
 56    else if(fp->flags&SYNC_FULL)
 57    {
 58     if(fp->size==fp->cap)
 59      {
 60      write(fp->fileno,fp->buffer,fp->size);
 61      fp->size=0;
 62      }
 63   } 
 64    else if(fp->flags& SYNC_LINE)
 65    {
 66      if(fp->buffer[fp->size-1]=='\n')//不考虑abcd\nabab
 67      {
 68      write(fp->fileno,fp->buffer,fp->size);
 69      fp->size=0;
 70      }
 71    }
 72    else 
 73    {
 74      //FAIL
 75    }
 76 
 77 }
 78 void fflush_(FILE_* fp)
 79 {
 80   if(fp->size>0) write(fp->fileno,fp->buffer,fp->size);
 81 }
 82 void fclose_(FILE_ *fp)//来源
 83 {
 84   fflush_(fp);
 85   close(fp->fileno);
 86 }

试一下行缓冲

image-20230128184759931

image-20230128185936782

命令行清空文件: 【空格】 > 文件名

全缓冲

image-20230128190143307

image-20230128190338375

无缓冲

image-20230128191323069

image-20230128192242390

内核缓冲区

按照上面的策略:把数据写入磁盘:通过库函数printf或者系统调用write写入缓冲区,然后按照指定方式(无缓冲、行缓冲、全缓冲)刷新缓冲区,把数据拷贝到文件里。

但实际上不是这样。**首先通过语言层面的文件函数(fwrite等)把数据拷贝到语言层缓冲区,再通过指定缓冲方式(无、行、全)通过系统调用(write)把数据拷贝到内核中的struct file中的内核层面缓冲区,然后以操作系统按照自己的方式(可能内存不够了刷新,也可能按照一定频率刷新)把数据拷贝到磁盘中。**一共三次拷贝。

image-20230128213614225

所以语言层文件函数fwrite等和系统调用函数write等写入函数应该叫拷贝函数。

fsync 写入函数

fsync是写入函数,数据在内核缓冲区时,调用该函数不再按照操作系统自己的方式来刷新缓冲区,而是将操作系统缓冲区的数据强行同步到文件中。

image-20230128214731595

同时也可能给我们写的小程序更改一下。

image-20230128215207244

 78 void fflush_(FILE_* fp)
 79 {
 80  // if(fp->size>0) write(fp->fileno,fp->buffer,fp->size);
 fsync(fp->fileno);//强制刷新内核缓冲区
 fp->size=0;
 81 }

所以语言层文件函数fwrite等和系统调用函数write等写入函数应该叫拷贝函数。

fsync 写入函数

fsync是写入函数,数据在内核缓冲区时,调用该函数不再按照操作系统自己的方式来刷新缓冲区,而是将操作系统缓冲区的数据强行同步到文件中。

 78 void fflush_(FILE_* fp)
 79 {
 80  // if(fp->size>0) write(fp->fileno,fp->buffer,fp->size);
 fsync(fp->fileno);//强制刷新内核缓冲区
 fp->size=0;
 81 }

同时也可能给我们写的小程序更改一下。

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

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

相关文章

新的一年,建议尝试下这7个JavaScript 库

常言道“你不必重新发明轮子”。第三方库它可以帮助您以简单的方式编写复杂且耗时的功能&#xff0c;一个好的项目应当使用一些优秀的库&#xff0c;下面我推荐下&#xff0c;在你的下个项目中&#xff0c;建议用上这7 个有用的库。1、Video.jsVideo.js 是一个基于 HTML5 的视频…

跑步用挂脖耳机好还是无线耳机、公认最好的跑步耳机推荐

蓝牙耳机近几年受到市场的欢迎&#xff0c;种类越来越多&#xff0c;各类功能也日益五花八门&#xff0c;消费者很难准确的进行分辨&#xff0c;一不小心可能买到华而不实的产品。现在了解一下值得入手的蓝牙耳机&#xff0c;从多个角度对蓝牙耳机进行评估后&#xff0c;得出以…

互联网导航系统——DNS:《流浪地球2》中重启互联网的现实解读

《流浪地球2》展现了一个浩大的宇宙级工程&#xff1a;宏大壮观的万座行星发动机、拥有超强算力的量子计算机、连接天地的太空电梯……这些“硬科技”让观众大开眼界。 电影中刘德华饰演的图恒宇能否重启互联网根服务器是拯救地球任务的关键。互联网可以重启吗&#xff1f;现实…

Array.prototype.sort()排序,升降排序使用方法

sort() 方法对数组中的元素进行适当排序并返回数组。这种情况不一定稳定。默认排序顺序根据字符串 Unicode 代码点。 目录 升序降序排序法 对象可以按照某个属性排序 const months [March, Jan, Feb, Dec] months.sort() // [Dec, Feb, Jan, March] console.log(months) // …

聊聊GC是如何快速枚举根节点的

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 世界上最快乐的事&#xff0c;莫过于为理想而奋斗。——苏格拉底 文章目录什么是根节点枚举根节点枚举存在的问题如何解决根节点枚举的问题安全点安全区域HotSpot使用的是可达性分析算法&#xff0c;该算法需…

ssm高校大学校园租赁平台的设计与实现java

当今社会&#xff0c;信息技术发展快速。同时&#xff0c;随着生活水平提高&#xff0c;学生有了更大的购买力&#xff0c;这就使得闲置物品增多&#xff0c;校园里物品更新快&#xff0c;使用周期短。而且传统的校园租赁平台&#xff0c;已经不能够满足学生的需求。学院校园租…

人工智能识别图片食物

一、准备食物图片&#xff08;橘子和苹果&#xff09;二、识别学习关键代码编写public static void study() throws Exception {//学习Picture picture new Picture();//图片解析类 图片&#xff08;文件&#xff09;-三通道矩阵Config config new Config();//现有的环境业务…

一文了解WebSocket及Springboot集成WebSocket

文章目录WebSocket是什么WebSocket通信原理和机制WebSocket协议是什么WebSocket协议和Http协议有什么区别WebSocket常用在那些场景Springboot集成WebSocketpom依赖java相关代码configcomponenthtml代码页面访问效果WebSocket是什么 &#x1f34a;WebSocket是一种网络通信协议&…

Linux C编程

编写C代码 编辑器&#xff1a;vim&#xff0c;编写.c文件 编译 gcc 源文件 -o 生成可执行文件名 gcc -c&#xff1a;只编译&#xff0c;不链接&#xff0c;生成.o文件 make工具和Makefile文件 make工具&#xff1a;GNU make&#xff0c;是一个文件&#xff0c;用于将源代…

【CANoe示例分析】EthernetCanGW_Test_CN

1、工程路径 此示例工程来自于Vector官网:EthernetCanGW_Test_CN 感兴趣的可以自行下载! 2、示例目的 如何在CANoe中创建一个网关,实现转发以太网报文到多个CAN网络中。该使用案例是对CAN网络进行压力测试 3、示例内容 本示例通过执行Test Module里的测试用例Bus_load…

《电路/电路原理》—戴维宁(南)定理实战演练

前言战前准备什么是戴维南定理&#xff1f;戴维南定理&#xff08;Thevenins theorem&#xff09;标准解释&#xff1a;含独立电源的线性电阻单口网络N&#xff0c;就端口特性而言&#xff0c;可以等效为一个电压源和电阻串联的单口网络。电压源的电压等于单口网络在负载开路时…

CSS预处理器、移动端适配

1、预处理器概念 1.1、CSS编写的痛点 CSS作为一种样式语言, 本身用来给HTML元素添加样式是没有问题的。 但是目前前端项目已经越来越复杂, 不再是简简单单的几行CSS就可以搞定的, 我们需要几千行甚至上万行的CSS来完成页面的美化工作。 随着代码量的增加, 必然会造成很多的…

LeetCode-26. 删除有序数组中的重复项

目录题目分析双指针理解代码实现题目来源 26. 删除有序数组中的重复项 题目分析 解法&#xff1a; 双指针 首先注意数组是有序的&#xff0c;那么重复的元素一定会相邻。 要求删除重复元素&#xff0c;实际上就是将不重复的元素移到数组的左侧。 考虑用 2 个指针&#xff0c;…

拉伯证券|7900亿芯片巨头狂跌,发生了什么?

全球芯片巨子忽然爆雷。 英特尔刚刚交出了一份“十分糟糕”的财报。美东时间1月26日美股盘后&#xff0c;英特尔公布的2022第四季度及全年财报显现&#xff0c;第四季度的营收为140亿美元&#xff0c;同比大幅下降32%&#xff0c;不及商场预期&#xff1b;第四季度净亏损7亿美元…

【项目精选】基于SpringBoot和Vue开发的功能强大的图书馆系统(附源码)

功能介绍 图书馆系统功能包括&#xff1a; 1、读者端&#xff1a; 1.智能推荐图书 2.读者在线预约座位 3.读者借阅归还图书 4.图书详情 5.图书评论、评星 6.用户登录、注册、修改个人信息 7.用户自定义图书标签 8.用户报名活动参加活动 9.书架展示和添加删除 10.用户邮件登录…

Win11的两个实用技巧系列之u盘怎么设置密码?

Win11系统u盘怎么设置密码?Win11设置u盘密码的方法win11U盘怎么设置密码&#xff1f;今天小编就为大家带来了Win11设置u盘密码的方法&#xff0c;需要的朋友一起看看吧现在还是有很多用户都在使用U盘来存储一些重要的文件和数据&#xff0c;而为了更好的保护里面的安全&#x…

mongodb的索引操作

Mongodb的索引操作 学习目标 掌握 mongodb索引的创建&#xff0c;删除操作掌握 mongodb查看索引的方法掌握 mongodb创建唯一索引的方法 1. 为什么mongdb需要创建索引 加快查询速度进行数据的去重 2. mongodb创建简单的索引方法 语法&#xff1a;db.集合名.ensureIndex({属…

【手写 Promise 源码】第八篇 - 完善 Promise 并通过 promise-aplus-tests 测试

一&#xff0c;前言 上一篇&#xff0c;实现 Promise 对返回值 x 各种情况的分析和处理&#xff0c;主要涉及以下几个点&#xff1a; 回顾了相关的 Promise A 规范内容&#xff1b;根据 Promise A 规范描述和要求&#xff0c;实现了核心解析方法&#xff1a;resolvePromise&a…

BUUCTF-Reverse Writeup【持续更新】

本文示例程序可见 BUUCTF 官网或者 github easyre | 入门级 方法一&#xff1a;WinHex 打开 easyre.exe&#xff0c;浏览一下字符串&#xff0c;发现有flag方法二&#xff1a;IDA Pro 打开 easyre.exe&#xff0c;能直接看到flag&#xff0c;或者 F5 反汇编看到逻辑是输入两个…

怎么把多个JPG合并成一个PDF?还不快来学

我们通常在处理工作文件时会有很多JPG图片需要传输&#xff0c;不过JPG图片数量一般都非常多&#xff0c;我们需要一张一张的进行传输&#xff0c;不仅会浪费很多时间&#xff0c;还很不方便查看&#xff0c;所以我们就可以及将JPG图片合并到一个PDF文件中&#xff0c;这样就可…