学习系统编程No.13【文件系统】

news2024/11/15 7:47:22

引言:

北京时间:2023/3/31/7:48,该篇博客在两天前本就应该产出,但是摆烂谁拦得住,所以呜呜呜!本以为欠的钱也要快还完了,没想到啊,越欠越多,烦人!但是,欠的都是小钱,不像以前,欠的是大钱,所以也不怎么打紧,慢慢还啦!这周的榜估计是保不住了,具体还要看看周末摆烂不摆烂,谁让腾讯有钱,可以看《阿凡达之水之道》,真的很想看,所以我已经料想都了自己的周末是怎么过的了,难过!但是不怕,伤心和开心就和机遇和挑战一样,是并存的。比如,我昨天非常的开心,幸福来得太突然,因为有人愿意帮我买溜溜球,开心激动坏了,可惜我不怎么好意思收,哈哈哈!但是我依然很开心,发现生活真的不只是学习,更重要的还有摆烂!!!哈哈哈!可能在我看来摆烂就是这么的朴实无华吧!今天就让我们来回顾一下缓冲区的知识和贯穿体系的过程,当然最重要的是文件系统知识!

在这里插入图片描述

自主封装系统调用接口

了解了上篇博客的知识,我们对写代码具体是在干什么有了一个更加深入的理解,对平时使用的无论是C语言代码还是C++代码都有了进一步理解,知道了,语言只不过是对系统调用接口的一个封装,目的只是为了可以提高编程效率而已,所以接下来,我们就自己也封装一个调用文件操作的系统调用,用来加深我们对FILE结构体和系统调用接口的理解,如下代码:

我们自主实现的就是一个对系统文件操作接口的封装,我们分成4个接口实现,打开(fopen)、关闭(fclose)、写入(fwrite)、刷新(fflush),具体每个接口的实现过程如下:

FILE对象结构体如下:
在这里插入图片描述

打开文件

1.识别标志位 2.尝试打开文 3.构建FILE结构体 4.初始化FILE对象

在这里插入图片描述

关闭文件

5.冲刷缓冲区中的数据 6.关闭文件 7.释放堆空间(FILE结构体) 8.指针置空

刷新缓冲区

9.判断缓冲区类型 10.按照缓冲区类型,根据原理刷新数据

在这里插入图片描述

写入数据

11.判断缓冲区是否满了,是否可以写入 12.根据缓冲区剩余情况,进行数据拷贝 13.更新计数器(更新数据大小) 14.刷新缓冲区中的数据

在这里插入图片描述

上述就是我们对系统文件操作接口的一个类似于C语言中文件操作接口的封装,当然我们只是简易封装,目的只是为了深入了解文件操作的原理而已,具体代码在该文最后

回顾缓冲区

上篇博客,我们讲缓冲区的知识,使用文字描述的形式进行了一个深刻的了解,知道了什么是缓冲区的基本刷新策略等知识,但是要注意,我们使用的缓冲区都是属于用户级缓冲区,并不是真正意义上系统中的缓冲区,所以在本质上看来,用户级的缓冲区就只是一个buffer数组而已,并且知道,在用户层设置这个缓冲区的意义,目的就是为了可以减少调用系统调用的次数,因为系统调用是需要时间的(存在效率问题),所以为了提高效率,在用户层,就需要去封装一个缓冲区,用于存储需要调用系统接口去写入到外设中的数据,例:寄送快递,快递站肯定是一批一批的寄,不可能一件一件的寄 ,同理缓冲区的作用也就是如此,还要注意,数据之间的传递使用的是 拷贝 的形式,具体如下图:

在这里插入图片描述
通过上图,我们可以发现,上述知识在基础IO中是属于用户层级别缓冲区的理解和使用,所以根据上图操作系统中的知识,此时我们来看一看系统中的缓冲区吧!并且对于操作系统中的文件对象、文件描述符表和被打开文件与进程之间关系的知识,在之前的博客中都进行了详细的讲解,这里就不多做了解了,重点搞定系统缓冲区,如上图,我们使用C库中的接口fprintf写入一个数据,此时经过两次拷贝之后来到了系统缓冲区中,所以此时身为缓冲区,无论是用户层面还是系统层面,在我们看来本质只是一个用来临时存储数据和拥有自己的刷新策略的数组而已(当然系统缓冲区可能是一块物理内存),并且目的还是一样的,就只是想要提高效率,因为从系统缓冲区将数据拷贝到外设,本质上是需要使用文件对象中的函数指针去调用外设驱动程序对应的读写函数的,同理,调用驱动程序的读写接口,需要时间,应该避免频繁调用,所以系统缓冲区的作用同理用户缓冲区。并且知道,系统缓冲区的刷新策略和用户缓冲区的刷新策略是不同的,系统缓冲区的刷新策略更加的复杂,需要根据内存的使用情况和CPU来提供相应的刷新策略,例如:平时我们害怕电脑在没把数据保存之前断电,此时这种情况,就是属于数据全部都存储在了系统缓冲区中,还没有被拷贝到外设中,解决方法就是使用Ctrl+S的方式,所以此时的Ctrl+S的方法就等于是一种将数据刷新到外设的手段,当然手段还有很多,例如,使用系统接口,强制刷新系统缓冲区中的数据,如下:fsync(fp->fd)

深入理解printf和scanf

明白了上述的知识之后,此时我们就以我们在C语言中经常使用的printfscanf来举例,看一下这两个用户接口到底是如何工作的呢?理解如果工作之前,我们首先要明白一点,就是无论是printf还是scanf,它的结尾都是 f ,所以要注意的是,它们本质上都是格式化接口,都会对输入和输出的数据进行格式化 ,明白了这点之后,接下来,我们就来看看输出接口的工作原理吧!

printf
假设此时有一个变量int a = 123456;此时在我们看来,这个123456代表的就是一个整形十二万三千四百五十六,而在编译器看来,此时它就是一个字符串,所以当我们使用 printf("%d\n",a);它的第一步工作是获取对应的变量,第二步是定义缓冲区,然后将变量对应的数据进行格式化(格式化成字符串形式),第三步是将字符串拷贝到stdout->buffe中(调用系统接口write完成),第四步就是结合刷新策略刷新缓冲区,所以综上考虑到printf接口的使用模式,此时我们就知道了,平常我们使用printf打印一条语句,例:printf("hello world\n");这个本来才是printf的真正使用,像上述的打印一个整形,不过只是因为printf具有格式化的能力,所以也可以打印而已,具体原理:进行字符串的遍历,如果遇到了 % 就进行格式化,格式化的具体类型,就看%后面的数据,如果是d此时就将该字符串格式化成整形,同理……

总:printf的本质就是用来打印字符串的,只是因为其具有将字符串格式化的能力,所以可以进行整形等类型数据的打印而已

scanf
第一步,直接进行读取,调用系统接口read,将数据读取到stdin->buffer(标准输入缓冲区中),第二步对缓冲区中的数据(buffer数组)进行格式化,第三步,将标准输入文件中的数据,写入到对应的变量中
,例:int a,b; scanf("%d %d",&a,&b); 调用系统接口read,read(0,stdin->buffer,num);将对应的数据写到标准输入文件的缓冲区中,此时由于标准输入文件就是我们的键盘,所以此时我们就可以从键盘中输入数据,并且将我们输入的数据存储到标准输入文件对应的缓冲区中,所以可以明白,我们从键盘中输入的数据本质上也是以字符串的形式进行的输入,所以此时标准输入缓冲区中,就是以字符串的形式存放,所以此时同理去遍历标准输入缓冲区中的字符串,遇到空格就切割成两个子串,然后使用atoi接口,将字符串类型转换成整形

文件系统

搞定了上述知识和之前几篇博客的知识,此时我们就明白了进程和被打开文件之间的关系,以及一些列的有关知识,所以此时我们就将操作系统中的文件给搞明白了,所以接下来我们就去看看不在操作系统中的文件(在外设中的文件),如何存储和供给调用 ,如下:

什么是磁盘文件

首先,我们要明白,一个文件如果不在内存之中,此时应该在哪里呢?了解到,一个文件如果不在内存之中,那么此时它只能是在磁盘等外设中静静存储着,只有当它被打开时,它才会被加载到内存(冯若依曼规定),并且因为之前我们学习的是被打开文件和进程之间的关系,也就是操作系统中的文件知识,所以此时我们就来了解一下操作系统以外的文件,也就是没有被打开的文件,目的:理解文件的动态特征和静态特征,从静态文件和动态文件两个方面看文件的本质,如下,以一个场景开始话题:

场景:一个快递没有被拿回家,还在菜鸟驿站中存储着,为什么要存储在菜鸟驿站呢?主要就是为了解决:快速定位,便于整理(快速读取和写入)

所以类似于上述的场景,我们在管理磁盘文件时,也应该模拟该模式,先定位文件,其次才是读取或者写入文件

了解磁盘的物理结构

磁盘详解

在以前,我们的电脑存储数据用的都是机械磁盘,但是随着科技的进步,机械磁盘逐渐被固态硬盘(SSD)替代,如下:就是我们现在电脑中的固态硬盘图片和以前电脑中的磁盘
在这里插入图片描述

磁盘逐渐被固态硬盘取代的原因就是因为,固态硬盘的读写速度和数据存储安全性方面相比于磁盘是更加优异的,但今天我们的重点不是学习固态硬盘,而是学习磁盘,所以让我们以磁盘为出发点,深入了解一下磁盘吧!

磁盘内部图片如下:
在这里插入图片描述
如上如所示,就是磁盘的物理结构,磁盘盘面类似于很久以前见过的光盘(CD),但是这两者是有区别的,磁盘不仅支持读数据,它也支持写数据,而CD则不支持写数据,只支持读数据,并且要知道磁盘在读写数据的过程,具体原理是因为磁生电和电生磁的概念,这里我们就不多做了解,感兴趣的同学可以参看上述的磁盘详解链接,本质:正负、0 1原理,万物都可以用0 1区分,所以数据也是同理,而我们的正负电,就是一个0 1最好的表征方式

磁盘具体物理存储结构

本来有关硬件层面的知识和我们都没有太大的关系的,例如什么切割磁感线等知识,但是为什么我们还要研究磁盘的存储结构呢?其实目的就是:尝试在硬件上,理解数据的一次读和写,也就是尝试着理解数据在硬件上存储的本质就行了,具体的存储原理我们不关心,但是涉及到我们需要调用外设的驱动程序存储数据的时候,我们就应该了解,驱动程序具体是如何将数据写入或者读取,所以我们就可以以磁盘为例,来看看外设都是如何进行读写操作的,所以凭借上述的知识,我们大致可以知道驱动程序想要将数据写入外设,或者想要从外设中读取数据,都是通过0 1的形式来工作的,因为以磁盘为例,磁盘只能通过正负,0 1来区分数据,所以可以猜想,驱动程序想要交互,也必须以0 1进行,如下图:

一片磁盘上的物理存储结构:
在这里插入图片描述
从该图我们可以看出 ,一个扇面可以被分成多个扇区,并且注意:一个扇区是512个字节(4kb),并且可以知道,每个扇区都有不同的磁道,所以可以看出,同半径的所有扇区,它们是处于扇面的同一磁道,所以此时磁盘以这个结构涉及,磁头就可以很好的通过磁道的不同和扇区的不同,进行不同扇区,不同磁道上的数据的读写所以如下图: 可以发现,磁头是有固定类型和移动类型的,并且磁盘是通过许多的扇面构成,不仅仅只有一个扇面,

整个磁盘具体物理存储结构:
在这里插入图片描述
注意:不同的磁盘的设计策略是不同的,例如上述磁盘,是通过磁头的移动来进行不同扇区中数据的读写的,但是有的磁盘磁头是被固定的,只能通过不同扇区上的不同磁头来读写数据,如下图:
在这里插入图片描述

