【Linux】进程与文件系统(详细解析)

news2024/11/25 12:50:33

文章目录

  • 1.前言(提出问题)
  • 2.认识问题
  • 3.回顾c文件接口
  • 4.学习系统文件IO
    • open函数
      • 第一个参数
      • 第二个参数
      • 第三个参数
      • ==函数的返回值==
    • write函数
    • read函数
    • close函数
  • 5.文件描述符


1.前言(提出问题)

在C语言阶段学习文件操作的时候,往往会有一些困惑:

1.我真的理解文件的原理和操作吗?不只是语言的问题,而且是在系统层面的问题;2.是不是只有C/C++有文件操作呢?Python,java,php,go…等语言他们的文件操作方法是不是一样的?有没有统一的视角,看待所有语言的文件操作呢?3.操作文件的时候,我们写的第一行代码都是打开文件,打开文件到底底层是在做什么呢?如何理解呢?等等一系列的问题。

这些问题也同样困扰我很久,那么这篇文章主要就是想深入理解一下文件操作。

2.认识问题

我们先从对文件已有的认识开始,认识一下文件:

  1. 文件=内容+属性,那么针对文件的操作,就转换成了对文件的内容进行操作+对文件的属性进行操作
  2. 当文件没有被操作时,文件一般都是待在磁盘的
  3. 当我们对文件进行操作时,文件需要在内存里,这是由冯诺依曼体系结构决定的,CPU只能和内存打交道。
  4. 那么当我们对文件进行操作时,文件肯定需要被提前从磁盘加载到内存,那么加载的是属性还是内容呢?从常识来看,至少也得load文件属性吧。
  5. 当我们对文件进行操作时,是不是只有一个文件被打开呢?肯定不是啦,内存中肯定存在大量的不同文件的属性
  6. 所以综上,打开文件的本质就是将需要的文件属性从磁盘加载到内存中,操作系统OS内部一定会同时存在大量的被打开的文件,那么OS当然就要管理这些文件——先描述,再组织
  7. 先描述:每一个被打开的文件,都要在OS内创建对应文件对象的struct结构体,再组织:将所有struct文件结构体用某种数据结构链接起来。。。那么在OS内部,对被打开的文件进行管理,就转变成了对数据结构的增删查改
  8. 结论:文件被打开,操作系统要为被打开的文件,创建对应的内核数据结构,里面包括各种属性,各种链接关系等等
  9. 那么讲到这里,应该可以知道文件其实可以被分为两大类:磁盘文件,被打开的文件(我们暂且称其为内存文件吧)。
    10.那么文件到底是谁在打开呢?我们知道是OS操作系统,但又是谁让OS去打开文件的呢?当然就是用户啦,那么实际上就是进程。所以所有的文件操作,就是进程和被打开文件的关系,struct task_struct和struct file之间的关系

3.回顾c文件接口

写文件

fputs功能是向指定的文件写入一个字符串。成功写入一个字符串后,文件的位置指针会自动后移,函数返回值为非负整数;否则返回EOF(符号常量,其值为-1)

fwrite() 是 C 语言标准库中的一个文件处理函数,功能是向指定的文件中写入若干数据块,如成功执行则返回实际写入的数据块数目。该函数以二进制形式对文件进行操作,不局限于文本文件。如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。

fprintf是C/C++中的一个格式化库函数,其作用是格式化输出到一个流文件中; printf的函数声明中没有FILE* stream,因为它默认打印到stdout(显示器文件中)。

snprintf()将可变个参数按照format格式化成字符串。

各个函数原型:

#include <stdio.h>
int fputs(const char *s, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int fprintf (FILE* stream, const char*format, [argument]);
int snprintf(char* dest_str,size_t size,const char* format,...);

使用方法:

//fputs
#include<stdio.h>
#define LOG "log.txt"//定义写入的文件名

int main()
{
	//w:以默认写的方式打开文件,如果文件不存在,就自动创建之
	//1.默认如果只是打开,文件内容会被自动清空
	//2.同时,每次进入写入的时候,都会从最开始进行写入
	FILE* fp = fopen(LOG,"w");
	//a:不会清空文件,而是每一次写入都是从文件结尾开始追加的
	FILE* fp = fopen(LOG,"a");
	if(fp == NULL)
	{
		perror("fopen error");//perror的功能是打印错误
		return 1;
	}
	//正常进行文件操作
	const char* msg = "hello world\n";
	int cnt = 5;
	while(cnt)
	{
		//int fputs(const char *s, FILE *stream);
		//将hello world\n以文件流的方式写入LOG文件中
		fputs(msg,fp);
		cnt--;
	}
	fclose(fp);
	return 0;
}
//演示fwrite,printf,fprintf,snprintf函数的使用
#include <stdio.h>
#include <string.h>
int main()
{
	FILE *fp = fopen("myfile", "w");
	if(!fp){
		printf("fopen error!\n");
	}
	const char *msg = "hello world!\n";
	int count = 5;
	while(count--){
		//都是将hello world\n以文件流的方式写入LOG文件中

		//size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
		fwrite(msg, strlen(msg), 1, fp);
		//int fprintf (FILE* stream, const char*format, [argument]);
		fprintf(fp,"%s: %d :nan\n",msg,cnt);
		fprintf(stdout,"%s: %d :nan\n",msg,cnt);//Linux下一切皆文件,这句代码相当printf,打印到显示器文件

		//int snprintf(char* dest_str,size_t size,const char* format,...);
		//1.先定义一个缓冲区字符串
		char buffer[256];
		//2.向buffer中写入字符串,相当于先写入一个字符缓冲区了
		snprintf(buffer ,sizeof(buffer), "%s: %d :nan\n",msg,cnt);
		//3.然后再将buffer中的数据以文件流的方式写入LOG文件
		fputs(buffer,fp);
	}
	fclose(fp);
	return 0;
}

读文件

fgets()函数的作用是用来读取一行数据的。

函数原型:

# include <stdio.h>
//有三个参数。它的功能是从 stream 流中读取 size 个字符存储到字符指针变量 s 所指向的内存空间。
//它的返回值是一个指针,指向字符串中第一个字符的地址。
//stream 表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流.
char *fgets(char *s, int size, FILE *stream);

使用方式:

#include<stdio.h>
#define LOG "log.txt"//定义写入的文件名

int main()
{
	FILE* fp = fopen(LOG,"r");
	if(fp == NULL)
	{
		perror("fopen error");//perror的功能是打印错误
		return 1;
	}
	while(1)
	{
		//1.先定义一个缓冲区
		char line[128];
		//2.char *fgets(char *s, int size, FILE *stream);
		//将文件中的数据读到缓冲区line字符数组中
		if(fgets(line, sizeof(line), fp) == NULL) 
		{
			break;
		}
		else//并打印出来
		{
			printf("%s",line);
		}
	}
	fclose(fp);
	return 0;
}

4.学习系统文件IO

open函数

系统接口中使用open函数打开文件,open函数的函数原型如下:

int open(const char *pathname, int flags, mode_t mode);

第一个参数

表示路径名称,若pathname以路径的方式给出,则当需要创建该文件时,就在pathname路径下进行创建;若pathname以文件名的方式给出,则当需要创建该文件时,默认在当前路径下进行创建。

第二个参数

flags,表示打开文件的方式。
常用的参数选项如下:
在这里插入图片描述
注意:O_CREAT需要使用mode选项(函数的第三个参数),来指明新文件的访问权限,不然会导致乱码。

打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。

例如:

//创建文件并可以向文件写入,相当于C语言的fopen("文件名","w");
//注意默认不会对文件内容做清空
O_WRONLY | O_CREAT 
//加上O_TRUNC,就不会对原始内容做清空了
O_WRONLY | O_CREAT | O_TRUNC
//相等于C语言的fopen("文件名","a");
O_WRONLY | O_CREAT | O_APPEND

扩展:第二个参数本质到底是什么?——是宏定义
在这里插入图片描述

这样一来,在open函数内部就可以通过使用“与”运算来判断是否设置了某一选项。

int open(arg1, arg2, arg3){
	if (arg2&O_RDONLY){
		//设置了O_RDONLY选项
	}
	if (arg2&O_WRONLY){
		//设置了O_WRONLY选项
	}
	if (arg2&O_RDWR){
		//设置了O_RDWR选项
	}
	if (arg2&O_CREAT){
		//设置了O_CREAT选项
	}
	//...
}

第三个参数

mode,表示创建文件的默认权限。
例如,将mode设置为0666,则文件创建出来的权限如下:-rw-rw-rw-

但实际上创建出来文件的权限值还会受到umask(文件默认掩码)的影响,实际创建出来文件的权限为:mode&(~umask)。umask的默认值一般为0002,当我们设置mode值为0666时实际创建出来文件的权限为0664。

若想创建出来文件的权限值不受umask的影响,则需要在创建文件前使用umask函数将文件默认掩码设置为0:umask(0)

函数的返回值

打开文件成功:open函数的返回值是新打开文件的文件描述符。
打开文件失败:返回-1

write函数

系统接口中使用write函数向文件写入信息,write函数的函数原型如下:

ssize_t write(int fd, const void *buf, size_t count);

我们可以使用write函数,将buf位置开始向后count字节的数据写入文件描述符为fd的文件当中。

  1. 如果数据写入成功,实际写入数据的字节个数被返回。
  2. 如果数据写入失败,-1被返回。

使用方式:

写文件:下面的代码先将“bbb”写入文件中,然后再追加“a”

  1 #include<stdio.h>
  2 #include<errno.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 #include<sys/types.h>
  6 #include<sys/stat.h>
  7 #include<fcntl.h>
  8 
  9 #define LOG "log.txt"
 10   
 11 //系统方案  
 12 int main()  
 13 {  
 14     //设置权限掩码umask,默认是0002  
 15     umask(0);  
 16   
 17     //O_WRONLY | O_CREAT :默认不会对原始文件做清空  
 18     //int fd = open(LOG, O_WRONLY | O_CREAT, 0666);  
 19     //int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);  //加上O_TRUNC在打开文件时就会默认对文件的原始内容做清空,并且从文件开头开始写
 20     
 21     int fd = open(LOG, O_WRONLY | O_CREAT | O_APPEND, 0666);//追加写                                                                                                             
 22                         
 23     //创建失败返回-1    
 24     if(fd == -1)         
 25     {                                                                        
 26         printf("fd: %d, errno: %d, errstring: %s\n",fd, errno,strerror(errno));                                                                        
 27     }                                                                                                                                                  
 28     else                                                                                                                                               
 29     {                                                                                                                                                  
 30         printf("fd: %d, errno: %d, errstring: %s\n",fd,errno, strerror(errno));                                                                        
 31     }                                                                                                                                                  
 32                                                                                                                                                        
 33     //将数据写入文件                                                                                                                                   
 34     //const char* msg = "bbb";                                                                                                                         
 35     //追加写"a"                                                                                                                                          
 36     const char* msg = "a";                                                                                                                             
 37     int cnt = 5;       
 38     while(cnt--)                                                                                                                                       
 39     {                                                                                                                                                  
 40         //定义缓冲区                                                                                                                                   
 41         char line[128];                                                                                                                                
 42         //将数据格式化写入字符串字符数组中                                                                                                             
 43         snprintf(line,sizeof(line),"%s, %d\n",msg,cnt);                                                                                                
 44                                                                                                                                                        
 45         write(fd,line,strlen(line));//将缓冲区的数据写入到文件中                                                                                       
 46         //这里的strlen不要+1,\0是C语言的规定,不是文件的规定                                                                                          
 47     }
 48 
 49     close(fd);
 50 
 51     return 0;
 52 }

read函数

系统接口中使用read函数从文件读取信息,read函数的函数原型如下:

ssize_t read(int fd, void *buf, size_t count);

我们可以使用read函数,从文件描述符为fd的文件读取count字节的数据到buf位置当中。

  1. 如果数据读取成功,实际读取数据的字节个数被返回。
  2. 如果数据读取失败,-1被返回。

使用方式:

读文件:上面我们已经将数据写入文件中,那么现在我们就把数据从文件中读出来

#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
 
#define LOG "log.txt"
  
//系统方案  
int main()  
{  
    //设置权限掩码umask,默认是0002  
    umask(0);  
   
    int fd = open(LOG, O_RDONLY);                                                                                                                 
                        
    //创建失败返回-1    
    if(fd == -1)         
    {                                                                        
        printf("fd: %d, errno: %d, errstring: %s\n",fd, errno,strerror(errno));                                                                        
    }                                                                                                                                                  
    else
    {                                                                                                                                               
        printf("fd: %d, errno: %d, errstring: %s\n",fd,errno, strerror(errno));                                                                        
    }                                                                                                                                                  
                                                                                                                                                       
    //读文件
    //这里我们无法做到按行读取,我们是整体读取的
    //做法是将文件中的数据读到缓冲区中,再打印出来
    char buffer[1024] = {0};
    ssize_t n = read(fd,buffer,sizeof(buffer)-1);//使用系统接口来进行IO的时候,一定要注意\0的问题
    if(n>0)
    {
        buffer[n] = '\0';

        printf("%s\n",buffer);
    }
    close(fd);
    return 0;
}

close函数

系统接口中使用close函数关闭文件,close函数的函数原型如下:

int close(int fd);

使用close函数时传入需要关闭文件的文件描述符即可,若关闭文件成功则返回0,若关闭文件失败则返回-1。

5.文件描述符

1.open函数的返回值是文件描述符

由于文件描述符这块知识点比较重要,所以放在这里统一讲解。
前面通过对open函数的学习,我们知道了文件描述符就是一个小整数,是open函数的返回值,我们再来回顾一下open函数的原型:

int open(const char *pathname, int flags, mode_t mode);

将前面的代码(用系统接口进行读写)在Linux下运行后,在Linux上的运行结果都有这么一句话:

fd: 3, errno:0, errstring:Success//运行成功

不管你怎么运行,会发现一个奇怪的点,那就是:文件描述符都是从3开始的。为什么呢?想搞明白的宝子请继续往下看——>

2.Linux下一切皆文件。
在这里插入图片描述

请看下列代码:

  1 #include<iostream>
  2 #include<cstdio>                                                                                                                                                                 
  3 
  4 int main()
  5 {
  6     //Linux下一切皆文件
  7     //C
  8     printf("hello printf->stdout\n");
  9     fprintf(stdout,"hello fprintf->stdout\n");
 10     fprintf(stderr,"hello fprintf->stderr\n");
 11 
 12     //C++
 13     std::cout << "hello cout -> cout" << std::endl;
 14     std::cerr << "hello cerr -> cerr" << std::endl;
 15 }

运行结果:标准输出和标准错误都会向显示器打印。
在这里插入图片描述

ps:我们重定向时只会对标准的输出进行正常的重定向,但是标准错误不受重定向的影响。

在这里插入图片描述

那么为什么我们上述代码打印出来的文件描述符是从3开始的呢?

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2
  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器
    所以输入输出还可以采用如下方式:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
	char buf[1024];
	ssize_t s = read(0, buf, sizeof(buf));
	if(s > 0){
	buf[s] = 0;
	write(1, buf, strlen(buf));
	write(2, buf, strlen(buf));
	}
	return 0;
}

