Linux-C/C++--深入探究文件 I/O (下)(文件共享、原子操作与竞争冒险、系统调用、截断文件)

经过上一章内容的学习,了解了 Linux 下空洞文件的概念;open 函数的 O_APPEND 和 O_TRUNC 标志;多次打开同一文件;复制文件描述符;等内容

本章将会接着探究文件IO,讨论如下主题内容。

 文件共享介绍;

 原子操作与竞争冒险;

 系统调用 fcntl() 和 ioctl() 介绍;

 截断文件;

一、文件共享

        什么是文件共享?所谓文件共享指的是同一个文件(譬如磁盘上的同一个文件,对应同一个 inode)被多个独立的读写体同时进行 IO 操作。多个独立的读写体大家可以将其简单地理解为对应于同一个文件的多个不同的文件描述符,譬如多次打开同一个文件所得到的多个不同的 fd,或使用 dup()(或 dup2)函数复制得到的多个不同的 fd 等。

        同时进行 IO 操作指的是一个读写体操作文件尚未调用 close 关闭的情况下,另一个读写体去操作文件,前面给大家编写的示例代码中就已经涉及到了文件共享的内容了,譬如 3.6 小节中编写的示例代码中,同一个文件对应两个不同的文件描述符 fd1 fd2,当使用 fd1 对文件进行写操作之后,并没有关闭 fd1,而此时使用 fd2 对文件再进行写操作,这其实就是一种文件共享。

        文件共享的意义有很多,多用于多进程或多线程编程环境中,譬如我们可以通过文件共享的方式来实现多个线程同时操作同一个大文件,以减少文件读写时间、提升效率。

        文件共享的核心是:如何制造出多个不同的文件描述符来指向同一个文件。其实方法在上面的内容中都已经给大家介绍过了,譬如多次调用 open 函数重复打开同一个文件得到多个不同的文件描述符、使用 dup()或 dup2()函数对文件描述符进行复制以得到多个不同的文件描述符。

常见的三种文件共享的实现方式

(1)同一个进程中多次调用 open 函数打开同一个文件,各数据结构之间的关系如下图所示:

这种情况非常简单,多次调用 open 函数打开同一个文件会得到多个不同的文件描述符,并且多个文件描述符对应多个不同的文件表,所有的文件表都索引到了同一个 inode 节点,也就是磁盘上的同一个文件。

(2)不同进程中分别使用 open 函数打开同一个文件,其数据结构关系图如下所示:

        进程 1 和进程 2 分别是运行在 Linux 系统上两个独立的进程(理解为两个独立的程序),在他们各自的程序中分别调用 open 函数打开同一个文件,进程 1 对应的文件描述符为 fd1,进程 2 对应的文件描述符为fd2,fd1 指向了进程 1 的文件表 1fd2 指向了进程 2 的文件表 2;各自的文件表都索引到了同一个 inode 节点,从而实现共享文件。

(3)同一个进程中通过 dupdup2)函数对文件描述符进行复制,其数据结构关系如下图所示:

        对于文件共享,存在着竞争冒险,这个是需要大家关注的,下一小节将会向大家介绍。除此之外,我们还需要关心的是文件共享时,不同的读写体之间是分别写还是接续写,这些细节问题大家都要搞清楚。

二、原子操作与竞争冒险

        Linux 是一个多任务、多进程操作系统,系统中往往运行着多个不同的进程、任务,多个不同的进程就有可能对同一个文件进行 IO 操作,此时该文件便是它们的共享资源,它们共同操作着同一份文件;操作系统级编程不同于大家以前接触的裸机编程,裸机程序中不存在进程、多任务这种概念,而在 Linux 系统中,我们必须要留意到多进程环境下可能会导致的竞争冒险。

1、竞争冒险简介

        本小节给大家竞争冒险这个概念,如果学习过 Linux 驱动开发的读者对这些概念应该并不陌生,也就意味着竞争冒险不但存在于 Linux 应用层、也存在于 Linux 内核驱动层。

