【Linux】基础IO——文件描述符,重定向,FILE

话接上篇: 

 1.文件描述符fd

磁盘文件 VS 内存文件?

        当文件存储在磁盘当中时,我们将其称之为磁盘文件而当磁盘文件被加载到内存当中后,我们将加载到内存当中的文件称之为内存文件。磁盘文件和内存文件之间的关系就像程序和进程的关系一样,当程序运行起来后便成了进程,而当磁盘文件加载到内存后便成了内存文件。

        进程想要访问文件必须先打开文件,一个进程可以打开多个文件,而系统当中又存在大量进程,也就是说,在系统中任何时刻都可能存在大量已经打开的文件,已经打开的文件会被加载到了内存中,这些文件也叫内存文件,反之,没有打开的文件就叫做磁盘文件。那么操作系统就要管理这些打开的文件

        如何管理就是先描述,再组织操作系统为每个已经打开的文件创建各自的struct file结构体,然后将这些结构体以双链表的形式连接起来,那么操作系统对文件的管理也就变成了对这张双链表的增删改查等操作,在每个节点中不仅有链表的指针,还应该存在着文件的内容+属性,这些信息大部分在磁盘中就保留在文件内部了,加载的时候就从磁盘中把数据加载到内存。

        而为了区分已经打开的文件哪些属于特定的某一个进程,我们就还需要建立进程和文件之间的对应关系。

进程和文件之间的对应关系是如何建立的?

当进程运行的时候,操作系统会将该程序的代码和数据加载到内存,然后创建对应的task_struct, mm_struct, 页表等…

        task_struct 里面有一个指针,指向files_struct结构体,结构体里面有名为fd_array的指针数组,该数组的下标就是文件描述符fd。

使用read和write的时候要传入文件描述符,通过文件描述符找到这个数组中的指针,进而对文件访问。

        当进程打开log.txt文件时,我们需要先将该文件从磁盘当中加载到内存,形成对应的struct file,将该struct file连入文件双链表,并将该结构体的首地址填入到fd_array数组当中下标为3的位置,使得fd_array数组中下标为3的指针指向该struct file,最后返回该文件的文件描述符给调用进程即可。

因此,我们只要有某一文件的文件描述符,就可以找到与该文件相关的文件信息,进而对文件进行一系列输入输出操作。

注意: 向文件写入数据时,是先将数据写入到对应文件的缓冲区当中,然后定期将缓冲区数据刷新到磁盘当中。

1.1.文件描述符的分配规则

我们之前连续打开了6个文件,我们发现文件描述符是从3开始的,并且是连续地址的。那真的是一直从3开始吗?下面我们看一段代码:

  #include<stdio.h>  #include<string.h>                                                                                                                              #include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>int main(){              close(0);int fd1=open("./log1.txt",O_WRONLY|O_CREAT,0644);int fd2=open("./log2.txt",O_WRONLY|O_CREAT,0644);int fd3=open("./log3.txt",O_WRONLY|O_CREAT,0644);int fd4=open("./log4.txt",O_WRONLY|O_CREAT,0644);printf("%d\n",fd1);printf("%d\n",fd2);printf("%d\n",fd3);printf("%d\n",fd4);close(fd1);close(fd2);close(fd3);close(fd4);return 0;}

 

我们发现怎么fd从0开始了,而之后的又是从3开始了。现在我们在将2也关了,我们再来看结果会是如何。

  #include<stdio.h>  #include<string.h>                                                                                                                              #include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>int main(){              close(0);close(2);int fd1=open("./log1.txt",O_WRONLY|O_CREAT,0644);int fd2=open("./log2.txt",O_WRONLY|O_CREAT,0644);int fd3=open("./log3.txt",O_WRONLY|O_CREAT,0644);int fd4=open("./log4.txt",O_WRONLY|O_CREAT,0644);printf("%d\n",fd1);printf("%d\n",fd2);printf("%d\n",fd3);printf("%d\n",fd4);close(fd1);close(fd2);close(fd3);close(fd4);return 0;}

 我们发现0和2也被用起来了。现在我们就明白了文件描述符的分配规则是从最小的未被使用的下标开始的

事实上

Linux下进程默认会打开三个文件描述符,0:标准输入、1:标准输出、2:标准错误。

        0,1,2对应的物理设备一般是:键盘、显示器、显示器。

我们之前验证了文件描述符默认是从3开始的,也就是说0,1,2是默认被打开的。

  • 0代表的是标准输入流,对应硬件设备为键盘;
  • 1代表标准输出流,对应硬件设备是显示器;
  • 2代表标准错误流,对应硬件设备为显示器。