3.文件描述符的本质是数组下标
可以让我们通过进程快速找到对应的打开文件。这个文件一定先被打开过,只有这样它才有对应的文件描述符。

解析:文件存储在磁盘当中,当打开一个文件的时候,OS都会在内核中创建一个struct file结构体来描述该文件,充当这个打开的文件。struct file结构体中包含着文件的大部分属性,就好像struct task_struct结构体中包含着进程的大部分属性。对文件的增删查改就转换成了对链表的增删查改。只要找到了file,就可以获取所有文件属性和内容。——这就是先描述,再组织的思想。

而文件是用户通过进程(调用系统调用接口)让OS打开的,我们在执行代码的时候,能最直接找到的就是进程的PCB,所以我们要维护进程和被打开文件之间的对应关系,即要维护好进程和被打开文件的映射关系:
一个进程可以打开多个文件,OS为了能让进程快速找到对应的文件,在内核中定义了一个数据结构struct files_struct,在该结构体中包含着一个数组struct file* fd_array[],其中包含的类型都是file*。当从磁盘加载一个文件时OS就要创建为该文件创建一个对应的对象,然后OS就要在当前执行打开操作的进程的struct files_struct内部的数组中从上到下进行遍历,找到一个没有被使用位置,将新加载的文件的结构体对象的地址填入到数组中,这样就建立了struct files_struct与被打开文件之间的对应关系。同时在进程的结构体当中,又包含了一个struct file_struct *files;指针,在进程初始化的时候,会为该进程创建struct files_struct对象,并将地址存在struct file_struct *files指针内,这样就建立了进程与被打开文件之间的映射关系。