假设有两个独立的进程 A 和进程 B 都对同一个文件进行追加写操作(也就是在文件末尾写入数据),每一个进程都调用了 open 函数打开了该文件,但未使用 O_APPEND 标志,此时,各数据结构之间的关系如图 3.8.2 所示。每个进程都有它自己的进程控制块 PCB,有自己的文件表(意味着有自己独立的读写位置偏移量),但是共享同一个 inode 节点(也就是对应同一个文件)。假定此时进程 A 处于运行状态,B 未处于等待运行状态,进程 A 调用了 lseek 函数,它将进程 A 的该文件当前位置偏移量设置为 1500 字节处(假设这里是文件末尾),刚好此时进程 A 的时间片耗尽,然后内核切换到了进程 B,进程 B 执行 lseek 函数,也将其对该文件的当前位置偏移量设置为 1500 个字节处(文件末尾)。然后进程 B 调用 write 函数,写入了 100 个字节数据,那么此时在进程 B 中,该文件的当前位置偏移量已经移动到了 1600 字节处。B 进程时间片耗尽,内核又切换到了进程 A,使进程 A 恢复运行,当进程 A 调用 write 函数时,是从进程 A 的该文件当前位置偏移量(1500 字节处)开始写入,此时文件 1500 字节处已经不再是文件末尾了,如果还从 1500字节处写入就会覆盖进程 B 刚才写入到该文件中的数据。其上述假设工作流程图如下图所示:

2、原子操作

        在上一章给大家介绍 open 函数的时候就提到过“原子操作”这个概念了,同样在 Linux 驱动编程中,也有这个概念,相信学习过 Linux 驱动编程开发的读者应该有印象。从上一小节给大家提到的示例中可知,上述的问题出在逻辑操作“先定位到文件末尾,然后再写”,它

使用了两个分开的函数调用,首先使用 lseek 函数将文件当前位置偏移量移动到文件末尾、然后在使用 write函数将数据写入到文件。既然知道了问题所在,那么解决办法就是将这两个操作步骤合并成一个原子操作,所谓原子操作,是有多步操作组成的一个操作,原子操作要么一步也不执行,一旦执行,必须要执行完所有步骤,不可能只执行所有步骤中的一个子集。

(1)O_APPEND 实现原子操作

        在上一小节给大家提到的示例中,进程 A 和进程 B 都对同一个文件进行追加写操作,导致进程 A 写入的数据覆盖了进程 B 写入的数据,解决办法就是将“先定位到文件末尾,然后写”这两个步骤组成一个原子操作即可,那如何使其变成一个原子操作呢?答案就是 O_APPEND 标志。前面已经给大家多次提到过了 O_APPEND 标志,但是并没有给大家介绍 O_APPEND 的一个非常重要

的作用,那就是实现原子操作。当 open 函数的 flags 参数中包含了 O_APPEND 标志,每次执行 write 写入操作时都会将文件当前写位置偏移量移动到文件末尾,然后再写入数据,这里“移动当前写位置偏移量到文件末尾、写入数据”这两个操作步骤就组成了一个原子操作,加入 O_APPEND 标志后,不管怎么写入数据都会是从文件末尾写,这样就不会导致出现“进程 A 写入的数据覆盖了进程 B 写入的数据”这种情况了。

(2)pread()pwrite()

        pread()和 pwrite()都是系统调用,与 read()write()函数的作用一样,用于读取和写入数据。区别在于,pread()和 pwrite()可用于实现原子操作,调用 pread 函数或 pwrite 函数可传入一个位置偏移量 offset 参数,用于指定文件当前读或写的位置偏移量,所以调用 pread 相当于调用 lseek 后再调用 read;同理,调用 pwrite相当于调用 lseek 后再调用 write。所以可知,使用 pread pwrite 函数不需要使用 lseek 来调整当前位置偏移量,并会将“移动当前位置偏移量、读或写”这两步操作组成一个原子操作。 pread、pwrite 函数原型如下所示(可通过"man 2 pread""man 2 pwrite"命令来查看):

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

