Linux文件重定向文件缓冲区
目录
一、C文件接口
二、系统文件I/O
2.1认识系统文件I/O
2.2系统文件I/O
2.3系统调用和库函数
2.4open( )的返回值--文件描述符
2.5访问文件的本质
三、文件重定向
3.1认识文件重定向
3.2文件重定向的本质
3.3在shell中添加重定向功能
3.4stdout和stderr
3.5如何理解“linux下一切皆文件” --以对外设的IO操作为例
四、文件缓冲区
4.1认识FILE
4.2文件缓冲区引入
4.3文件缓冲区的原理
4.4解释现象
4.5总结
一、C文件接口
- stdin & stdout & stderr
- C默认会打开三个输入输出流,分别是stdin, stdout, stderr
- 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针
- fwrite向指定文件写入内容
- fread从指定文件读取内容
- fprintf根据指定的format(格式)发送信息(参数)到由stream(流)指定的文件,fprintf可以使得信息写入到指定的文件
- 调用C文件接口,以w的形式打开,若文件不存在,会在当前目录下新建文件,当前路径就是进程的当前路径cwd,如果改变了进程的cwd就可以在其他目录下新建文件
- w写入前都会对文件进行清空,a在文件结尾追加写,两者都是写入
- C默认打开的三个输入输出流不是C语言的特性,而是操作系统的特性,进程会默认打开键盘,显示器,显示器
二、系统文件I/O
2.1认识系统文件I/O
-
文件其实是在磁盘上的,磁盘是外设,对文件进行访问,就是对硬件进行访问
-
任何用户都不能直接访问硬件的数据 ,而必须通过系统调用
-
几乎所有的库只要是访问硬件设备,必须封装系统调用
-
C文件接口就是一种库函数,是对系统调用的封装
2.2系统文件I/O
-
open( )
-
#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: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行 “ 或 ” 运算,构成 flags参数 :O_RDONLY: 只读打开O_WRONLY: 只写打开O_RDWR : 读写打开O_CREAT : 若文件不存在,则创建它,需要使用 mode(例0666) 选项,来指明新文件的访问权限O_APPEND: 追加写O_TRUNC: 每一次写入都清空文件返回值:成功:新打开的文件描述符失败:-1
代码示例:
umask( )可以用来设置掩码的值
- 比特方位式的标志位传递方式
- 通过位运算来实现
2.3系统调用和库函数
- 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)
- open close read write lseek 都属于系统提供的接口,称之为系统调用接口
- 可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。
2.4open( )的返回值--文件描述符
- Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2
- 0,1,2对应的物理设备一般是:键盘,显示器,显示器
- linux下文件描述符的分配规则:从0下标开始,寻找最小没有被使用过的数组位置,它的下标就是新文件的文件描述符--结合访问文件的本质来说明
代码示例:
- 因为C库函数是对系统接口的封装,系统接口下只认识文件描述符,所以C库自己提供的FILE结构体中必定也包含着文件描述符,用_fileno记录
如果关闭了1号文件,printf就无法向1号文件(显示器)写入了 ,但可以向3号文件写入,所以我们打印就只能看到n的值
2.5访问文件的本质
-
任何一个被打开的文件在内存中都要被管理起来,操作系统如果管理被打开的文件?----先描述再组织
- 当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件--file结构体(直接或间接包含如下属性:文件的基本属性,文件的内核缓冲区信息,引用计数,struct file*next,在磁盘的什么位置),表示一个已经打开的文件对象
- 而进程执行open系统调用,所以必须让进程和文件关联起来,每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!
- 所以,本质上,文件描述符就是该数组的下标,只要拿着文件描述符,就可以找到对应的文件
- 当一个进程open()一个文件时,操作系统会在struct_file的指针数组中从下标为0的地方在开始寻找一个没有被使用过的数组位置,填入要打开文件的struct file*,再将数组下标返回给open( )调用,作为该文件的文件描述符fd
- 当一个进程要向某个文件写入的时候,操作系统只认识文件描述符,根据文件描述符找到对应的数组下标,根据数组下标位置里的内容找到所对应的文件再写入
- close关闭文件本质上是清空对应fd数组下标位置的内容,再将该fd内容指向的文件的引用计数--,引用计数为0才释放销毁相应的struct_ file
三、文件重定向
3.1认识文件重定向
-
关闭1号文件再打开新文件 ,向1号文件写入内容
可以看到,原来要向1号文件(显示屏)打印的信息,被写入到了新打开的文件,其中,fd=1。这种现象叫做输出重定向
常见的重定向有:>输出重定向, >>追加重定向, <输入重定向
- 追加重定向
- 输入重定向
3.2文件重定向的本质
-
文件重定向的本质:将1号文件描述符在指针数组中对应位置的内容,用log.txt文件描述符在指针数组中对应位置的内容进行覆盖,原本数组内的指向1号文件的文件指针就被替换成log.txt的文件指针,当我们再向1号文件描述符写入内容的时候,就是向文件指针指向的log.txt内写入而不再写到标准输出
-
dup2系统调用
-
原本向显示屏打印的内容被写入到log.txt文件中
3.3在shell中添加重定向功能
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<ctype.h>
#include<fcntl.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGV_SIZE 32#define NONE -1
#define IN_RDIR 0
#define OUT_RDIR 1
#define APPEND_RDIR 2extern char** environ;
char commandline[LINE_SIZE];
char* argv[ARGV_SIZE];
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];int lastcode=0;
int quit=0;char *rdirfilename = NULL;
int rdir = NONE;const char* getuser()
{return getenv("USER");
}const char* gethostname()
{return getenv("HOSTNAME");
}void getpwd()
{getcwd(pwd,sizeof(pwd));
}void check_redir(char *cmd)
{// ls -al -n// ls -al -n >/</>> filename.txtchar *pos = cmd;while(*pos){if(*pos == '>'){if(*(pos+1) == '>'){*pos++ = '\0';*pos++ = '\0';while(isspace(*pos)) pos++;rdirfilename = pos;rdir=APPEND_RDIR;break;}else{*pos = '\0';pos++;while(isspace(*pos)) pos++;rdirfilename = pos;rdir=OUT_RDIR;break;}}else if(*pos == '<'){*pos = '\0'; // ls -a -l -n < filename.txtpos++;while(isspace(*pos)) pos++;rdirfilename = pos;rdir=IN_RDIR;break;}else{//do nothing}pos++;}
}void interact(char* cline,int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getuser(),gethostname(),pwd);char* s=fgets(cline,size,stdin);assert(s);(void)s;cline[strlen(cline)-1]='\0';//printf("echo : %s",cline);//ls -a -l > myfile.txtcheck_redir(cline);
}int splitstring(char cline[],char* _argv[])
{int i=0;_argv[i++]=strtok(cline,DELIM);while(_argv[i++]=strtok(NULL,DELIM));return i-1;
}void normalexcute(char* _argv[])
{pid_t id=fork();if(id<0){perror("fork");//continue;return ;}else if(id==0){int fd = 0;// 后面我们做了重定向的工作,后面我们在进行程序替换的时候,难道不影响吗???if(rdir == IN_RDIR){fd = open(rdirfilename, O_RDONLY);dup2(fd, 0);}else if(rdir == OUT_RDIR){fd = open(rdirfilename, O_CREAT|O_WRONLY|O_TRUNC, 0666);dup2(fd, 1);}else if(rdir == APPEND_RDIR){fd = open(rdirfilename, O_CREAT|O_WRONLY|O_APPEND, 0666);dup2(fd, 1);}//子进程执行指令//execvpe(argv[0],argv,environ);execvp(argv[0],argv);}else{int status=0;pid_t rid=waitpid(id,&status,0);if(rid==id){lastcode=WEXITSTATUS(status);}}
}int buildcommand(char* _argv[],int _argc)
{if(_argc==2&&strcmp(_argv[0],"cd")==0){chdir(_argv[1]);getpwd();sprintf(getenv("PWD"),"%s",pwd);return 1;}else if(_argc==2&&strcmp(_argv[0],"export")==0){strcpy(myenv,_argv[1]);putenv(myenv);return 1;}else if(_argc==2&&strcmp(_argv[0],"echo")==0){if(strcmp(_argv[1],"$?")==0){printf("%d\n",lastcode);lastcode=0;}else if(*_argv[1]=='$'){char* s=getenv(_argv[1]+1);if(s) printf("%s\n",s);}else{printf("%s\n",_argv[1]);}return 1;}//特殊处理lsif(_argc==2&&strcmp(_argv[0],"ls")==0){_argv[_argc++]="--color";_argv[_argc]=NULL;}return 0;}int main()
{while(!quit){//交互问题,获得命令行参数interact(commandline,sizeof commandline);//字符串分割,解析命令行参数int argc = splitstring(commandline,argv);if(argc==0) continue;//指令的判断int n=buildcommand(argv,argc); //普通指令的执行if(!n)normalexcute(argv);}return 0;
}
- 进程历史打开的文件以及文件的重定向关系,并不会被程序替换所影响!!进程程序替换之后影响页表右边的物理地址所指向的内容,虚拟地址并左边的部分并不会受到影响
- 程序替换并不会影响文件访问
3.4stdout和stderr
- stdout和stderr对应的硬件设备都是显示屏,访问的都是同一个文件(引用计数)
- 在重定向的时候,默认只对stdout的fd进行重定向
代码示例:
- 如果对1号和2号文件都要进行重定向呢?
示例:./mytest 1> log.txt 2>err.txt
示例:./mytest > log.txt 2>&1
3.5如何理解“linux下一切皆文件” --以对外设的IO操作为例
- 不同的外设在进行IO操作时都有自己对应的读写方法,放在struct device里
- 这些读写方法如何被找到?--由struct operation_func来对读写方法进行管理,该结构体里存在指向对应读写法的函数指针
- 如何找到struct operation_func?--由struct file来对struct operation_func进行管理,file结构体存在指向struct operation_func的指针,基于struct file之上的被称为虚拟文件系统(VFS)--一切皆文件
- 当我们打开一个文件的时候,通过进程的pcb数据结构找到struct struct_file,操作系统根据文件描述符的分配规则,在struct struct_file的指针数组中为该文件分配一个fd;当我们要访问一个外设的时候,根据该外设文件fd对应的数组下标内容找到该外设文件的struct file,根据file结构体找到对应的struct operation_func,由于访问的外设的不同,在struct operation_func中根据函数指针找到对应的读写方法,就可以对外设进行访问了
四、文件缓冲区
4.1认识FILE
- 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的
- 所以C库当中的FILE结构体内部,必定封装了fd
4.2文件缓冲区引入
- 对比有无fork( )的代码
- 我们发现 printf 和 fwrite (库函数)都输出了 2 次,而 write 只输出了一次(系统调用),为什么呢?肯定和 fork有关!
- 再来验证一个现象:

代码运行的结果是:只有系统调用接口写入的内容被打印出来了
加上'\n',结果又不一样了
4.3文件缓冲区的原理
- C语言会提供一个缓冲区,我们调用C文件接口写入的数据会被暂存在这个缓冲区内,缓冲区的刷新方式有三种:
- 无缓冲:直接刷新,一般我们使用的fflush( )就是无缓冲的刷新方式
- 行缓冲:遇到'\n'才刷新,一般对应显示器
- 全缓冲:缓冲区满了才刷新,一般对应普通文件的写入
- 特殊说明:进程结束的时候会自动刷新缓冲区
- 在操作系统的内核中也存在一个内核级别的缓冲区,目前认为,只要将数据刷新到了内核,数据就可以到硬件了,内核缓冲区也有自己的刷新方式
- 为什么要有C层面的缓冲区?
- 用户不需要一步一步将数据写入到硬件中,而是可以直接调用C库为我们提供的读写方法,将数据交给库函数来处理,解决用户的效率问题
- 我们真正存到文件里的都是一个个的字符,调用C库的读写方法,可以在放入缓冲区之前将我们的数据格式化成字符串,再刷新到内核中进而写入文件,C层面的缓冲区可以配合格式化的工作
- C为我们提供的缓冲区在FILE结构体里,FILE里面有相关缓冲区的字段和维护信息,FILE属于用户层面,而不属于操作系统
- 文件写入的过程:
- 首先,在文件写入之前,进程会打开一个文件,通过对各种内核数据结构的访问和操作,获得该文件的文件描述符
- 如果使用系统调用接口来对文件进行写入,数据直接通过write和fd写入对应的内核级别缓冲区,默认最后都会刷新到硬件中
- 如果使用fwrite等库函数来对文件进行写入,首先,在语言层面会malloc出一个FILE结构体,FILE里面有对应的缓冲区信息以及文件的fd,然后内容会先被暂存在C层面的缓冲区,如果是无缓冲,数据直接被刷新到内核中,如果是行缓冲,遇到'\n'就会被刷新到内核中,如果是全缓冲,等缓冲区满了就被刷新到内核中
- 由于库函数是对系统调用接口的封装,用户通过write和fd将数据刷新到对应的文件的内核缓冲区内,再由该内核缓冲区刷新到外设
4.4解释现象
-
为什么不加'\n'并且close(1)的时候,使用库函数写入的内容不会被显示?
-
不加'\n',调用库函数写入的数据都会被暂存在C层面的缓冲区
-
close(1)后,即使进程退出后缓冲区会自动刷新,但是此时已经找不到1号文件的fd了,缓冲区内的数据也无法被写入到内核中,最后也不会显示到显示器上
-
加了'\n'即使最后close(1),遇到'\n'缓冲区就会立马将数据刷新到内核中,就会显示到显示器上
- 为什么fork()之后重定向C接口会被调用两次?
- 重定向后,缓冲区的刷新方式会从行缓冲变成全缓冲,也就说,数据要么等到缓冲区满了再被刷新,要么等待进程结束后再刷新,所以我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
- fork( )之后,创建子进程,子进程会继承父进程的内核数据结构对象的内容,父子进程在一开始的时候数据和代码是共享的,缓冲区也属于数据
- 进程退出后,要对缓冲区的数据进行统一刷新,刷新就是对数据进行访问写入,此时父子数据会发生写时拷贝,所以当父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据
- 由于write没有所谓的缓冲区,write()写入的数据直接在内核中,所以write( )的数据只有一份
4.5总结
- printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。这里所说的缓冲区, 都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区
- 那这个用户级缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统 调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,说明该缓冲区是二次加上的,由C标准库提供

相关文章:

Linux文件重定向文件缓冲区
目录 一、C文件接口 二、系统文件I/O 2.1认识系统文件I/O 2.2系统文件I/O 2.3系统调用和库函数 2.4open( )的返回值--文件描述符 2.5访问文件的本质 三、文件重定向 3.1认识文件重定向 3.2文件重定向的本质 3.3在shell中添加重定向功能 3.4stdout和stderr 3.5如何理…...

训练贪吃蛇ai的后续记录
发现可以结合遗传算法的思路,产生更好的效果。 即每训练一段时间,就停下来测试一下新模型的效果。如果效果优于记录中最好的,则继续导入该模型并训练。重复几次,效果可能更好。 例如,昨晚我便通过唯一一个在十次测试中…...

WPF 手撸插件 八 操作数据库一
1、本文将使用SqlSugar创建Sqlite数据库,进行入门的增删改查等操作。擦,咋写着写着凌乱起来了。 SqlSugar官方文档:简单示例,1分钟入门 - SqlSugar 5x - .NET果糖网 2、环境SqlSugar V5.0版本需要.Net Framework 4.6 ࿰…...

代数结构基础 - 离散数学系列(八)
目录 1. 群(Group) 群的定义 群的示例 2. 环(Ring) 环的定义 环的示例 3. 域(Field) 域的定义 域的示例 域在密码学中的应用 4. 实际应用场景 1. 对称性与加密 2. 误差检测与纠正 3. 数据编码…...
函数的arguments为什么不是数组?如何转化为数组?
因为arguments本身并不能调用数组方法,它是一个另外一种对象类型,只不过属性从0开始排,依次为0 1 2…最后还有callee和length属性,我们也把这样的对象成为类数组。 常见的类数组还有: 1.用getElementsByTagName/Class…...

Java之反射
目录 反射 定义 主要用途 反射相关的类 Class类中【获得类相关方法】 Class类中【获得类中属性相关的方法】 Class类中【获得类中注解相关的方法】 Class类中【获得类中构造器相关的方法】 Class类中【获得类中方法相关的方法】 获得Class对象 代码示例1 代码示例…...

3dsMax添加天空盒
点击渲染,环境 , 点击位图 找到要设置的天空HDR,可以使用HDR(EXR)贴图 一个可以下载HDR贴图的网站 https://polyhaven.com/hdris在渲染的时候不要使用使用微软输入法,3dsmax会卡死, 在渲染的时候不要使用使用微软…...

C语言的类型提升机制
概念 在C语言中,整数类型按照其大小可以分为以下几类(从小到大): charshortintlonglong long 当在表达式中涉及这些类型的混合运算时,较小的类型会被提升为较大的类型。具体规则如下: ①char 和 short …...

Pandas和Seaborn数据可视化
Pandas数据可视化 学习目标 本章内容不需要理解和记忆,重在【查表】! 知道数据可视化的重要性和必要性知道如何使用Matplotlib的常用图表API能够找到Seaborn的绘图API 1 Pandas数据可视化 一图胜千言,人是一个视觉敏感的动物,大…...
爬虫(Python版本)
1.爬虫的法律问题 爬虫技术(Web Scraping)指通过程序自动访问网页并提取其中的数据。在使用爬虫的过程中,涉及到一些法律法规和合规性问题。 常见法律风险 ①未经授权的访问:很多网站对爬虫行为设置了限制。如果未获得授权就进行…...
【分布式训练 debug】VS Code Debug 技巧:launch.json实用参数
VS Code Debug技巧:launch.json实用参数 在使用Visual Studio Code (VS Code)进行调试时,launch.json文件是一个强大的工具,它允许你自定义调试会话。以下是一些实用的参数,可以帮助你更有效地调试Python代码。 1. 调试第三方库…...
pycharm连接linux服务器需要提前安装ssh服务
在 Debian 或 Ubuntu 系统上,使用 APT: bash复制代码 sudo apt-get install openssh-server 在基于 RPM 的系统如 CentOS 或 RHEL 上,使用 YUM 或 DNF: bash复制代码 sudo yum install openssh-server 或对于较新的 RHEL/Cent…...

通信工程学习:什么是LAN局域网、MAN城域网、WAN广域网
LAN局域网、MAN城域网、WAN广域网 LAN(Local Area Network,局域网)、MAN(Metropolitan Area Network,城域网)和WAN(Wide Area Network,广域网)是计算机网络中根据覆盖范围…...
LeetCode热题100速通
一丶哈希 1、两数之和(简单) 给定一个整数数组 n u m s nums nums 和一个整数目标值 t a r g e t target target,请你在该数组中找出 和为目标值 t a r g e t target target 的那 两个 整数,并返回它们的数组下标。 你可以假设…...
Python代码编写KDJ指标
KDJ指标由三部分组成:K值、D值、J值,主要用于分析股票市场的超买超卖状态及股价波动的趋势。博主记录学习编写KDJ指标线 import numpy as npdef calculate_kdj(close_prices, n9, m13, m23):"""计算KDJ指标:param close_prices: 收盘价序…...

传统少数民族物品检测系统源码分享
传统少数民族物品检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer…...
深度学习中的迁移学习:预训练模型微调与实践
深度学习中的迁移学习:预训练模型微调与实践 目录 💡 迁移学习的核心概念🧠 预训练模型的使用:ResNet与VGG的微调🏥 迁移学习在医学图像分析中的应用🔄 实践中的迁移学习微调过程 1. 💡 迁移学…...

原生input实现时间选择器用法
2024.10.08今天我学习了如何用原生的input,实现时间选择器用法,效果如下: 代码如下: <div><input id"yf_start" type"text"> </div><script>$(#yf_start).datepicker({language: zh…...
对象的概念
对象是编程中一个重要的概念,尤其在面向对象编程(OOP)中更为核心。简单来说,对象是一种数据结构,它可以存储相关的数据和功能。以下是关于对象的详细描述: 1. 对象的定义 对象是属性(数据&…...

ARIMA|基于自回归差分移动平均模型时间序列预测
目录 一、基本内容介绍: 二、实际运行效果: 三、原理介绍: 四、完整程序下载: 一、基本内容介绍: 本代码基于Matlab平台,通过ARIMA模型对时间序列数据进行预测。程序以通过调试,解压后打开…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...