综上:在应用层返回的文件描述符就是上面说的数组的下标,所以当使用read、write、close这些系统调用接口的时候,必须在参数列表传入文件对应的文件描述符,通过结构体对象的映射关系来找到对应的文件进行操作。

在这里插入图片描述
4.对文件缓冲区的理解

接下来,我们对文件的写入、读取操作进行进一步的理解(关于常常涉及的缓冲区):

每个文件都要匹配一个缓冲区,比如当我们使用write(3, buffer, xxx)向缓冲区写入操作时,所做的工作过程是:我们调用了write函数 ——> OS识别到了系统调用 ——> 找到进程对应的pcb ——> 找到file_struct结构体 ——> 在file_struct结构体中找到fd_array[]数组,通过传入的文件描述符找到索引 ——> 找到匹配的文件结构体file,找到后从用户层将对应的数据拷贝到缓冲区。调用结束后返回写了多少字节。而拷贝的数据什么时候由缓冲区刷新到外设磁盘,由OS自主决定。(过程如下如所示)

所以我们的IO类read、write函数本质是拷贝函数!(用户空间与内核空间进行数据的来回拷贝)

在这里插入图片描述

5.如何理解Linux下一切皆文件。

计算机有很多的硬件外设,比如键盘、显示器、网卡、磁盘等,并且这些硬件都有与之匹配的驱动程序来实现与硬件的交互。