首先调用这两个函数需要包含头文件<unistd.h>

函数参数和返回值含义如下:

fdbufcount 参数与 read write 函数意义相同。

offset表示当前需要进行读或写的位置偏移量。

返回值:返回值与 readwrite 函数返回值意义一样。

虽然 pread(或 pwrite)函数相当于 lseek pread(或 pwrite)函数的集合,但还是有下列区别:

调用 pread 函数时,无法中断其定位和读操作(也就是原子操作);

不更新文件表中的当前位置偏移量。

关于第二点我们可以编写一个简单地代码进行测试,测试代码如下所示:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{unsigned char buffer[100];int fd;int ret;/* 打开文件 test_file */fd = open("./test_file", O_RDWR);if (-1 == fd) {perror("open error");exit(-1);}/* 使用 pread 函数读取数据(从偏移文件头 1024 字节处开始读取) */ret = pread(fd, buffer, sizeof(buffer), 1024);if (-1 == ret) {perror("pread error");goto err;}/* 获取当前位置偏移量 */ret = lseek(fd, 0, SEEK_CUR);if (-1 == ret) {perror("lseek error");goto err;}printf("Current Offset: %d\n", ret);ret = 0;
err:/* 关闭文件 */close(fd);exit(ret);
}

        在当前目录下存在一个文件 test_file,上述代码中会打开 test_file 文件,然后直接使用 pread 函数读取100 个字节数据,从偏移文件头部 1024 字节处,读取完成之后再使用 lseek 函数获取到文件当前位置偏移量,并将其打印出来。假如 pread 函数会改变文件表中记录的当前位置偏移量,则打印出来的数据应该是1024 + 100 = 1124;如果不会改变文件表中记录的当前位置偏移量,则打印出来的数据应该是 0,接下来编译代码测试:

        从上图中可知,打印出来的数据为 0,正如前面所介绍那样,pread 函数确实不会改变文件表中记录的当前位置偏移量;同理,pwrite 函数也是如此,大家可以把 pread 换成 pwrite 函数再次进行测试,不出意外,打印出来的数据依然是 0

        如果把 pread 函数换成 read(或 write)函数,那么打印出来的数据就是 100 了,因为读取了 100 个字节数据,相应的当前位置偏移量会向后移动 100 个字节。

三、fcntl ioctl

1、fcntl 函数

        cntl()函数可以对一个已经打开的文件描述符执行一系列控制操作,譬如复制一个文件描述符(与 dup、dup2 作用相同)、获取/设置文件描述符标志、获取/设置文件状态标志等,类似于一个多功能文件描述符管理工具箱。fcntl()函数原型如下所示(可通过"man 2 fcntl"命令查看)

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ )

函数参数和返回值含义如下:

fd文件描述符。

cmd操作命令。此参数表示我们将要对 fd 进行什么操作,cmd 参数支持很多操作命令,大家可以打

man 手册查看到这些操作命令的详细介绍,这些命令都是以 F_XXX 开头的,譬如 F_DUPFDF_GETFD、F_SETFD 等,不同的 cmd 具有不同的作用,cmd 操作命令大致可以分为以下 5 种功能:

复制文件描述符(cmd=F_DUPFD cmd=F_DUPFD_CLOEXEC);

获取/设置文件描述符标志(cmd=F_GETFD cmd=F_SETFD);

获取/设置文件状态标志(cmd=F_GETFL cmd=F_SETFL);

获取/设置异步 IO 所有权(cmd=F_GETOWN cmd=F_SETOWN);

获取/设置记录锁(cmd=F_GETLK cmd=F_SETLK);

这里列举出来,并不需要全部学会每一个 cmd 的作用,因为有些内容并没有给大家提及到,譬如什么异步 IO、锁之类的概念,在后面的学习过程中,当学习到相关知识内容的时候再给大家介绍。