当一个进程被创建时,OS就会根据键盘、显示器、显示器形成各自的struct file,将这3个struct file链接到文件的双链表当中,并将这3个struct file的地址分别填入fd_array数组下标为0、1、2的位置,至此就默认打开了标准输入流、标准输出流和标准错误流。

        文件描述符的分配规则:分配最小的,没有被占用的。如果我把0号关闭,那么为新文件分配的时候就从最小的0分配。

2.重定向

2.1.输出重定向

1.输入重定项。

我们之前学习过的输出重定向就是,将我们本应该输出到显示器上的数据重定向输出到另一个文件中。那他的原理是什么了?

例如: 如果我们想让本应该输出到“显示器文件”的数据输出到log.txt文件当中,那么我们可以在打开log.txt文件之前将文件描述符为1的文件关闭,也就是将“显示器文件”关闭,这样一来,当我们后续打开log.txt文件时所分配到的文件描述符就是1。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{close(1);// 打开文件int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open");exit(1);}// 打开成功printf("fd: %d\n", fd);printf("fd: %d\n", fd);printf("fd: %d\n", fd);printf("fd: %d\n", fd);fprintf(stdout, "hello fprintf\n");const char* s = "hello fwrite\n";fwrite(s, strlen(s), 1, stdout);fflush(stdout);// 关闭文件close(fd);   return 0;
}

通过上面的现象也可以看出,打印的数据没有到显示器上,而是到了磁盘的文件中,这是为什么呢?

        上面就说过,0、1、2默认是被打开的,对应的就要打开显示器,所以stdout的文件描述符就是1,所以C语言的接口fprintf认识的就是stdout或者说就是1,我们一开始就关闭了1号文件描述符,把数组下标为1的位置设置为NULL,然后打开了log.txt文件,此时1没有被占用,所以就把下标为1的位置填入log.txt的结构体的地址log.txt的文件描述符就是1了,但是上层的C语言函数认识的还是1,他们还是继续往1中写入,这样就不能打印到屏幕而是重定向到了文件中。

 

重定向的本质是在操作系统中更改fd对应的内容,上面演示的这就就叫做输出重定向。

2.2.输入重定向       

输入重定向就是,将我们本应该从一个键盘上读取数据,现在重定向为从另一个文件读取数据。

 

 比如说我们的fget函数是从标准输入读取数据,现在我们让它从log1.txt当中读取数据,我们在scanf读取数据之前close(0).这样键盘文件就被关闭,这样一样log1.txt的文件描述符就是0. 

int main()
{close(0);// 打开文件int fd = open("log.txt", O_RDONLY);if (fd < 0){perror("open");exit(1);}printf("fd: %d\n", fd);char buffer[64];fgets(buffer, sizeof(buffer), stdin);printf("%s\n", buffer);// 关闭文件close(fd);return 0;
}

关闭了0号文件描述符,所以打卡的新文件的文件描述符就变成了0,然后读取了文件中的第一行数据。 

2.3.追加重定向

还有一种就是追加重定向,更改一下选项就行了。

int main()
{close(1);// 打开文件int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT);if (fd < 0){perror("open");exit(1);}printf("%d\n", fd);fprintf(stdout, "append success\n");fflush(stdout);// 关闭文件close(fd);return 0;
}

【注意】:“>”输出重定向修改的只是1号也就是stdout标准输出,所以尽管程序中有两行代码,一行向1号文件描述符中打印,另一行向2号文件描述符中打印,那么使用输出重定向只会使1号文件描述符重定向,2号还是打印到显示器上。

2.4.dup2

我们发现我们上面只能通过close关闭对应的文件描述符实习对应的输出重定向和输出重定向,那我们能不能不关闭呢?

要完成重定向我们只需对fd_array数组当中元素进行拷贝即可。

例如,我们若是将fd_array[3]当中的内容拷贝到fd_array[1]当中,因为C语言当中的stdout就是向文件描述符为1文件输出数据,那么此时我们就将输出重定向到了文件log.txt。而在linux当中就给我们提供了这个系统调用:

  • 函数功能: dup2会将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中。
  • 函数返回值:调用成功返回0,失败返回-1

使用的过程中需要注意:

  1. 如果oldfd不是有效的文件描述符,则dup2调用失败,并且此时文件描述符为newfd的文件没有被关闭。
  2. 如果oldfd是一个有效的文件描述符,但是newfd和oldfd具有相同的值,则dup2不做任何操作,并返回newfd。

        只需要把想要重定向的文件在数组中拷贝过去,比如我想要输出重定向,重定向到某个文件,那么1就代表标准输出,所以就要改变1的指向,就把3的地址拷贝过去,这样1就指向了重定向的文件。 

