LINUX基础IO [六] - 文件理解与操作

news2025/3/30 15:35:14

目录

前言

C语言文件操作回顾

文件的打开与关闭

文件的增删改查

文件系统调用

比特位方式的标志位传递原理  

访问文件的本质

文件描述符fd

理解文件描述符fd

三个流的理解 

文件描述符的分配规则

重定向再理解

输出重定向 

输入重定向 

如何理解一切皆文件

理解为什么会有面向对象


前言

文件操作是 基础IO 学习的第一步,我们在 C语言 进阶中,就已经学习了文件相关操作,比如 fopen 和 fclose,语言层面只要会用就行,但对于系统学习者来说,还要清楚这些函数是如何与硬件进行交互的

文件由什么构成?一般文件放在哪里?

文件 = 内容 属性
未使用的文件位于 磁盘,而使用中的文件 属性 会被加载至内存中
本文讨论的是已被加载至内存文件的相关操作

系统是如何区分文件的?

文件可以同时被多次使用,OS 为了管理好文件,会像使用 task_struct 管理进程一样,通过 struct file 存储文件属性进行管理
struct file 结构体包含了文件的各种属性和链接关系

可以用以下的例子去理解:

快递(文件)  分为被人(进程)取走的快递(打开的文件)和没被取走的快递(没打开的文件),被人取走的快递研究的是人和快递的关系(进程和文件的关系) ,而没被人取走的快递,他会被暂时安防在菜鸟驿站(磁盘)  

他的数量很多(文件非常多) 所以我们打算去取的时候其实我们是会收到一个取件码的(查找该文件的信息)  然后我们根据这个号码比方说3-1113   我们会找到这个区域 然后再去找号码  

所以最关键的是快递如何被按照区域划分好(对文件分门别类地存储)  这样才能方便人去取(方便我们快速找到文件并对文件的增删查改)

  • 语言层面的文件操作就是直接使用库函数,而事实上,文件操作是系统层面的问题,就像进程管理一样,系统也会通过 先描述,再组织 的方式对文件进行管理、操作

C语言文件操作回顾

文件的打开与关闭

fopen与fclose

FILE * fopen ( const char * filename, const char * mode );

参数mode:

  • 只写,如果文件不存在,会新建,文件写入前,会先清空内容
  • 追加,在文件末尾,对文件进行追加写入,追加前不会清空内容
  • 只读,打开已存在的文件进行读取,若文件不存在,会打开失败
  • w+、a+、r+ 读写兼具,区别在于是否会新建文件,只有 r+ 不会新建

若文件打开失败,会返回空 NULL,可以在打开后判断是否成功

注意: 若参数1直接使用文件名则此文件需要位于当前程序目录下,如果想指定目录存放,可以使用绝对路径

int fclose ( FILE * stream );

 关闭已打开文件,只需通过 FILE* 指针进行操作即可

注意: 只能对已打开的文件进行关闭,若文件不存在,会报错

#include <stdio.h>

int main()
{
    // 打开文件的路径和文件名,默认在当前路径下新建一个文件?
    FILE *fp = fopen("log.txt", "w");
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    fclose(fp);

    return 0;
}

为什么我们默认会新建在当前路径,凭什么?

当前路径,其实就是进程的路径,因为进程在执行的过程中,他需要知道自己从哪来,也要知道如果自己产生一些临时性的文件时应该放在哪里,所以他需要一个默认路径cwd。表明的是他当前的工作目录。

因为进程PCB结构体内部有一个cwd属性,如果我们更改了进程的,cwd属性,就可以将文件新建到别的地方!

先被加载到内存的是文件的属性还是文件的内容?

当你fopen的时候,其实就需要创建一个文件的内核数据结构,里面包含了文件的一些必要属性,所以是属性先被加载进去了!! 至于内容需不需要被加载,关键看你有没有通过一些接口来对该文件进程增删查改!!

文件的增删改查

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    // chdir("/home/whb");
    printf("Pid: %d\n", getpid());
    // 打开文件的路径和文件名,默认在当前路径下新建一个文件?
    FILE *fp = fopen("log.txt", "w");
    if(fp == NULL){
        perror("fopen");
        return 1;
    }

    const char *message = "hello Linux message";
    // strlen(message) + 1 ? 为什么?
    fwrite(message, strlen(message), 1, fp);

    fclose(fp);
    sleep(1000);
    return 0;
}

  • w:在写入之前,会对文件进行清空处理!! 所以我们可以知道echo重定向方式写入也会先清空文件,所以底层必然也是w形式!
  • a:在文件的结尾追加写!! 