所以定位磁盘上的数数据,使用的就是CHS定位法:第一步确定哪一个扇面(通过磁头解决),第二步确定磁道,第三步确定磁道属于的扇区

明白了上述磁头是如何定位一个磁盘上的数据时,此时我们可以推测,如果操作系统可以通过磁头获取的这个数据的地址从而来获取磁盘上的数据,那么操作系统就可以间接访问磁盘上的任意一个位置的数据了,那么我们的这个推测是正确的吗?

显然,我们这个推测是不正确的,首先,我们电脑中的磁盘是可以别替换的,所以如果磁盘被替换,那么操作系统接收的磁盘中的数据位置就会发生很大的改变,操作系统是不允许发生很大的改变的,所以想要获取磁盘上数据的位置,操作系统就需要和磁盘进行一个解耦工作,避免因为磁盘发生改变,而导致操作系统发生改变,而是让解耦工作去适应不同的磁盘,操作系统通过获取解耦之后的数据位置来访问磁盘数据,这样更加安全和可靠,所以总的来说,操作系统想要访问磁盘上的数据就需要通过解耦的形式构建一个自己的新地址

磁盘的抽象理解

如下图,我们可以通过磁带来抽象理解磁盘:
在这里插入图片描述
所以根据上图,我们可以将物理磁盘抽象成线性,将磁道抽象成一段相同的磁带,所以我们就可以将磁盘上的磁道给抽象成一个一个的数组(数组之间是以扇区分割),最终我们就将磁盘以磁道的形式抽象成了一个一个的数组,所以此时我们就可以通过数组下标的形式去访问每个磁道上对应扇区的数据了,具体原理,通过扇区的不同来区分出不同的数组,例,1号扇区表示的就是对应磁道上的第一个数组,2号扇区表示的就是对应磁道上的第二个数组,如下图所示:
在这里插入图片描述
所以如果按照上述的理论,我们将磁盘给抽象成了这个样子,那么此时操作系统就可以通过以4kb(512字节)为单位对磁盘中的数据块进行读写操作,所以在操作系统的角度下,它并不关心扇区!操作系统想要找到一个数据块(也就是一个扇区),只要知道起始扇区,然后通过偏移量的相加,就可以很好的进行下一个或者几个数据块位置的获取(LBA.逻辑块地址),获取到了该数据块的地址之后,本质又因为该数据块是一个数组,所以此时我们就可以通过数组下标的形式对这个数据块进行读写;
所以计算机的常规访问形式,起始地址+偏移量(数据类型),从而进行地址访问,获取第一个地址,所以我们只要知道第一个扇区的起始位置,然后+4kb(块数据类型),此时我们就可以获取到一整个块类型的数据(类比获得一个int类型的数据),并且块的地址,本质就是一个数组下标,此时就可以用数组下标的方式,定位任何一个下标,然后访问该下标上的数据

明白了上述知识之后,我们就知道了,磁盘是通过CHS的方法定位数据,而操作系统是通过LBA的方式定位数据,所以磁盘和操作系统是存在一定的耦合关系的,所以如果想要让操作系统获取到磁盘上的数据,此时就需要进行一个解耦操作,而这个解耦工作,本质上就是通过加减乘除,一定的运算来实现的,这里不多做讲解,感兴趣的同学可以参考该链接:LBA和CHS之间的转换

总:通过抽象的方式,此时操作系统对磁盘的管理,通过先描述,再组织的方式(struct block),就变成了对一个数组的管理!

正式学习文件系统

有了上述的知识,此时我们就可以进入文件系统的学习了,文件系统的本质就是操作系统对磁盘上的数据进行管理的方式,通过日常生活的理解,解决一个大问题,我们都是将问题划分成一个一个的小问题,管理一个大数据,我们都是通过划分成一个一个的小数据,所以此时操作系统想要管理磁盘上的文件不仅仅是对数组进行管理那么简单,因为磁盘上的数据过于庞大了,所以此时我们以日常生活中为例,通过分治的方式让操作系统完成对磁盘上数据的管理。