输入重定向也是一样的,0是标准输入,就要从其他文件输入,就把其他文件的地址拷贝到0的位置。 

下面通过dup2演示一下前面的输出重定向:

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<unistd.h>5 #include<fcntl.h>                                                                                                                               6 int main()7 {8   int fd=open("./log.txt",O_WRONLY|O_CREAT,0644);9    dup2(fd,1);10  printf("hello world\n");11  printf("hello world\n");12 13 }

2.5.重定向的本质 

重定向的本质,其实是在OS内部,更改fd对应的内容的指向!!

3.FILE

        FILE是C语言定义的文件结构体,里面包含了各种文件信息。可以肯定的一点是,FILE结构体内一定封装了 fd 。为什么?来看接下来的思路分析:

1.使用系统接口的必然性
         文件存储在磁盘上,属于外设。谁有权限访问外设呢?只有操作系统。因为操作系统对上要提供稳定的服务,对下要管理好各种软硬件资源。
         如果文件操作能绕开操作系统,那么操作系统怎么知道某个文件到底有没有被创建,有没有被销毁呢,还怎么给你提供稳定的服务呢?基于上述简单的认识,我们不难理解,要想访问硬件资源,就必须通过操作系统。
         而操作系统出于安全性和减少使用成本的角度考虑,是不相信任何人的。就像银行一样,不会将金库直接向大众开放,而是只会有几个业务窗口为大家提供服务。操作系统也是这样,操作系统提供的窗口就是系统接口。
         至此通过我们的逻辑推演,我们已经可以得出以下的结论:要想访问外设就必须使用操作系统提供的系统接口。所以C语言的各种文件操作函数本质就是对系统接口的封装

2.FILE结构体封装fd的必然性
 C语言的文件操作都是系统统接口的封装,而系统接口的使用只认fd,因此FILE结构体中必然会封装fd

验证的方法也很简单直接:

FILE究竟是个什么东西呢?是一个c语言提供的结构体类型

我们在/usr/include/stdio.h头文件中可以看到下面这句代码,也就是说FILE实际上就是struct _IO_FILE结构体的一个别名。

typedef struct _IO_FILE FILE;

 而我们在/usr/include/libio.h头文件中可以找到struct _IO_FILE结构体的定义,在该结构体的众多成员当中,我们可以看到一个名为_fileno的成员,这个成员实际上就是封装的文件描述符。

struct _IO_FILE {int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags//缓冲区相关/* The following pointers correspond to the C++ streambuf protocol. *//* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */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; //封装的文件描述符
#if 0int _blksize;
#elseint _flags2;
#endif_IO_off_t _old_offset; /* This used to be _offset but it's too small.  */#define __HAVE_COLUMN /* temporary *//* 1+column number of pbase(); 0 is unknown. */unsigned short _cur_column;signed char _vtable_offset;char _shortbuf[1];/*  char* _save_gptr;  char* _save_egptr; */_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

FILE当中的缓冲区

我们来看看下面这段代码,代码当中分别用了两个C库函数和一个系统接口向显示器输出内容,在代码最后还调用了fork函数。

#include <stdio.h>
#include <unistd.h>
int main()
{//cprintf("hello printf\n");fputs("hello fputs\n", stdout);//systemwrite(1, "hello write\n", 12);fork();return 0;
}

运行该程序,我们可以看到printf、fputs和write函数都成功将对应内容输出到了显示器上

但是,当我们将程序的结果重定向到log.txt文件当中后,我们发现文件当中的内容与我们直接打印输出到显示器的内容是不一样的。

同样一个程序,为什么C库函数printf和fputs打印的内容重定向到文件后就变成了两份,而系统接口write打印的内容还是原来的一份呢?

我们先来了解一下缓冲区

首先我们应该知道的是,缓冲的方式有以下三种:

  1. 无缓冲。
  2. 行缓冲。(常见的对显示器进行刷新数据)
  3. 全缓冲。(常见的对磁盘文件写入数据)

        当我们直接执行可执行程序,将数据打印到显示器时所采用的就是行缓冲,因为代码当中每句话后面都有\n,所以当我们执行完对应代码后就立即将数据刷新到了显示器上。