fwrite(message, strlen(message)+1, 1, fp);为什么这里的strlen要+1?

字符串在 C 语言中以 \0(空字符)结尾,它用于标识字符串的结束。虽然 strlen(message) 计算的是不包含 \0 的长度,但如果你想要完整写入整个字符串(包括 \0),你需要加 1。如果我们+1带上/0,此时会打出一个乱码,但是这个乱码是什么并不重要,重要的是我们发现/0也是一个可以被写进去的字符!

结论:因为字符串以/0结尾,是C语言的规定,跟文件、跟操作系统没有任何关系!

文件系统调用

我们上面说了,未使用文件是在磁盘上的。所以我们想要访问文件其实就是访问硬件,因此几乎所有的库想要访问硬件设备,就必须封装系统调用!!

我们来看open的man手册

参数pathname是文件名,参数flags是打开的模式,而mode是权限设置      因此第一个open是用来打开一个已经存在的文件,而第二个open打开的是新建的文件(因为我们需要给新建的文件设置权限!)

  • O_RDONLY:只读模式
  • O_WRONLY:只写模式
  • O_RDWR:读写模式
  • O_CREAT:如果文件不存在则创建它
  • O_EXCL:如果文件已存在,则调用 open 失败
  • O_APPEND:每次写入数据时都附加到文件末尾
  • O_TRUNC:如果文件存在并且是写模式,则清空文件
  • O_CLOEXEC:在执行 exec() 时关闭文件描述符

位操作原理

  • 这些文件状态标志使用位来表示。例如,O_RDONLY 是一个标志,它占据一个特定的位,类似地,O_WRONLYO_RDWR 也分别占据不同的位。
  • 位操作:通过组合这些标志,我们能够灵活地设置文件打开时的行为。例如,如果想要以可读写模式打开文件,并且如果文件不存在就创建它,可以使用 O_RDWR | O_CREAT 来组合两个标志。

关于文件权限的传递,我们要记得因为有粘滞位umask 所以我们想要设置的权限可能并不是我们最终想要的,所以我们要向用umask把该进程的粘滞位变成0!

比特位方式的标志位传递原理  

状态的组合方式有很多种,但是为什么操作系统只用一个int类型就可以表明这些情况??

#define ONE (1<<0) // 1
#define TWO (1<<1) // 2
#define THREE (1<<2) // 4
#define FOUR (1<<3) // 8

void show(int flags)
{
    if(flags & ONE) printf("hello function1\n");
    if(flags & TWO) printf("hello function2\n");
    if(flags & THREE) printf("hello function3\n");
    if(flags & FOUR) printf("hello function4\n");
}
int main()
{
    printf("\n");
    show(ONE);
    printf("\n");
    show(TWO);
    printf("\n");

    show(ONE | TWO);
    printf("\n");
    show(ONE | TWO | THREE);
    printf("\n");

    show(ONE | THREE);
    printf("\n");
    show(THREE | FOUR);
    printf("\n");
}

通过位图的方式一次向一个调用传递多个标记位,这是操作系统传递参数的一种方式!本质上是在外部用 | 的方式组合  在内部的方式用& 的方式检测!! 

访问文件的本质

以前我们学C语言的时候,fopen的返回值是一个FILE*  那个时候我们知道这个是C库封装的一个结构体,但是为什么系统调用解决open的返回值是一个整形呢???

因为一个进程可能打开多个文件,那么我们想要快速地找到任意一个文件,如果仅仅是用链表的方式组织,确实太慢了!!

所以在PCB结构体内部,其实有一个file_struct*指针,该指针指向一个file_struct结构体,该结构体就是操作系统给该进程提供的一个文件描述符表,里面除了一些必要的字段信息,还有一个存放file*指针的指针数组,这些file*指针分别指向一个个被该进程打开的文件!

所以fd我们称之为文件描述符,他的本质就是文件描述符表的下标,我们可以通过这个下标里存储的file指针找到我们想操作的被打开的文件!! 

file结构体里面有什么呢?? 

肯定直接或者间接(间接的意思是可能内部还有别的结构对象)包含如下属性:

  • 在磁盘的什么位置
  • 基本的属性:权限、大小、读写位置、谁打开的
  • 文件的内核缓冲区
  • 引用计数(因为一个文件可能会被多个进程打开,所以当一个进程关闭该文件的时候不代表这个文件的结构体就被释放了,而是要引用计数为0时才释放)
  • file* next:链表节点结构,可以跟其他的文件链接成双链表的形式做管理!