fcntl 函数是一个可变参函数,第三个参数需要根据不同的 cmd 来传入对应的实参,配合 cmd 来使

用。

返回值:执行失败情况下,返回-1,并且会设置 errno;执行成功的情况下,其返回值与 cmd(操作命令)有关,譬如 cmd=F_DUPFD(复制文件描述符)将返回一个新的文件描述符、cmd=F_GETFD(获取文件描述符标志)将返回文件描述符标志、cmd=F_GETFL(获取文件状态标志)将返回文件状态标志等。

fcntl 使用示例

(1)复制文件描述符

        前面给大家介绍了 dup dup2,用于复制文件描述符,除此之外,我们还可以通过 fcntl 函数复制文件描 述 符 , 可 用 的 cmd 包 括 F_DUPFD F_DUPFD_CLOEXEC , 这 里 就 只 介 绍 F_DUPFD ,F_DUPFD_CLOEXEC 暂时先不讲。

        当 cmd=F_DUPFD 时,它的作用会根据 fd 复制出一个新的文件描述符,此时需要传入第三个参数,第三个参数用于指出新复制出的文件描述符是一个大于或等于该参数的可用文件描述符(没有使用的文件描述符);如果第三个参数等于一个已经存在的文件描述符,则取一个大于该参数的可用文件描述符。

测试代码如下所示:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{int fd1, fd2;int ret;/* 打开文件 test_file */fd1 = open("./test_file", O_RDONLY);if (-1 == fd1) {perror("open error");exit(-1);}/* 使用 fcntl 函数复制一个文件描述符 */fd2 = fcntl(fd1, F_DUPFD, 0);if (-1 == fd2) {perror("fcntl error");ret = -1;goto err;}printf("fd1: %d\nfd2: %d\n", fd1, fd2);ret = 0;close(fd2);
err:/* 关闭文件 */close(fd1);exit(ret);
}

        在当前目录下存在 test_file 文件,上述代码会打开此文件,得到文件描述符 fd1,之后再使用 fcntl 函数复制 fd1 得到新的文件描述符 fd2,并将 fd1 fd2 打印出来,接下来编译运行:

        可知复制得到的文件描述符是 7,因为在执行 fcntl 函数时,传入的第三个参数是 0,也就时指定复制得到的新文件描述符必须要大于或等于 0,但是因为 0~6 都已经被占用了,所以分配得到的 fd 就是 7;如果传入的第三个参数是 100,那么 fd2 就会等于 100,大家可以自己动手测试。

(2)获取/设置文件状态标志

        cmd=F_GETFL 可用于获取文件状态标志,cmd=F_SETFL 可用于设置文件状态标志。cmd=F_GETFL 时不需要传入第三个参数,返回值成功表示获取到的文件状态标志;cmd=F_SETFL 时,需要传入第三个参数,此参数表示需要设置的文件状态标志。

        这些标志指的就是我们在调用 open 函数时传入的 flags 标志,可以指定一个或多个(通过位或 | 运算符组合),但是文件权限标志(O_RDONLYO_WRONLYO_RDWR)以及文件创建标志(O_CREAT、O_EXCL、O_NOCTTYO_TRUNC)不能被设置、会被忽略;在 Linux 系统中,只有 O_APPENDO_ASYNC、O_DIRECT、O_NOATIME 以及 O_NONBLOCK 这些标志可以被修改,这里面有些标志并没有给大家介绍过,后面我们在用到的时候再给大家介绍。所以对于一个已经打开的文件描述符,可以通过这种方式添加或移除标志。

测试代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{int fd;int ret;int flag;/* 打开文件 test_file */fd = open("./test_file", O_RDWR);if (-1 == fd) {perror("open error");exit(-1);}/* 获取文件状态标志 */flag = fcntl(fd, F_GETFL);if (-1 == flag) {perror("fcntl F_GETFL error");ret = -1;goto err;}printf("flags: 0x%x\n", flag);/* 设置文件状态标志,添加 O_APPEND 标志 */ret = fcntl(fd, F_SETFL, flag | O_APPEND);if (-1 == ret) {perror("fcntl F_SETFL error");goto err;}ret = 0;
err:/* 关闭文件 */close(fd);exit(ret);
}

        以上给大家介绍了 fcntl 函数的两种用法,除了这两种用法之外,还有其它多种不同的用法,这里暂时先不介绍了,后面学习到相应知识点的时候再给大家讲解。