所以此时通过分治的概念,我们就可以把一整个磁盘先分成若干份,如下图所示,分成C盘和D盘:
在这里插入图片描述
然后在C盘和D盘内部进行进一步的分区,将磁盘划分成一个一个的小区间,这样,我们只要把一个小区间管理好之后,其它的小区间就依葫芦画瓢进行管理,这样,我们就可以将一个庞大的磁盘数据,给有序的管理好,这样操作系统在读写磁盘的时候,就可以凭借很高的效率完成

如何管理好一个小区间

所以明白了上述知识,此时就可以知道,我们还是通过先描述再组织的方式对一个一个分区进行管理,谈到先描述再组织,此时就又涉及到了结构体,所以本质上,我们还是通过结构体的方式对磁盘进行管理,例如:分成几个区,我们可以如下这样表示,然后再将每个区进行分组

struct disk
{
   struct part[4];
   .....
}

每个组如何管理,我们可以这样表示:

struct part
{
    struct part group[100];
    int lba_start;
    int lba_end;
    .....
}

如上,我们就可以通过控制每个区间的每个组的开始位置和结束位置来进行对每一个区间的管理,如下图,就是一个组中的详细管理模式:
在这里插入图片描述

注意:文件系统每次访问外设都是4kb,所以无论你写入多少数据,操作系统每次都是开辟4kb空间使用

在这里插入图片描述

总结:摆烂归摆烂,只要不放弃就行,哈哈哈!!!

#include<stdio.h>
#include<string.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<windows.h>
#include<assert.h>

#define MAX 1024

//设置缓冲区
#define BUFF_NOME 0x1//无缓冲
#define BUFF_LINE 0x2//行缓冲
#define BUFF_ALL 0x4//标志位的运用而已

typedef struct My_FILE
{
	int fd;//文件描述符
	char outputbuffer[MAX];//缓冲区
	int flags;//刷新方式
	int current;//表示的是就是数组中的size(代表下次应该从那个位置开始写入数据)

}My_FILE;
 
#define _CRT_SECURE_NO_WARNINGS