文件描述符fd

理解文件描述符fd

 一个进程在打开的时候默认会打开3个文件:标准输入文件(键盘)、标准输出文件(显示器)、标准错误文件(显示器)…… 他们的fd分别为 0  1  2   

有没有觉得很眼熟??因为我们在学C语言的时候也知道C程序会默认打开3个流!有什么关系??

标准输入流、标准输出流、标准错误流其实并不是C语言的特性!!而是操作系统的特性!

FILE* 是什么??如何理解?

FILE* 是一个C库自己封装的结构体,由于系统调用接口用的是fd文件描述符来对指定的文件进行操作,所以我们可以知道FILE结构体内部必然封装着文件描述符fd!

为什么一定要打开这三个流呢??

因为我们的电脑开机的时候,我们的操作系统就默认检测到了显示器、键盘这类的设备,所以进程打开的时候就必然需要有这些,因为我们程序员天然需要通过键盘、显示器来观察结果。

三个流的理解 

看下面的代码

int main()
{
    printf("stdin->fd: %d\n", stdin->fileno());
    printf("stdout->fd: %d\n", stdout->fileno());
    printf("stderr->fd: %d\n", stderr->fileno());
}

输出结果

stdin->fd: 0
stdout->fd: 1
stderr->fd: 2

 如果我们先把文件操作符1关闭,看看会出什么结果

int main()
{
    // 关闭文件描述符 1,即 stdout
    close(1);
    printf("stdin->fd: %d\n", stdin->_fileno());
    printf("stdout->fd: %d\n", stdout->_fileno());
    printf("stderr->fd: %d\n", stderr->_fileno());
}

我们把printf换成fprint再次打印printf的返回值

int main()
{
    close(1); // 关闭 stdout(文件描述符 1)

    int n = printf("stdin->fd: %d\n", stdin->_fileno());
    printf("stdout->fd: %d\n", stdout->_fileno());
    printf("stderr->fd: %d\n", stderr->_fileno());

    fprintf(stderr, "printf ret: %d\n", n);
}
printf ret:13

我们发现,如果把1号文件描述符关闭,就无法出结果了,但是读取的结果还是正确的。原因是:printf是C库函数 底层封装的时候默认向 stdout的1号描述符里写入,所以如果你把1号给关了,printf底层调用write这个函数会失败,但是printf本身并不知道,所以他是有返回值的。而 fprintf 的优点是可以指定我们想要输出的流,所以当我们想stderr的2号描述符写入的时候,恰好也是指向显示器文件,所以就会被打印出来!!

侧面可以证明  文件的file结构体里必然存在引用计数!! 因为在1号描述符关闭之后,显示器文件并没有被关闭,所以close底层的操作就是对count计数--,然后将文件描述符表的指针置空,但是显示器文件还是打开着的,因为2号描述符还指向显示器文件!!

总结:任何一门语言对文件描述符的封装不一样,是千变万化的,比如在C++中可能还会有继承和多态的体系。但是万变不离其中,在底层都是使用的操作系统提供的系统调用接口,对fd文件描述符进行封装。 所以底层理解了,其实任何语言都是学习他的应用而已!

文件描述符的分配规则

在学习文件描述符的规则之前,我们先回想下刚才讲的 open 和 write 系统调用接口

最小未使用原则:

  • 进程在分配文件描述符时,会查询其内部的文件描述符表(内核中的文件指针数组)
  • 选择分配最小的、当前未被使用的文件描述符给新打开的文件或流

当我们关闭1之后,我们执行程序发现,并没有在屏幕上产生输入

我们来修改一下代码: 

// 各种头文件
int main()
{
    close(1);
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd:%d\n", fd);
    printf("stdout:%d\n", stdout->_fileno);
	fflush(stdout);
                                                                                                                                                                                  
    close(fd);
    return 0;
}

 

我们在刷新之后发现,本来要打印在屏幕上的fd竟然出现在了log.txt里面,如果我们先把1关掉,再打开文件,那么给它分配的文件描述符就是1,但是为什么会将内容写到文件里面呢? 

  • printf 默认会将输出写入 标准输出流(stdout)。在文件描述符中,标准输出流的文件描述符是 1。所以,当你调用 printf 时,数据会通过文件描述符 1 输出到终端。,所以打印的内容就被重定向到了log.txt中
  • 为什么是刷新之后有,不刷新就没有呢?-> 因为在没有刷新时,内容是储存在缓冲区的,刷新之后才会出现