以键盘为例:读取方法(read_keyboard();)站在内存的视角来看,就是要将数据从外设键盘读取到内存中;而键盘这个外设不需要写入方法(write_keyboard();),因为总不能从内存写入到键盘让键盘自己动,即让键盘驱动中的写入操作只有声明,函数体是空的、什么都不做即可。

与键盘类似的,计算机所有的外设都有IO操作,即驱动都有read与write功能。

再举个例子:显示器设备,在显示器显示数据,就是将数据写入到了显示器外设,所以显示器也有写入方法(write_screen();),而读取方法(read_screen();)则不需要,因为计算机是从键盘读取的数据,只是回显到了显示器上。
所有的硬件设备都是不同的,那么在Linux下一切皆文件是怎么做到的呢,是如何将不同的硬件设备都看做是文件的呢?——当打开一个硬件设备的时候,在OS内部也同样要创建一个struct file结构体对象,来描述这个硬件文件,其中包含了该文件的权限、大小、以及各种其他属性,除此之外还包含两个函数指针int (*readp)(int fd, char buffer, int size);和int (*writep)(int fd, char buffer, int size);,同时这个文件的struct file结构体对象中也有一小块缓冲区。读写的两个函数指针就指向属于自己的硬件驱动中的读写方法,并且每一个硬件外设在被打开时都有一个这样的结构体对象,其中都会将属于自己的读写方法的地址填入自己的函数指针中。

在进程的struct files_struct中的数组中保存着使用的外设文件对应的结构体地址,在进程通过该数组(文件描述符)访问对应的外设时,在进程看来,所有的外设都是struct file文件结构体对象。数据拷贝放入struct file的缓冲区中,在需要进行硬件设备的读写时,只需要通过函数指针调用对应底层驱动中的方法来完成读写即可,根本不用关心底层实现的差异。而OS也有读写操作,它的读写操作本质就是拷贝,只需要将上层应用层的数据拷贝到缓冲区中,然后通过指针调用底层不同的读写方法就可以将数据放到对应的外设中,所以在文件对象struct file以上来看,就实现了Linux下一切皆文件!不用关心底层的差异化。
我们都是通过进程的方式来进行OS的访问的!而进程只能看到文件(struct file),所以才有了一切皆文件。这其实也是设计面向对象的思想(多态特性)。
在这里插入图片描述

6.标准输入、输出、错误的返回值类型FILE*的理解

标准输入输出错误、fopen的类型都是FILE*,那么FILE到底是什么?

FILE是一个封装后的结构体,由是C语言提供的。可以进入/usr/include/stdio.h文件中进行查看:发现其名字是_IO_FILE,重命名为:typedef struct _IO_FILE FILE。

FILE与struct file有关系吗?FILE结构体中封装了什么?

没有关系。并且这个结构体中必定封装了fd(系统调用的文件描述符),因为返回值为FILE*类型的例如fopen、fclose、fwrite、fread,这些C的文件操作接口底层都调用了open、close、write、read这样的系统调用接口。

  • 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访
    问的。
  • 所以C库当中的FILE结构体内部,必定封装了fd。

如果有兴趣,可以看看FILE结构体:

typedef struct _IO_FILE FILE;/usr/include/stdio.h
/usr/include/libio.h
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 0
int _blksize;
#else
int _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*是一个指针,我就可以通过指针来查看_fileno(文件描述符):