My_FILE* my_fopen(const char* path, const char* mode)
{
	// 1.识别标志位(这块可以回去把标志位的知识复习一下)
	int flag = 0;
	if (strcmp(mode, "r") == 0)
	{
		flag |= O_RDONLY;//读
	}
	else if (strcmp(mode, "w") == 0)
	{
		flag |= (O_WRONLY | O_CREAT | O_TRUNC);//写、创建、清空
	}
	else if (strcmp(mode, "a") == 0)
	{
		flag |= (O_WRONLY | O_CREAT | O_APPEND);//写、创建、追加
	}
	else
	{
		printf("其它方式我们暂不实现\n");
	}
	//2. 尝试打开文件

	int fd = 0;
	if (flag & O_CREAT)//此时flag因为通过上述的判断,如果是w或者a,那么肯定会满足该条件语句,所以此时就需要满足w和a,去创建一个文件(具体方法是调用系统调用接口)
	{
		fd = _open(path, flag, 0666);
	}
	else
	{
		fd = _open(path, flag);
	}
	if (fd < 0)
	{
		return NULL;
	}

	//3. 给用户返回My_FILE对象,需要先进行构建
	My_FILE* mf = (My_FILE*)malloc(sizeof(My_FILE));
	if (mf == NULL)
	{
		_close(fd);
		return NULL;
	}
	//4.初始化My_FILE对象
	mf->fd = fd;
	mf->flags = 0;
	mf->flags |= BUFF_LINE;//此时这个表示的只是缓冲区的刷新方式
	//mf->outputbuffer[0] = 0;//这种写法比较挫
	memset(mf->outputbuffer, '\0', sizeof(mf->outputbuffer));
}
int my_fflush(My_FILE* fp)
{
	assert(fp);
	_write(fp->fd, fp->outputbuffer, fp->current);//向特定的文件中(fd)写入特定的数据(缓冲区buff中)并且还有写入多少数据(current此时表示的就是数据的大小)
	fp->current = 0;//将缓冲区的数据刷新出去之后,无论是行缓冲,还是全缓冲,都要把这个值给赋成0
	return 0;
}
size_t my_fwrite(const void* ptr, size_t size, size_t nmemb, My_FILE* stream)//4个参数,我们只要注意中间两个就行,一个表示的是单个字段的大小,一个表示的是字段数量,例如:100,4,表示4个人字段,每个字段100大小  
{                                        //stream表示的就是我们需要写入的文件流(但是注意,本质是写到了文件的缓冲区(buffer数组中))
	//1.判断缓冲区是否满了,是否可以写入
	if (stream->current == MAX)//文件中的缓冲区满了就刷新
	{
		my_fflush(stream);
	}
	//2.根据缓冲区剩余情况,进行数据拷贝即可(就是将我们要写入的数据拷贝到文件的缓冲区中,也就是文件的struct FILE结构体中)
	size_t user_size = size * nmemb;//这两个相乘表示的就是总字节大小
	size_t my_size = MAX - stream->current;//此时的my_size表示的是剩下的缓冲区的大小

	size_t writen = 0;//这个用于充当最后的返回值(表示最终写入了几个数据)

	if (my_size >= user_size)//缓冲区剩下空间的大小大于我想写入数据的大小
	{//满足该条件,就支持拷贝,否则就不支持拷贝
		memcpy(stream->outputbuffer+stream->current, ptr, user_size);
		//3.更新计数器(更新数据大小)
		stream->current += user_size;
		writen = user_size;//表示这一次写入了这么多个数据
	}
	else
	{//缓冲区的空间,不能容纳全部的数据
		memcpy(stream->outputbuffer + stream->current, ptr, my_size);//表示只能将缓冲区中剩余的空间来存ptr中的数据(缓冲区满了之后就不拷贝了)
		stream->current += my_size;
		writen = my_size;
	}
	//4.刷新缓冲区中的数据(数据已经在缓冲区中了)
	if (stream->flags & BUFF_ALL)//如果此时flags满足这个条件,就表示是全缓冲
	{
		if (stream->current == MAX)//如果flags此时是全缓冲,就一定要满足缓冲区空间满了这个条件,才可以刷新,不然不可以刷新
		{
			my_fflush(stream);
		}
	}
	else if (stream->flags & BUFF_LINE)
	{
		if (stream->outputbuffer[stream->current - 1] == '\n')//满足这个条件就是行缓冲
		{
			my_fflush(stream);//如果是行缓冲就直接刷新数据
		}
	}
	else
	{
		my_fflush(stream);//表示的就是无缓冲
	}

	return 0;
}

int my_fclose(My_FILE* fp)
{
	assert(fp);
	//关文件步骤
	//1.冲刷缓冲区
	if (fp->current > 0)//这个判断,表示此时缓冲区中(也就是数组中)一定要有相应的数据,此时才需要刷新数据,没数据就不需要刷了
	{
		my_fflush(fp);
	    //2.关闭文件
		_close(fp->fd);//因为fflush不是系统接口,所以要自己实现,而close是系统接口,所以可以直接使用
	}
	//3.释放堆空间
	free(fp);
	//4.指针置NULL
	fp = NULL;

	return 0;
}


#define MYFILE "log.txt"

int main()
{
	My_FILE* fp = my_fopen(MYFILE, "w");
	if (fp == NULL)
	{
		return 1;
	}
	const char* str = "hello my fwrite";
	
	int cnt = 0;
	while (true)
	{
		char buffer[1024];
		snprintf(buffer, sizeof(buffer), "%s:%d\n", str, cnt++);
		size_t size = my_fwrite(buffer, strlen(buffer), 1, fp);
		Sleep(1);
		printf("当前成功写入:%lu个字节\n",size);//此时size由于上述的write返回的返回值就是写入了多少数据
	}
	my_fclose(fp);

	return 0;
}

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

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