2、ioctl 函数

        ioctl()可以认为是一个文件 IO 操作的杂物箱,可以处理的事情非常杂、不统一,一般用于操作特殊文件或硬件外设,此函数将会在进阶篇中使用到,譬如可以通过 ioctl 获取 LCD 相关信息等,本小节只是给大家引出这个系统调用,暂时不会用到。此函数原型如下所示(可通过"man 2 ioctl"命令查看):

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

函数参数和返回值含义如下:

fd文件描述符。

request此参数与具体要操作的对象有关,没有统一值,表示向文件描述符请求相应的操作;后面用到的时候再给大家介绍。

...此函数是一个可变参函数,第三个参数需要根据 request 参数来决定,配合 request 来使用。

返回值:成功返回 0,失败返回-1

四、截断文件

        使用系统调用 truncate()ftruncate()可将普通文件截断为指定字节长度,其函数原型如下所示:

#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

        这两个函数的区别在于:ftruncate()使用文件描述符 fd 来指定目标文件,而 truncate()则直接使用文件路径 path 来指定目标文件,其功能一样。

        这两个函数都可以对文件进行截断操作,将文件截断为参数 length 指定的字节长度,什么是截断?如果文件目前的大小大于参数 length 所指定的大小,则多余的数据将被丢失,类似于多余的部分被“砍”掉了;如果文件目前的大小小于参数 length 所指定的大小,则将其进行扩展,对扩展部分进行读取将得到空字节"\0"

        使用 ftruncate()函数进行文件截断操作之前,必须调用 open()函数打开该文件得到文件描述符,并且必须要具有可写权限,也就是调用 open()打开文件时需要指定 O_WRONLY O_RDWR

        调用这两个函数并不会导致文件读写位置偏移量发生改变,所以截断之后一般需要重新设置文件当前的读写位置偏移量,以免由于之前所指向的位置已经不存在而发生错误(譬如文件长度变短了,文件当前所指向的读写位置已不存在)。

        调用成功返回 0,失败将返回-1,并设置 errno 以指示错误原因。

使用示例

        示例代码 3.11.1 演示了文件的截断操作,分别使用 ftruncate()truncate()将当前目录下的文件 file1 截断为长度 0、将文件 file2 截断为长度 1024 个字节。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{int fd;/* 打开 file1 文件 */if (0 > (fd = open("./file1", O_RDWR))) {perror("open error");exit(-1);}/* 使用 ftruncate 将 file1 文件截断为长度 0 字节 */if (0 > ftruncate(fd, 0)) {perror("ftruncate error");exit(-1);}/* 使用 truncate 将 file2 文件截断为长度 1024 字节 */if (0 > truncate("./file2", 1024)) {perror("truncate error");exit(-1);}/* 关闭 file1 退出程序 */close(fd);exit(0);
}

        上述代码中,首先使用 open()函数打开文件 file1,得到文件描述符 fd,接着使用 ftruncate()系统调用将 文件截断为 0 长度,传入 file1 文件对应的文件描述符;接着调用 truncate()系统调用将文件 file2 截断为 1024字节长度,传入 file2 文件的相对路径。

        接下来进行测试,在当前目录下准备两个文件 file1 file2,如下所示:

可以看到 file1 file2 文件此时均为 592 字节大小,接下来运行测试代码:

        程序运行之后,file1 文件大小变成了 0,而 file2 文件大小变成了 1024 字节,与测试代码想要实现的功能是一致的。

本小节内容到此结束。感谢。。。

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

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

相关文章

npm run dev 时直接打开Chrome浏览器