//头文件略
#define LOG "log.txt"
int main()
{
	printf("%d\n",stdin->_fileno);//0
	printf("%d\n",stdout->_fileno);//1
	printf("%d\n",stderr->_fileno);//2
	FILE* fp = fopen(LOG, "w");
	
	printf("%d\n", fp->_fileno);//3
	return 0;
}

运行结果于上述的解释一致:0,1,2描述符被stdin、stdout、strerr占用,我们自己打开的文件描述符为3。

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

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

相关文章

基于PP-OCRv3的车牌检测和识别

本项目基于百度飞桨AI Studio平台进行实现&#xff0c;百度出品的深度学习平台飞桨&#xff08;PaddlePaddle&#xff09;是主流深度学习框架中一款完全国产化的产品&#xff0c;与Google TensorFlow、Facebook Pytorch齐名。2016 年飞桨正式开源&#xff0c;是国内首个全面开源…

【SpringMVC】| 域对象共享数据

目录 前期准备 域对象共享数据 一&#xff1a;向request域共享数据&#xff08;五种方法&#xff09; 1. 使用ServletAPI向request域对象共享数据&#xff08;了解&#xff09; 2. 使用ModelAndView向request域对象共享数据 3. 使用Model向request域对象共享数据 4. 使用…

chatgpt赋能python:Python代码怎么找?这里介绍几种方法

Python代码怎么找&#xff1f;这里介绍几种方法 在编写Python程序的时候&#xff0c;经常会遇到需要查找已有代码的情况。那么&#xff0c;在这里&#xff0c;我们将介绍几种查找Python代码的方法&#xff0c;希望能对大家有所帮助。 使用文本编辑器的查找功能 在大多数文本…

3.场(field)

目录 1.复习 2.引言 3.数量场 1.概念 2.例题 4.矢量场 1.概念 2.例题 5.坐标变换和坐标单位矢 1.坐标变换 2.单位矢 1.复习 2.引言 如果说矢量分析研究的是矢量的时间变化&#xff0c;那么场就是它的空间变化. 场是客观存在的&#xff0c;杨振宁先生在总结20世…

Spring Boot 3.1中如何整合Spring Security和Keycloak

在今年2月14日的时候&#xff0c;Keycloak 团队宣布他们正在弃用大多数 Keycloak 适配器。其中包括Spring Security和Spring Boot的适配器&#xff0c;这意味着今后Keycloak团队将不再提供针对Spring Security和Spring Boot的集成方案。但是&#xff0c;如此强大的Keycloak&…

数据库|TiDB 数据库大版本升级-基于TiCDC异机升级

作者&#xff1a;高文峰 | 神州数码云基地TiDB团队成员 目录 一、前言 二、升级架构图 三、升级流程 1.下游TiDB集群部署过程 2. 上游TiCDC节点的扩容 3. 上游数据全备恢复到下游 4. TiCDC启用正向同步任务 5. 应用停服务&#xff0c;tidb 无业务会话连接 6. 确认数据…

2023年6月跟产品开发专家学NPDP产品经理认证课到这里

NPDP产品经理国际资格认证是国际公认的唯一的新产品开发专业认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 我们针对互联网时代的个人、互联网企业、与传统企业推出一系列学习。 课程从…

小车跑迷宫,如何完成?

先看视频1&#xff1a; 先看视频2&#xff1a; 要制作一个能顺利走到迷宫终点&#xff0c;并能按最短路径回来的小车&#xff0c;重中之重就是寻找其最短路径的算法&#xff0c;迷宫情况复杂多变&#xff0c;多个路口交错纵横&#xff0c;想要完美的找出最短路径并不容易&#…

事件相关功能磁共振波谱技术(fMRS)

导读 质子磁共振波谱(MRS)是一种非侵入性脑成像技术&#xff0c;用于测量不同神经化学物质的浓度。“单体素”MRS数据通常在几分钟内采集&#xff0c;然后对单个瞬态进行平均&#xff0c;从而测量神经化学物质浓度。然而&#xff0c;这种方法对更快速的神经化学物质的时间动态…

chatgpt赋能python:Python人脸身份识别:提高安全性和效率的先进技术

Python人脸身份识别&#xff1a;提高安全性和效率的先进技术 随着科技的发展&#xff0c;人类对于安全性和效率的需求逐渐增加。而人脸身份识别技术正是一个能够满足这一需求的先进技术。在过去的几年中&#xff0c;这种技术已经逐渐发展成为一种普及的安全措施&#xff0c;这…