相关文章

领略未来无需远方,华为全屋智能将在AWE描绘智慧生活新图景

作者 | 曾响铃 文 | 响铃说 4月27日-30日&#xff0c;AWE 2023中国家电及消费电子博览会将在上海新国际博览中心举行&#xff0c;这是AWE展沉淀两年后的再次回归。 作为家电及消费电子领域TOP3的国际盛会&#xff0c;本届AWE以“智科技&#xff0c;创未来”为主题&#xff0…

Redis 数据存储原理

核心模块如图 1-10。 图1-10 图 1-10 Client 客户端&#xff0c;官方提供了 C 语言开发的客户端&#xff0c;可以发送命令&#xff0c;性能分析和测试等。 网络层事件驱动模型&#xff0c;基于 I/O 多路复用&#xff0c;封装了一个短小精悍的高性能 ae 库&#xff0c;全称是 …

如何写好学位论文

昨天刚提交了学位论文&#xff0c;今天准备来总结一下一些常用的Word写作技巧。 样式与多级列表 论文一般都会对章节条目的标题、参考文献、表格内容、题注的字体字号、行间距、段落间距等作出规定。为每一类文字设置一种样式&#xff0c;在写的时候直接应用样式可以方便地对全…

研报精选230421

目录 【行业230421南京证券】氢能行业&#xff1a;地缘政治加速绿色能源转型 【行业230421华安证券】AIGC行业研究框架与投资逻辑 【行业230421信达证券】工控行业深度报告&#xff1a;行业拐点将至&#xff0c;国产品牌加速崛起 【个股230421国信证券_华阳集团】聚焦汽车智能化…

ruoyi启动前端报错‘vue-cli-service‘ 不是内部或外部命令

解决方案&#xff1a; 在新项目的pack.json的文件目录&#xff0c;执行命令npm install 或者 cnpm install 如果报npm 不是内部命令&#xff0c;需要在系统配置全局的变量 在使用npm的时候&#xff0c;或者是通过npm下载了一些包&#xff0c;使用这些包的时候&#xff0c;有时…

LightGBM模型详解

1.背景 LightGBM 是微软开发的 boosting 集成模型&#xff0c;和 XGBoost 一样是对 GBDT 的优化和高效实现&#xff0c;原理有一些相似之处&#xff0c;但它很多方面比 XGBoost 有着更为优秀的表现。官方给出的这个工具库模型的优势如下&#xff1a; 更快的训练效率低内存使用…

全网最详细,web自动化测试窗口框架与验证码登录处理(超详细实战)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 selenium的作用域…

传输协议特点大比拼之TCP

文章目录 前言一.Tpc报头格式源端口和目的端口&#xff1a;序列号&#xff1a;确认号&#xff1a;数据偏移&#xff1a;控制位&#xff1a;窗口大小&#xff1a;校验和&#xff1a;紧急指针&#xff1a; 二.TPC原理2.1 确认应答机制2.2 超时重传机制2.3 连接管理机制2.3.1 三次…

档案室温湿度相关资料

国家档案局《档案库房技术管理暂行规定》对档案库房(也适用于机关档案室)温湿度要求做了如下明确界定&#xff1a;档案库房(含胶片室、磁带室)的温度应控制在14&#xff5e;24℃&#xff0c;有设备的库房日变化幅度不超过2℃&#xff0c;相对湿度应控制在45&#xff5e;60%&…

【大数据离线开发】9 Pig:数据分析引擎

文章目录 Pig&#xff1a;数据分析引擎9.1 什么是Pig9.1.1 简介9.1.2 与 Hive 的对比 9.2 Pig的体系结构和数据模型9.3 Pig的安装和工作模式9.3.1 Pig安装9.3.2 Pig工作模式 9.4 Pig的内置函数9.5 使用PigLatin语句分析数据9.6 Pig的自定义函数9.6.1 自定义过滤和运算函数9.6.2…

低资源环境下对风湿性心脏病的综合预测