package.json 修改下配置 "scripts": {"dev": "vite --open chrome.exe",......}, "dev": "vite" 修改为 "dev": "vite --open chrome.exe" 这样方便一点&#xff0c;省得每次去点调试窗口的链接

微软预测 AI 2025,AI Agents 重塑工作形式

1月初&#xff0c;微软在官网发布了2025年6大AI预测&#xff0c;分别是&#xff1a;AI模型将变得更加强大和有用、AI Agents将彻底改变工作方式、AI伴侣将支持日常生活、AI资源的利用将更高效、测试与定制是开发AI的关键以及AI将加速科学研究突破。 值得一提的是&#xff0c;微…

《从入门到精通:蓝桥杯编程大赛知识点全攻略》(五)-数的三次方根、机器人跳跃问题、四平方和

本博客将详细探讨如何通过二分查找算法来解决这几个经典问题。通过几个实际的例子&#xff0c;我们将展示如何在这些问题中灵活应用二分查找&#xff0c;优化计算过程&#xff0c;并在面对大数据量时保持高效性。 目录 前言 数的三次方根 算法思路 代码如下 机器人跳跃问题…

微服务知识——4大主流微服务架构方案

文章目录 1、微服务聚合模式2、微服务共享模式3、微服务代理模式4、微服务异步消息模式 微服务是大型架构的必经之路&#xff0c;也是大厂重点考察对象&#xff0c;下面我就重点详解4大主流微服务架构方案。 1、微服务聚合模式 微服务聚合设计模式&#xff0c;解决了如何从多个…

麒麟操作系统服务架构保姆级教程(十三)tomcat环境安装以及LNMT架构

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 之前咱们学习了LNMP架构&#xff0c;但是PHP对于技术来说确实是老掉牙了&#xff0c;PHP的市场占有量越来越少了&#xff0c;我认识一个10年的PHP开发工程师&#xff0c;十年工资从15k到今天的6k&am…

游戏AI,让AI 玩游戏有什么作用?

让 AI 玩游戏这件事远比我们想象的要早得多。追溯到 1948 年&#xff0c;图灵和同事钱伯恩共同设计了国际象棋程序 Turochamp。之所以设计这么个程序&#xff0c;图灵是想说明&#xff0c;机器理论上能模拟人脑能做的任何事情&#xff0c;包括下棋这样复杂的智力活动。 可惜的是…

Golang的文件处理优化策略

Golang的文件处理优化策略 一、Golang的文件处理优化策略概述 是一门效率高、易于编程的编程语言&#xff0c;它的文件处理能力也非常强大。 在实际开发中&#xff0c;需要注意一些优化策略&#xff0c;以提高文件处理的效率和性能。 本文将介绍Golang中的文件处理优化策略&…

数据结构学习记录-队列

队列的基本概念 1、队列是操作受限的线性表 2、队头&#xff1a;允许删除的一端 3、队尾&#xff1a;允许插入的一端 4、空队列&#xff1a;不含任何元素的空表 5、特点&#xff1a;先进先出、FIFO 6、应用场景&#xff1a; 栈&#xff1a;解决括号匹配&#xff1b;逆波…

java知识框架

面试1 基础篇 如何理解OOP面向对象编程&#xff1f; 对现有事物进行抽象&#xff0c;具有继承、封装、多态的特征。 继承&#xff1a;从已有的类也就是父类进行继承信息。 封装&#xff1a;对数据和数据操作的方法绑定起来&#xff0c;通过方法进行访问或者操作数据。 多态…

JDBC实验测试

一、语言和环境 实现语言&#xff1a;Java。 环境要求&#xff1a;IDEA2023.3、JDK 17 、MySQL8.0、Navicat 16 for MySQL。 二、技术要求 该系统采用 SWING 技术配合 JDBC 使用 JAVA 编程语言完成桌面应用开发。 三、功能要求 某电商公司为了方便客服查看用户的订单信…

小程序获取微信运动步数