重定向再理解

在Linux中,重定向是一种将命令的标准输入(stdin)、标准输出(stdout)或标准错误(stderr)重新指向文件或其他命令的技术。这种机制允许用户将命令的输出保存到文件中,或者将文件的内容作为命令的输入。重定向通过使用特定的符号来实现,这些符号主要包括 >、>>、<2>

输出重定向 

我们在关闭1后发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向 

重定向的本质其实就是修改文件特征fd的下标内容。新文件描述符覆盖旧的文件描述符

难道我们必须要先把1号文件关闭了再打开新的文件才能完成重定向吗??

其实本质上就是将新文件的指针覆盖掉原来1号位置的指针就行了,系统提供了一个接口叫dup来帮助我们解决这个问题!所以输出重定向和追加重定向底层肯定使用了dup接口

dup2是一个系统调用,用于复制一个现有的文件描述符到另一个文件描述符的位置,同时关闭目标文件描述符(如果它之前已打开)。这个调用主要用于重定向标准输入、标准输出或标准错误流到文件或其他I/O设备

 他是用oldfd覆盖掉newfd

int main()
{
    //int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fd = open("log.txt", O_RDONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    //dup2(fd, 1);                                                                                                                                                                                             
    //printf("hello world\n");
    //fprintf(stdout, "hello wrold\n");

	dup2(fd, 0); 
	char buf[1024];  
	fread(buf, 1, sieof(buf), stdin);
	printf("%s\n, buf"):
	
    close(fd);
 	return 0;
 }

进程替换会影响文件的重定向吗??

输入重定向 

是指将程序的标准输入从键盘重定向到一个文件,或者将命令的输入从标准输入流(通常是键盘)重定向到文件、管道等。它通常在命令行或脚本中使用。

read的使用:

int main()
{
    close(0);
    int fd = open("log.txt", O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    char buf[1024];
    fread(buf, 1, sizeof(buf), stdin);
    printf("%s\n", buf);                                                                                                                                                                                     

    close(fd);
 	return0;
 }

 本来fread是要求我们从键盘输入的,但是他直接从文件里面读取了,那么到底什么是重定向,我们来画图了解一下 

为什么要有stderr? 

1. 将程序的运行结果分别重定向到两个不同的文件

这样我们可以把运行结果放到我们的正常文件里,然后把错误的一些信息放到我们的错误文件里,方便我们观察,这就是为什么要有stderr的原因。

这才是重定向的本质写法,只不过我们平常不写fd的话默认就是将1号文件的内容重定向。

2. 将两个文件的结果都写到一个文件

这个意思是1号文件的地址变成了all.log 然后2也被写入到原来1的位置,所以最后其实都被写到了all.log文件里面

如何理解一切皆文件

计算机上进行的所有操作,所有任务都会被系统检测成进程,所以进程是操作系统帮助用户完成任务的主要渠道,几乎没有之一 

因此我们目前对文件的所有操作其实都依赖于进程操作!!

而我们所有的外设都需要提供相应的读写方法 (跟文件有点类似)!所以我们会尝试把外设也当成是文件来处理,在使用的时候在内核层面搞一个file结构体, 但是这样会遇到一个问题就是,并不是所有的外设都有读写方法的!!比如说键盘只有写方法,而显示器只有读方法,那我们的file要如何做区分呢??
所以操作系统还给这些文件提供了一个  方法  结构体  里面保存了读方法和写方法的指针  

这俩函数指针指向的是各个外设的读写方法。

就比如说我当前想要打开显示器,在创建file结构体的时候顺便创建了方法结构体,里面的读写的函数指针分别指向显示器的读方法和写方法,所以因为显示器只有写方法,读方法是空,于是在调用的时候就自然区分得出来了

其实这就是VFS 虚拟文件系统,所以可以理解Linux一切皆文件。

其实我们还可以发现  这个文件其实就是基类,而外设就是派生类,然后指针指向什么就调用什么对象,这就是多态,只不过Linux必须用C语言写,所以只能用函数指针来完成这个工作!!

理解了Linux的一切皆文件后,懂得了文件操作的底层,即使以后在使用其他语言的文件操作时对接口不熟,但只要给时间查一下,很快就会懂得怎么用了!!(没有太多的恐惧),这就是理论知识所带给我们的力量和底气!! 

理解为什么会有面向对象

如果面向对象是C++的专属,那么他只能算是一种特性,但是当你发现大部分主流语言都是支持面向对象的那么这就说明面向对象不是一两门语言的特性,而是历史的必然!!

为什么会有人能凭空想出来面向对象的语言呢??

因为人们在经过大量的工程实验后,发现我们总是或多或少要使用一些多态的特性,比如说写操作系统的人必然也是有可能开发语言的人,他在写的时候就意识到Linux里面很多虚拟化的东西,要不是你必须拿C去写,我早就发明出一门面向对象的语言了,直接搞个基类派生类出来就很快了!! ——>因为很多地方需要对软件做分层,设置出各种虚拟化的场景(比如刚刚提到的文件虚拟系统就是,只不过Linux必须用C写,否则肯定用C++写更方便) ——>封装、继承、多态!

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

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

相关文章

拥抱人工智能大模型时代:大模型会改变我们的生活吗?

在这个科技日新月异的时代&#xff0c;人工智能&#xff08;AI&#xff09;正以前所未有的速度改变着我们的生活和工作方式。尤其是随着人工智能大模型&#xff08;如ChatGPT、DeepSeek等&#xff09;的崛起&#xff0c;人们对于AI技术的期待和关注达到了前所未有的高度。那么&…

常见框架漏洞攻略-ThinkPHP篇

漏洞名称&#xff1a;Thinkphp5x远程命令执行及getshell 第一步&#xff1a;开启靶场 第二步&#xff1a;准备工具 第三步&#xff1a;启动工具&#xff0c;进行漏洞检测 #存在漏洞 1.目标存在tp5_invoke_func_code_exec_1漏洞2.目标存在tp5_dbinfo_leak漏洞payload:http://47…

LlamaFactory部署及模型微调【win10环境】

1.Llama-Factory简介 LLaMA-Factory&#xff0c;全称 Large Language Model Factory&#xff0c;旨在简化大模型的微调过程&#xff0c;帮助开发者快速适应特定任务需求&#xff0c;提升模型表现。它支持多种预训练模型和微调算法&#xff0c;适用于智能客服、语音识别、机器翻…

vue3配置代理实现axios请求本地接口返回PG库数据【前后端实操】

前端编写 安装 axios 如果当前未安装axios&#xff0c;可以执行如下指令安装 npm install axios配置代理 当前为基于Vite构建的项目&#xff0c;在 vite.config.ts 中配置代理&#xff0c;在defineConfig中新增server配置&#xff0c;主要关注两个点&#xff1a; 一、需要代…

trae 配置 gradle springboot项目

一 本机安装gradle 1.下载gradle &#xff1a; https://github.com/gradle/gradle-distributions/releases/download/v8.13.0/gradle-8.13-all.zip 2.配置相关环境变量&#xff1a; GRADLE_HOME&#xff1a;本地的gradle路径。 GRADLE_USER_HOME&#xff1a;gradle 本地仓…

uv:Rust 驱动的 Python 包管理新时代

在 Python 包管理工具层出不穷的今天&#xff0c;pip、pip-tools、poetry、conda 等各有千秋。而今天要介绍的 uv&#xff0c;则是一款由 Astral 团队推出、采用 Rust 编写的全新工具&#xff0c;目标直指成为 “Python 的 Cargo”。它不仅在性能上表现优异&#xff0c;而且在功…

sqlserver 阻止保存要求重新创建表的更改

1 选择 “工具” 菜单&#xff0c;然后点击 “选项” 2 进入选项界面后&#xff0c;选择 “设计器”&#xff0c;取消勾选 “阻止保存要求重新创建表的更改” 选项&#xff0c;点击 “确定”

5.Excel:从网上获取数据

一 用 Excel 数据选项卡获取数据的方法 连接。 二 要求获取实时数据 每1分钟自动更新数据。 A股市场_同花顺行情中心_同花顺财经网 用上面方法将数据加载进工作表中。 在表格内任意区域右键&#xff0c;刷新。 自动刷新&#xff1a; 三 缺点 Excel 只能爬取网页上表格类型的…

在word中使用zotero添加参考文献并附带超链接

一、引言 在写大论文时&#xff0c;为了避免文中引用与文末参考文献频繁对照、修改文中引用顺序/引用文献时手动维护参考文献耗易出错&#xff0c;拟在 word 中使用 zotero 插入参考文献&#xff0c;并为每个参考文献附加超链接&#xff0c;实现交互式阅读。 版本&#xff1a…

性能测试、负载测试、压力测试的全面解析

在软件测试领域&#xff0c;性能测试、负载测试和压力测试是评估系统稳定性和可靠性的关键手段。​它们各自关注不同的测试目标和应用场景&#xff0c;理解这些差异对于制定有效的测试策略至关重要。 本文对性能测试、负载测试和压力测试进行深入分析&#xff0c;探讨其定义、…

Redis中的数据类型与适用场景

目录 前言1. 字符串 (String)1.1 特点1.2 适用场景 2. 哈希 (Hash)2.1 特点2.2 适用场景 3. 列表 (List)3.1 特点3.2 适用场景 4. 集合 (Set)4.1 特点4.2 适用场景 5. 有序集合 (Sorted Set)5.1 特点5.2 适用场景 6. Redis 数据类型的选型建议结语 前言 Redis 作为一款高性能的…

gz sim机器人SDF模型 [持续更新]

机器人SDF模型 linklink的一级pose材质 plugin话题信息通信键盘操作plugin Sensor传感器imu 不算教学&#xff0c;个人的记录 sdf的格式跟urdf有所不同&#xff0c;必须是完整的一个包括&#xff0c;比如< pose></ pose>这样前一个后一个&#xff0c;urdf中是有<…

【MySQL | 六、索引特性(进一步理解)】

目录 索引的理解索引的作用MySQL与磁盘的IOPage单个Page的分类多个Page的组织B树的特点 B树和B树的区别聚簇索引 VS 非聚簇索引聚簇索引的优缺点非聚簇索引的优缺点 创建索引常见索引分为&#xff1a;主键索引InnoDB主键索引的生成过程&#xff08;1&#xff09;初始化&#xf…

计算机网络高频(三)UDP基础

计算机网络高频(三)UDP基础 1.UDP的头部格式是什么样的?⭐ UDP 头部具有以下字段: 源端口(Source Port):16 位字段,表示发送方的端口号。目标端口(Destination Port):16 位字段,表示接收方的端口号。长度(Length):16 位字段,表示 UDP 数据报(包括头部和数据部…

【测试开发】OKR 小程序端黑盒测试报告

【测试报告】OKR 小程序端 项目名称版本号测试负责人测试完成日期联系方式OKR 小程序端4.0马铭胜2025-03-2515362558972 1、项目背景 1.1 OKR 用户端 在如今这个快节奏的时代中&#xff0c;个人和组织的成长往往依赖于清晰、明确且意义深远的目标。然而&#xff0c;如何设定…

部署高可用PostgreSQL14集群

目录 基础依赖包安装 consul配置 patroni配置 vip-manager配置 pgbouncer配置 haproxy配置 验证 本文将介绍如何使用Patroni、Consul、vip-manager、Pgbouncer、HaProxy组件来部署一个3节点的高可用、高吞吐、负载均衡的PostgresSQL集群&#xff08;14版本&#xff09;&…

Vue3中keep-alive缓存组件应用场景。

文章目录 一、KeepAlive是什么&#xff1f;二、基本使用1.例子2.keep-alive使用 三、其他属性3.1 包含/排除3.2 最大缓存实例数3.3 缓存实例的生命周期 总结 一、KeepAlive是什么&#xff1f; 是一个内置组件&#xff0c;它的功能是在多个组件间动态切换时缓存被移除的组件实例…

CosyVoice2在Windows系统上本地部署的详细步骤

CosyVoice2在Windows系统上本地部署的详细步骤&#xff1a; 下载源码并初始化&#xff1a; 确保你的设备上安装了Git。打开命令提示符&#xff08;cmd&#xff09;&#xff0c;执行以下命令来克隆仓库&#xff1a;git clone --recursive https://github.com/FunAudioLLM/CosyVo…

鸿蒙入门——ArkUI 跨页面数据同步和应用全局单例的UI状态存储AppStorage 小结(三)

文章大纲 引言一、AppStorage 应用全局的UI状态存储1、StorageProp和StorageLink装饰器建立联系2、StorageProp2.1、StorageProp使用规则2.2、StorageProp变量的传递/访问规则2.3、StorageProp支持的观察变化2.4、StorageProp 值初始化和更新 3、StorageLink3.1、StorageLink使…

海思烧录工具HITool电视盒子刷机详解

HiTool是华为开发的一款用于海思芯片设备的刷机和调试工具&#xff0c;可对搭载海思芯片的机顶盒、智能电视等设备进行固件烧录、参数配置等操作。以下为你详细介绍&#xff1a; 功能用途 固件烧录&#xff1a;这是HiTool最主要的功能之一。它能够将下载好的适配固件文件烧录到…