文章目录 Ensembled Prediction of Rheumatic Heart Disease from Ungated Doppler Echocardiography Acquired in Low-Resource Settings摘要方法Echocardiogram Homogenization风湿性心脏病的检测 Ensembled Prediction of Rheumatic Heart Disease from Ungated Doppler Ech…

17、Logos使用摘要

本篇将讲述如何将WX的设置界面添加两个自定义的UI行: 包含是否启用某功能的开关,以及手速设置.并且如何定位到修改的代码.采用的是砸过壳的包. 成品也就是增加了两个UI及开关联动效果、 界面分析 如果我们要破解别人的App, 首先从界面UI入手,定位UI 1、使用class-dump导出全部…

分子共价对接(Covalent Docking)

共价对接&#xff08;Covalent Docking&#xff09; 随着人们对共价抑制剂的发现越来越重视&#xff0c;越来越多的软件开始支持共价抑制的虚拟筛选。常用的共价对接软件有CovDock&#xff0c;AutoDock4&#xff0c;FITTED&#xff0c;GOLD&#xff0c;ICM-Pro与MOE等。共价抑制…

断网了,还能ping通 127.0.0.1 吗?为什么?

你女神爱不爱你&#xff0c;你问她&#xff0c;她可能不会告诉你。 但网通不通&#xff0c;你 ping 一下就知道了。 可能看到标题&#xff0c;你就知道答案了&#xff0c;但是你了解背后的原因吗&#xff1f;那如果把 127.0.0.1 换成 0.0.0.0 或 localhost 会怎么样呢&#x…

易基因:ChIP-seq等揭示热休克转录因子A1b调控植物高温胁迫响应的分子机制|应激反应

在拟南芥中&#xff0c;热休克转录因子A1b&#xff08;HEAT SHOCK TRANSCRIPTION FACTORA1b&#xff0c;HSFA1b&#xff09;通过影响种子产量来调控对环境胁迫的抗性。HSFA1b是生殖适应性的决定性因素&#xff0c;这种调控机制怎么形成的呢&#xff1f; 2018年&#xff0c;英国…

基于静态分析结果的测试用例自动生成方法

自动生成用例的方法有多种&#xff0c;主要包括随机法、遗传算法、等价类划分法、约束求解法等。各种方法的适用范围、性能及生成的用例质量各不相同&#xff0c;具体见表1。从表1可以看出&#xff0c;约束求解法效率高&#xff0c;生成的用例质量也高&#xff0c;所以本项目中…

RabbitMQ-Topic(主题模式)

Topic topics 模式支持模糊匹配RoutingKey&#xff0c;就像是sql中的 like子句模糊查询&#xff0c;而路由模式等同于sql中的where子句等值查询 topic 交换机背后的路由算法类似于 direct 交换&#xff0c;使用特定路由键发送的消息将被传递到使用匹配绑定键绑定的所有队列。…

期权价格上下限与期权平价关系

目录 1. 期权的基本概念 2. 期权的上下限 3. 期权的平价关系 1. 期权的基本概念 期权&#xff1a;是一种选择权&#xff0c;期权买方向卖方支付一定数额的 期权费 后&#xff0c;可获得在 一定时间&#xff08;到期日&#xff09;内以 一定价格&#xff08;执行价格&#x…

如何使用 Java 将 JSON 文件读取为字符串?这三种方法很管用!

JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式&#xff0c;它可以用来存储和传输结构化的数据。在 Java 中&#xff0c;有多种方法可以将 JSON 文件读取为字符串&#xff0c;本文将介绍其中的几种。 方法一&#xff1a;使用 java.io 包中的类 java.io 包中提…

【MySQL】索引和事务的详细介绍

目录 一.索引 1.索引概念 2.查看索引 3.创建索引 4.索引使用的数据结构是什么 1.HASH&#xff08;不是&#xff09; 2.二叉搜索树&#xff08;不是&#xff09; 3.B树 N叉数&#xff08;不是&#xff09; 4.B树&#xff08;是滴&#xff09; 5.组合索引 6.索引覆盖 7…