1、用户点击按钮&#xff0c;在小程序中触发getuserinfo方法&#xff0c;获取用户信息 <scroll-view class"scrollarea" scroll-y type"list"><view class"container"><button bind:tap"getLogin">获取</button&…

macOS 安装JDK17

文章目录 前言介绍新特性下载安装1.下载完成后打开downloads 双击进行安装2.配置环境变量3.测试快速切换JDK 小结 前言 近期找开源软件&#xff0c;发现很多都已经使用JDK17springboot3 了&#xff0c;之前的JDK8已经被替换下场&#xff0c;所以今天就在本机安装了JDK17&#…

Windows电脑桌面记录日程安排的提醒软件

在快节奏的现代生活中&#xff0c;工作效率成为了衡量个人能力的重要标准之一。然而&#xff0c;日常办公中常常会遇到各种琐事和任务&#xff0c;如果没有合理安排日程&#xff0c;很容易陷入混乱&#xff0c;导致效率低下。因此&#xff0c;做好日程安排对于日常工作至关重要…

MFC 使用 32位带Alpha通道的位图

最近需要做一个MFC界面上的图片,众所周知,MFC 好像只支持 bmp 格式的! 先看我的原始24位图片,RGB 三个颜色各占8位 (256色), 所以是24位。 如果放到MFC界面上,是这个很丑的效果 它是一个正方形图片,周围的白色可以看见。 解下来,进入今天的主题: 32位带 Alpha 通…

Ubuntu22部署MySQL5.7详细教程

Ubuntu22部署MySQL5.7详细教程 一、下载MySQL安装包二、安装MySQL三、启动MySQL 检查状态登录MySQL 四、开启远程访问功能 1、允许其他主机通过root访问数据库2、修改配置文件&#xff0c;允许其他IP通过自定义端口访问 五、使用Navicat连接数据库 默认情况下&#xff0c;Ubun…

大模型 | AI驱动的数据分析:利用自然语言实现数据查询到可视化呈现

【本文作者&#xff1a;擎创科技资深产品专家 布博士】 在当今AI驱动的时代&#xff0c;数据分析已成为各行各业不可或缺的能力。然而&#xff0c;传统的数据分析流程通常需要掌握SQL、数据处理和可视化等多项专业技能&#xff0c;这对非技术背景的业务人员来说是一个不小的挑…

C++ ——— 模拟实现 vector 类

目录 vector 类的框架 无参数的构造函数 析构函数 获取有效数据个数 获取容量 重载 [] 运算符 可读可写版本 只可读版本 扩容 尾插 实现迭代器 可读可写版本 只可读版本 自定义设置size长度和内容 在任意位置插入 删除任意位置的数据 赋值重载 vector 类的框…

Git处理冲突详解

文章目录 Git处理冲突详解一、引言二、冲突产生的原因三、解决冲突的步骤1. 手动解决冲突1.1 查看冲突文件1.2 编辑冲突文件1.3 提交解决冲突 2. 使用合并工具解决冲突 四、使用示例五、总结 Git处理冲突详解 一、引言 在团队协作开发中&#xff0c;Git冲突是不可避免的。当多…

GS论文阅读--GeoTexDensifier

前言 本文是一个关于高斯致密化策略对高斯地图进行优化&#xff0c;他主要关注了几何结构和纹理信息。我最近对于高斯点的分布比较感兴趣&#xff0c;因为高斯点的分布决定了之后重建质量的好坏&#xff0c;初始化高斯很重要&#xff0c;但之后的维护需要致密化与修建策略&…

【云原生布道系列】第三篇:“软”饭“硬”吃的计算

1 虚拟化技术定义 首先援引一段《虚拟化技术发展编年史》中针对虚拟化技术的定义&#xff1a;在计算机科学中&#xff0c;虚拟化技术&#xff08;Virtualization&#xff09;是一种资源管理&#xff08;优化&#xff09;技术&#xff0c;将计算机的各种物理资源&#xff08;例如…