串口组件:ZylSerialPort.NET 1.83 Crack

ZylSerialPort.NET 1.83 .NET 组件 库 ZylSerialPort.NET 是一个基于线程、事件驱动、异步/同步串口的.NET 组件库。 使用 ZylSerialPort.NET 组件可以轻松地通过串行端口连接与外部设备进行通信&#xff0c;例如调制解调器、条形码阅读器、GSM 模块等。 您也可以将它与 USB、…

Volatile、Synchronized、ReentrantLock锁机制使用说明

一、Volatile底层原理 volatile是轻量级的同步机制&#xff0c;volatile保证变量对所有线程的可见性&#xff0c;不保证原子性。 当对volatile变量进行写操作的时候&#xff0c;JVM会向处理器发送一条LOCK前缀的指令&#xff0c;将该变量所在缓存行的数据写回系统内存。由于缓…

DJ4-7 请求分页存储管理方式

目录 4.7.1 请求分页中的硬件支持 1、页表机制 2、缺页中断机构 4.7.2 内存分配策略和分配算法 1、最小物理块数的确定 2、物理块的分配策略 3、物理块的分配算法 4.7.3 调页策略 1、系统应当在何时把一个页面装入内存&#xff1f; 2、从何处调入页面&#xff1f;…

机器学习常识 12: SVM

摘要: 支持向量机 (support vector machine, SVM) 有很多闪光点, 理论方面有 VC 维的支撑, 技术上有核函数将线性不可分变成线性可分, 实践上是小样本学习效果最好的算法. 1. 线性分类器 如图 1 所示, 基础的 SVM 仍然是一个线性二分类器, 这一点与 logistic 回归一致. 图 1.…

MATLAB 之 隐函数绘图、图形修饰处理、图像处理与动画制作和交互式绘图工具

这里写目录标题 一、隐函数绘图1. 隐函数二维绘图3. 隐函数三维绘图 二、图形修饰处理1. 视点处理2. 色彩处理2.1 颜色的向量表示2.2 色图2.3 三维曲面图形的着色 3. 图形的裁剪处理 三、图像处理与动画制作1. 图像处理1.1 图像的读/写1.2 图像的显示 2. 动画制作2.1 制作逐帧动…

chatgpt赋能python:Python交流App:提高Python社区交流效率

Python 交流 App: 提高 Python 社区交流效率 Python 是当今流行程度最高的编程语言之一&#xff0c;有着广泛的应用场景和庞大的社区。 作为 Python 工程师&#xff0c;经常有各种问题需要得到解决&#xff0c;同时也希望能与同行进行交流、分享和学习。这时&#xff0c;一款高…

Linux——Centos7进入单用户模式修改密码

本篇文章适用于经常忘记自己root用户密码的初学者&#xff01;&#xff01;&#xff01;&#xff0c;会进入单用户模式修改root密码即可。 系统启动进入到如下界面后输入字母“e”; 2.可以看到进入到如下界面&#xff1b; 3.一直下翻到图中圈起来的这部分&#xff1b; 4.在Lin…

excel 获取指定字符前后的字符串

目录 excel 获取指定字符前后的字符串 1.截取指定字符前的字符串 2.截取指定字符后的字符串 excel 获取指定字符前后的字符串 1.截取指定字符前的字符串 1.1LEFT FIND find:返回一个字符串在另一个字符串中出现的起始位置。 (区分大小写&#xff0c;且不允许使用通配符) …

综合指挥调度系统行业分类汇总

综合指挥调度系统是将语音、视频、GIS进行高度融合&#xff0c;构建“平战结合”的指挥调度模式&#xff0c;既满足平时的应急培训、日常通信、会议会商等要求&#xff0c;也能够应对战时的应急指挥、应急救援、应急决策等需求&#xff0c;达到统一指挥、联合行动的目的&#x…

ArcGIS中实现土地利用转移矩阵

土地利用转移矩阵&#xff0c;就是根据同一地区不同时相的土地覆盖现状的变化关系&#xff0c;求得一个二维矩阵。通过对得到的转移矩阵进行分析&#xff0c;能够得到&#xff12;个时相&#xff0c;不同的地类之间相互转化的情况&#xff0c;它描述了不同的土地利用的类型在不…