        而当我们将运行结果重定向到log.txt文件时,数据的刷新策略就变为了全缓冲,此时我们使用printf和fputs函数打印的数据都打印到了C语言自带的缓冲区当中,之后当我们使用fork函数创建子进程时,由于进程间具有独立性,而之后当父进程或是子进程对要刷新缓冲区内容时,本质就是对父子进程共享的数据进行了修改,此时就需要对数据进行写时拷贝,至此缓冲区当中的数据就变成了两份,一份父进程的,一份子进程的,所以重定向到log.txt文件当中printf和puts函数打印的数据就有两份。

        但由于write函数是系统接口,我们可以将write函数看作是没有缓冲区的,因此write函数打印的数据就只打印了一份。

这个缓冲区是谁提供的?

         printf fwrite 是库函数,write是系统调用,库函数在系统调用的“上层”, 是对系统 调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为 是C,所以是C标准库提供的

        换句话说如果说这个缓冲区是操作系统提供的,那么printf、fputs和write函数打印的数据重定向到文件后都应该打印两次。

这个缓冲区在哪里?

我们常说printf是将数据打印到stdout里面,而stdout就是一个FILE*的指针,在FILE结构体当中还有一大部分成员是用于记录缓冲区相关的信息的。

//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
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. */

也就是说,这里的缓冲区是由C语言提供,在FILE结构体当中进行维护的,FILE结构体当中不仅保存了对应文件的文件描述符还保存了用户缓冲区的相关信息。

操作系统有缓冲区吗?

        操作系统实际上也是有缓冲区的,当我们刷新用户缓冲区的数据时,并不是直接将用户缓冲区的数据刷新到磁盘或是显示器上,而是先将数据刷新到操作系统缓冲区,然后再由操作系统将数据刷新到磁盘或是显示器上。(操作系统有自己的刷新机制,我们不必关系操作系统缓冲区的刷新规则)

 进程替换时,是否会干扰重定向对应的数据结构?

它们当然不会互相影响。换而言之,将来 fork 创建子进程,子进程会以父进程的大部分数据为模板,子进程进行程序替换时并不会影响曾经打开的文件,也就不会影响重定向对应的数据结构。 

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

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

相关文章

JVM 三色标记算法

三色标记算法核心原理 三色标记算法是一种JVM的垃圾标记算法&#xff0c;CMS/G1垃圾回收器就是使用的这种算法&#xff0c;它可以让JVM在不发生或者尽可能短的发生STW&#xff08;Stop The World&#xff09;的情况下进行垃圾的标记和清除。 顾名思义&#xff0c;三色标记算法…

实现JWT认证与授权的Spring Boot项目详解

我们将详细介绍如何使用JWT&#xff08;JSON Web Tokens&#xff09;结合Spring Boot框架实现用户认证和授权系统。此方案将包括用户注册、登录以及通过JWT令牌进行后续请求的身份验证过程。我们将从引入必要的依赖开始&#xff0c;然后逐步构建项目的各个部分&#xff0c;包括…

精品丨PowerBI迁移到SSAS

业务场景&#xff1a; 企业初期在进行 BI 可视化路线的时候&#xff0c;往往不会选择方案较为完整的SSAS&#xff0c;而是会选择轻量的 PowerBI 方案&#xff0c;究其根本还是软件成本的问题。 但是随着模型越来越臃肿&#xff0c;维护成本越来越高&#xff0c;有很多模型需要进…

Java面向对象-抽象类和抽象方法

Java面向对象-抽象类和抽象方法 1、代码案例展示2、抽象类和抽象方法的关系&#xff1a; 1、代码案例展示 1、在一个类中会有一类方法&#xff0c;无需重写&#xff0c;直接使用 2、在一个类中会有一类方法&#xff0c;会对这个方法进行重写 3、一个方法的方法体去掉&#xff…

【文心智能体分享】日记周报助手

引言 在繁忙的实习生活中&#xff0c;你是否曾为如何整理日常的工作日志、周报、月报而烦恼&#xff1f;现在&#xff0c;我们为你带来了一个全新的智能体——“日记周报助手”&#xff0c;它将成为你实习过程中的得力助手&#xff0c;帮你轻松整理实习日志&#xff0c;让你的…

mysql 中的锁

一.锁的介绍 锁是计算机协调多个进程或线程并发访问某一资源的机制&#xff0c;在数据库中&#xff0c;除了传统的计算资源&#xff08;cpu&#xff0c;ram&#xff0c;i/o&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性…

初见 Rollup 的十大常见问题

文章目录 初见 Rollup 的十大常见问题1. 超神奇的 Rollup 英文解释&#xff01;2. 为什么 ESM 要比 CommonJS 要好呢&#xff1f;3. 什么是 tree-shaking ?4. 如何使用 Rollup 处理 CommonJS&#xff1f;5. 为什么 node-resolve 不是一个内置功能&#xff1f;6. 为什么在进行代…

如何警用root用户登录ssh

使用tail指令&#xff0c;可以动态查看日志信息。 &#xff08;tail -f /var/log/secure或messages&#xff09; 使用>符号&#xff0c;可以清空日志内容&#xff0c;不删除文件本身。 禁用root用户为以下步骤&#xff1a; 首先使用useradd创建用户&#xff08;可以修改为其…

STM32HAL-最简单的时间片论法

目录 概述 一、开发环境 二、STM32CubeMx配置 三、编码 四、运行结果 五、总结 概述 本文章使用最简单的写法时间片论法框架,非常适合移植各类型单片机,特别是资源少的芯片上。接下来将在stm32单片机上实现,只需占用1个定时器作为tick即可。(按键框架+时间片论法)…

【数据结构之B树的讲解】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

【乐吾乐2D可视化组态编辑器】开关、阀门、报警状态切换

开关状态 开关的断开与闭合&#xff1a;将电力组件的“开”与“关”2个组件重叠在一起&#xff0c;右键选择“组合为状态”&#xff0c;属性面板中就可以任意切换状态。 视频教程&#xff1a;开关阀门多状态控制 乐吾乐2D可视化组态编辑器地址&#xff1a;https://2d.le5le.co…

【python】python指南(三):使用正则表达式re提取文本中的http链接

一、引言 对于算法工程师来说&#xff0c;语言从来都不是关键&#xff0c;关键是快速学习以及解决问题的能力。大学的时候参加ACM/ICPC一直使用的是C语言&#xff0c;实习的时候做一个算法策略后台用的是php&#xff0c;毕业后做策略算法开发&#xff0c;因为要用spark&#x…

js编程环境配置-vscode

1、安装Node.js 官网下载 选择适合你Windows系统架构&#xff08;32位或64位&#xff09;的安装包。windows系统选择“Windows Installer (.msi)”或“Windows Binary (.exe)”进行下载。 双击下载的.msi或.exe文件进行安装。 在cmd中输入node --version和npm --version&…

2-4 基于matlab的洛伦兹系统分岔图实现

基于matlab的洛伦兹系统分岔图实现。通过2种方法&#xff0c;最大值法&#xff0c;庞加莱截面法进行输出分岔图。可直接运行。 2-4 洛伦兹系统分岔图 最大值法 - 小红书 (xiaohongshu.com)

如何平衡安全访问和办公效率?零信任安全×统一身份才是解决之道

在远程办公、混合办公、跨团队协作日益频繁的今天&#xff0c;企业的业务开展需要支持多种访问接入的需求和场景。如何平衡企业数据的安全访问和办公效率将成为挑战。 在业务的多种接入场景上&#xff0c;企业引入零信任&#xff08;Zero Trust&#xff0c;ZT&#xff09;产品…

ESP-IDF OTA升级过程中遇到的“esp_transport_read returned:-1 and errno:128”问题(4)

接前一篇文章:ESP-IDF OTA升级过程中遇到的“esp_transport_read returned:-1 and errno:128”问题(3) 上一回讲到,笔者准备第二天围绕信号强度展开进一步测试。实际上没等到第二天,笔者在当天下午下班时间(18点)以后就进行了相关测试(不过测试倒并不是完全针对于信号强…

手机是如何实现多个应用程序同时运行的?

想要理解这个问题&#xff0c;我们要先了解一下操作系统以及进程相关的知识&#xff1a; 操作系统的功能有很多&#xff0c; 例如&#xff1a; 进程管理&#xff08;Process Management&#xff09;&#xff1a; 功能&#xff1a;创建和终止进程&#xff0c;进程调度&#xf…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 连续区间和(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 📎在线评测链接 连续区间和(100分) 🌍 评测功能需要订阅专栏后私信联系清隆…

海豚调度异常处理: 使用 arthas 在内存中删除启动失败的工作流

&#x1f4a1; 本系列文章是 DolphinScheduler 由浅入深的教程&#xff0c;涵盖搭建、二开迭代、核心原理解读、运维和管理等一系列内容。适用于想对 DolphinScheduler了解或想要加深理解的读者。祝开卷有益。大数据学习指南 大家好&#xff0c;我是小陶&#xff0c;DolphinSch…

明天二战六级

明天二战六级&#xff0c;各位程序员们&#xff0c;加油