Linux - 基础IO(Linux 当中的文件,文件系统调用接口,文件描述符)- 上篇

前言

首先,关于文件我们最先要理解的是,文件不仅仅存储的是数据,一个文件包括 内容 + 数据。内容好理解,就是我们先要这文件存储哪一些数据,这些数据就是文件的内容。

但是,在计算机当中,有两种文件,一种是 正在打开的文件 ,另一种是 没有被打开文件。

文件没有被打开,那么就是存储在外设当中,最常见的存储数据的外设比如 磁盘,u盘,硬盘等等,那么我们知道,外设当中存储速度是比不上内存的,但是,外设有一个好处,就是存储容量大。

那么也就意味着,在外设当中存储了很多个文件, 我们如何 对这些文件进行 增删查改 ,其实靠的就是文件的属性,文件的属性有很多,比如文件的最近一次的修改时间,文件的创建的时间,文件的大小,文件的存储位置等等···  这些,在windows 当中右键某一个文件,我们也可以查看文件的属性:

而,对于未被打开的文件,文件的属性就是为了方便我们来对文件 进行 增删查改 。也就是说,如何在“浩瀚”的外设当中找到文件,进行 增删查改,文件的属性不能少。

就好比是在快递站当中找快递,这些快递不是随机的,混乱的堆放在某处,然后让我们自己的一个一个找的;而是,把这个快递 编写一个快递号,比如是 12 - 3 - 4444,那么我就在 12 号货架的 第 3 层的位置,来找 4444 的快递就行了,这样不仅方便了 快递站的人来存放,也方便了 找快递的人来拿。 不会显得很乱。


文件要被打开,也就是要被修改,那么肯定是要加载到内存当中 ,而且,一个进程可能不只是打开一个文件,一般大多数情况下, 一个进程打开的文件数 是 1 : n 的关系。因为,存储进程的代码 数据,本身就是一个可执行文件当中存储的,同时,进程很可能还会打开其他的多个文件。

所以,不仅仅是外设当中有很多的文件,在内存当中,操作系统也要维护很多的 文件。

所以,这就要谈一个六字真言了 -- 先描述在组织

其实在操作系统 在 内存当中管理这么多个进程,和 文件,本质上就是管理这些进程 和 文件 的属性。操作系统 像 Linux 使用 C 语言实现的,所以,其实本质上就是用 一个个 struct 结构体来表述 一个进程 / 文件 的属性(也就是属性的集合)(这叫做描述),然后操作系统只要管理好了这些个 strutc 结构体就可以管理好 所以的进程 / 文件(这叫做组织)。而管理(组织)这些 结构体对象,本质上就是用 某种 数据结构来维护某种关系。

比如 进程 等待 硬件资源,但是这个硬件当前可能是多个进程在等待资源,那么就有先后顺序,就要等待。所以,在Linux 当中,基本上每一个 硬件在操作系统当中都有一个等待队列,用于进程之间在这个硬件上的排队。

所以,我们才说,在Linux 当中一切都是文件,为什么,就因为上述我们所说的 -- 先描述在组织


所以文件也是一样,操作系统在内存当中维护这些个资源,本质上就是维护 操作系统为 这个加载到内存当中文件 所 创建的 文件结构体对象

你在很多的编程语言当中看到的 ,为什么 像类似 fopen()这样的函数,返回的往往是一个 文件对象的指针,其实就离不开 操作系统当中为了更好管理文件,为这个文件所单独创建的 文件结构体对象。两者实际上是大同小异的。

文件

在C 语言当中的 各个文件接口(函数)

首先当然是 fopen()函数打开一个文件:
 

可知道 fopen()函数返回的是一个 FILE*(文件指针 / 文件句柄) 。

  • path : 打开文件的路径;(相对路径,绝对路径,或者不带路径)
  • mode:打开文件的方式;

后续我们对这个文件的操作都需要 fopen()函数的返回值,也就是这个 FILE* 文件指针。

  • 如果你以 "w" 写的方式打开一个文件的话,如果文件在你传入的路径之下不存在,那么就会自动创建一个文件;
  • 而且,"w" 方式写入数据到文件的话,如果文件当中有数据,那么就先把文件的长度清零,然后在文件的最开始处写入。(这种方式 和 Linux 命令行当中的 ">" 输出重定向 向文件当中的写入方式是一样的。如 echo "hello Linux!" > text.txt  这个文件当中输入 "hello Linux!"字符串,就是以 'w' 的方式来写入的。所以像 "> text.txt"  这样的方式就可以 清空 text.txt 这个文件当中的数据
  • "a" 是在文件末尾处以 追加的方式来写。 同样 , 如果文件在你传入的路径之下不存在,那么就会自动创建一个文件;(这种方式 和 Linux 命令行当中的 ">>" 输出重定向 向文件当中的写入方式是一样的。">>" 就是追加的方式来在文件当中输入数据的
  • 对于 path 路径,如果你只是直接写上文件名,没有带上路径,那么就会默认是在当前 路径下查找这个文件。(此处的 "当前路径"指的是 调用 fopen()函数的 进程的当前路径)。

在 /proc/进程PID 当中就有这个进程对应的 当前工作路径:

所以,如果把这个进程的  cwd,也就是这个进程的当前工作路径给改掉了,那么像 fopen("text.txt",'w'); 这样的操作,就会在新修改的 工作路径的下创建这个新文件。


c 程序在运行之时,会自动打开 三个 标准输入输出流

 像 fprintf()这些函数可以像写入数据到文件,或者是读取数据到程序当中的一样,来像上述的 三种 标准输入输出流当中输入和输出数据:


上述只是简单的把 C 语言当中的一下文件操作浅谈一下,因为不是本篇博客的主要内容,所以不在过多阐述。

被打开的文件(被加载到内存当中的文件)

 我们说文件是存储在 磁盘上的,而磁盘是外部设备,访问文件的操作,其实是在访问硬件!!所以,其实 三个标准输入输出流也是在访问文件。

 又因为,像在 C 语言当中使用 printf/fprintf/fscanf/fwrite/fread/fgets/gets ···· 这些函数,想访问文件的话,其实就是想访问对应的硬件。这些函数使用用户所书写的,用户不能直接绕过中间的层级,直接访问到最底层的 硬件:

 如上图所示,用户和底层硬件之间 还有 很多层级。只能一层一层去访问到 底层硬件

所以,这些访问到硬件的函数,本质上是 用户操作接口,然后在这些函数的实现当中,就需要使用到 系统调用接口几乎所有的库,只要是想要访问到底层硬件设备,都必定是要封住 系统调用接口的。

Linux 当中常见的 文件系统调用接口介绍

 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选项,来指明新文件的访问权限
    O_APPEND: 追加写

返回值:

    成功:新打开的文件描述符
    失败:-1

上述的 类似 O_RDONLY 的5个参数,其实是宏,上述的 flags 这个参数,其实是作 标志的作用,用户表示某一些状态是否存在,比如 O_RDONLY 就表示 只读打开。

以往我们使用一个变量作为一个标志位的时候,一般是这一个变量只能作一个标志位,也就只能表示一种状态。但是其实,一个 int 变量,有 32 个bit 位,如果每一个位表示一个状态的话,那么按道理这个 int 变量是可以表示 32 种状态是否存在的。

上述的 flags 变量就是一个 比特位方式的标志位传递方式

我们可以利用像上述一样的 二进制操作符 取出 int 变量 每一个 bit 位的值,从而判断某一个 function 状态是否存在,从而实现不同的功能。 

这样的话,我们在外部使用这个函数,就可以在 show 函数当中传入这些宏,而且,顺序无关,在 show 函数内部,就会一次按位与上 每一个 宏,从而解析出 当前传入了多少个宏(状态)。 


O_WRONLY 参数  和 O_CREAT   参数

而且,O_WRONLY 这个参数,如果路径当中没有 此文件的话,不会创建的新的文件。除非又引入了 O_CREAT  参数。

此时,在进程的当前目录下是没有 log.txt 这个文件的。发现:文件打开失败了,而且在进程的工作目录下没有 创建新的文件。

 所以,要想上述一样 传入O_CREAT    参数告诉操作系统,如果打开文件之时,没有该文件,就创建一个新的文件。

但是我们又发现了问题:
 

使用上述  O_CREAT   参数来创建 文件的话,发现权限完全是不对的。

因为 在 Linux 当中新建一个文件的时候,必须要告诉 操作系统,要 创建的 新文件的 访问权限。

 我们要指定 新创建文件的权限,那么就要使用 带 mode 参数的 open()函数:

向下述一样传参:

 此时发现,新创建的 log.txt 文件就已经创建成功了,而且 文件的 访问权限还是 664 权限。

为什么不是 666 权限呢?因为 ,Linux 创建文件时候,有一个 umask 来控制 新创建的文件的权限。 

 如果必须要使用 666 权限的话,有一个 umask()系统调用接口
 

进程的代码当中调用 umask ()函数,可以把当前进程的 umask 值修改为传入的参数的值。 

 调用 umask ()函数之后,在系统当中是系统的 umask,在进程当中是进程自己的 umask

两个 umask,那么进程此时应该使用 哪一个 umask 呢?使用 就近原则。此时在进程当中,最近就有一个新刷新的 umask,那么就使用 这个 umask,如果没有,就使用系统当中的。


open()函数的返回值是一个 整形

 这个返回值 称之为 -- file descriptor ,换句话说 就是 文件描述符。代表的意思就是:如果打开文件成功,返回的是一个  >0 的数,此时这个数代表的就是一个 文件。


O_TRUNC参数 

O_WRONLY  参数不仅不会 帮我们创造新的文件,而且,它不是像 fopen()函数当中的 "w" 一样,先把文件当中的数据进行清空,然后从文件的开始处进行书写;它虽然依然是从文件的开始处进行书写,但是, O_WRONLY 不会 把文件当中的数据进行清空;

 所以,如果先要实现 fopen()函数当中的 "w" 参数一样的效果的话,需要加上 O_TRUNC 这个参数,它所实现的功能就是 把文件当中原本的数据清空

此时才能实现   fopen()函数当中的 "w" 参数一样的效果。


O_APPEND 参数

 这就是追加模式,在文件末尾追加数据。

很显然,追加 参数 和 清空 参数,两个参数是冲突的。

此时我们进行追加的方式在文件当中输入数据:
 


close()函数

关闭文件。

fildes: 文件描述符,也就是上诉 open()函数当中,打开文件成功返回的  file descriptor。


 write()函数

像文件当中写入数据:
 


 文件系统调用接口 和 文件库函数的关系

 我们之前说过,库函数是用户层面使用的接口,用户如果想要访问到 底层硬件的话,必须要一层一层往下去调用,才能调用到 底层硬件,或者是下层应用,数据或者说是 接口。

操作系统不会容许 用户,或者是上层的各种接口 跨层级 去访问到下层当中的数据,硬件,接口等等。

 所以,才会出现了上述所说的 文件系统调用接口 和 文件库函数的关系。库函数要想访问下层,只能通过系统调用接口。

 他一定是像下述类似的方式来在调用系统调用接口的:


 fd 文件描述符 

 操作系统也要维护 打开了的文件,这些文件都是被加载到内存当中的来才能进行 增删查改的操作的。

操作系统如何进行管理这么多的文件,用的就是 --- 先描述在组织

 谁管理谁来描述这个被打开的文件,操作系统要把这个 被打开的文件的属性,放到一起(也就是放到一个 struct 一个结构体当中),每一个被打开的文件,都会在内核当中创建一个 内核结构体。比如 我们把这个内核结构体 称职为 --- struct file。

在这个 struct file 当中,之间或者间接的包含如下属性:

  • 在磁盘的什么位置?
  • 基本属性:权限,大小,读写位置(当前要从那个位置开始读写(开始位置的偏移量)),谁打开的·······
  • 文件的内核的缓冲区信息(文件被打开时,这个文件在内核当中对应的内核空间位置)(将来要想文件当中写数据,会先写到这个缓冲区当中,然后操作系统会定期从 缓冲区当中 向磁盘当中拷贝数据)
  • ·····

上述只是举了一些例子,其实一个文件还有很多信息,每一个被打开的文件,这些信息都会保存到一个 struct file 当中,操作系统可能要维护很多个 struct file;

操作系统会以 双链表的形式,把这一个一个的 struct file 链接在一起
 

现在,我们对被打开的文件进行操作的话,就变成了对 这个 文件结构体对象双联表  的 增删查改
这个过程就是  先描述在组织 的过程,和进程当中  先描述在组织  没什么区别。


在前言当中也进行了说明:一个进程打开的文件数 是 1 : n 的关系。所以,我们如何知道,哪一个进程 打开了那些文件?或者说是,这个进程 和 那些文件有什么关系?  进程 如何和 自己打开的文件做关联的?    这些都是需要 进程 的 PCB对象当中需要去维护 的。

进程PCB 当中会存在一个指针,比如是 struct files_struct* f ; 这个

源码当中是这样写的:

这个指针指向一个 files_struct 结构体对象,在这个 files_struct 结构体对象 当中有一个数组 -- struct file * fd_array[]

struct file * fd_array[]  数组是一个指针数组,数组当中每一个元素都指向一个文件对象,而,在这个数组当中的文件对象 所 对应的文件,就是这个进程所 打开的文件

 如上如图所示,这个 数组维护的就是一个一个 文件对象,所以我们使用 open()函数之时,open()函数的返回值就是一个 int 类型的返回值,其实对应的就是 这个 struct file * fd_array[]  数组 下标。而这个 struct file * fd_array[]  数组 我们称之为 -- 文件描述符表

在这个 文件描述符表当中就存储了 这个进程所打开的 各个文件的 struct file 文件的对象的地址

 所以,当我们调用open()函数打开一个文件,那么 就会帮这个文件创建一个 struct file 文件对象,然后,在 struct file * fd_array[]  数组当中,找到一个没有被使用过的 成员位置,把这个文件对象的 地址 存储到这个成员位置。

然后,把这个 成员 的下标,返回给用户。


 所以,之所以 被打开的文件(被加载到内存当中文件)为什么要单独用一个双链表来连接起来呢?

因为 ,进程可能会中途退出,但是文件可能还会被访问修改,而且打开文件,把文件内容加载到内存当中是操作系统做的事情,不是进程做的事情,进程只能向操作系统申请某一个文件的打开,但是不能直接访问到这个文件。

所以,关于文件的加载和操作系统所做的事情,那么操作系统就要用 一个数据结构来关联 为各个文件创建的 文件对象。方便操作系统进行管理。

进程要想和 操作系统维护的 文件对象数据结构 的话,就要用到上述所说的  files_struct 结构体对象。在这个 files_struct 结构体对象 当中有一个数组 -- struct file * fd_array[] 。这个数组就存储了这个进程当前所打开的文件的文件对象的地址。所以,就用这个数组的下标的方式 和 操作系统维护的 文件对象数据结构 联系起来了。

 stdin,stdout,stderr 三个标准输入输出流的 文件描述符

在上图的程序当中,fd1 - fd4 是分别打开的四个文件的 下标返回值。那么输出结果是什么呢?

输出:
 

 发现,输出结果是 3 - 6 四个连续的下标,但是,fd1 不是我们打开的第一个文件吗?为什么fd 是从 3 开始而不是 从 0 号开始存储呢?

是不是 前三个 (0 - 2 下标的三个位置)已经存储了其他的文件了呢?

是的

在上述我们说过,运行一个C语言程序,默认就会打开三个标准输入输出流, stdin,stdout,stderr。而这  三个 标准输入输出流 其实本质上就是  文件

 而这 stdin,stdout,stderr 三个 标准输入输出流的名字,其实是 C语言当中规定的,不是操作系统当中规定的

操作系统当中,对于文件,只认 fd(文件描述符)。

 这三个对应的输入输出流的, stdin,stdout,stderr  对应 fd 就是 0 ,1 , 2

所以,对于 stdout,stderr   两个文件当中写入数据的话,程序执行就会直接 在屏幕上输出我们在文件当中写入的内容:
 

输出:

 同理,我们还可以从 stdin 文件当中读取数据到 程序当中,使用 read()函数可以从文件当中读取数据出来:
 

输出:
 

在上两个例子当中,我们什么文件都没有打开,(0,1,2)三个 标准输出输入的文件是 默认打开的。


操作系统当中的  三个标准输入输出流

 stdin,stdout,stderr 三个标准输入输出流 是 C语言的特性吗?

肯定不是

各种语言肯定会已有自己的  三个标准输入输出流,这  三个标准输入输出流 不是C语言特有的。

所以,其实不是 C语言的程序在运行之时 自动打开 这 三个标准输入输出流。

  • 而是,这三个 标准输出输入流 文件,在操作系统启动之时,就已经被打开了
  • 所有的进程,只是把操作系统在 文件对象双链表当中,找到这三个文件,把这个三个文件 链接到 自己的  文件描述符表 当中。

 因为使用进程 和调试进程的 用户和程序员,就是需要程序的输入输出来查看 进程运行结果


 在 C 当中的 FILE 结构体,是 C库当中自己封装的一个结构体。因为操作系统当中只认 fd 文件描述符,所以在 FILE 这个结构体当中,一定封装得 有 fd 文件描述符

 而, C 当中的 stdin,stdout,stderr 三个标准输入输出流 他们的类型就是 FILE 结构体,所以在这 stdin,stdout,stderr 三个标准输入输出流  当中,一定有 对应的 保存 fd 文件描述符  的 成员属性

 所以,我们现在就来验证一下这 stdin,stdout,stderr 三个标准输入输出流 当中的  fd 文件描述符:

程序输出:
 

发现:

 stdin,stdout,stderr 三个标准输入输出流 对应的 fd 就是 0 , 1 , 2。

 同样的,如果使用 close ()接口之间关闭了 fd 为   某一个 文件(0 , 1 , 2,那么就相当于是这个文件没有在这个进程当中打开了

 输出:

发现程序没有任何输出,因为 printf()函数就是在 stdout 当中输出,所以,这个 stdout 文件已经被关闭了,就不能在输出了 printf()函数底层必定用了 1 号文件

 其实这里的 printf()函数是写入成功了,已经在 操作系统当中的 显示器文件 当中输入了,但是当前进程已经把这个 显示器文件 从 数组当中剔除了,不能再打印了。

操作系统 文件对象双链表 中的 引用计数

 操作系统维护的 文件对象双链表 当中的文件对象,肯定是被很多的进程所指向的。一个文件对象,可能要多很多个 进程所服务。

比如 stdin,stdout,stderr 三个标准输入输出流 ,这三个文件,就是要本很多文件所使用的。

那么,操作系统如何知道一个文件被多少个 进程所使用呢?

答案就是使用 引用计数

 在 每一个文件对象当中都有一个 count 的字段,这个字段就 记录了 当前有多少 个 文件描述符指向这个文件对象。

 如果 count 字段不为了0,说明当前,虽然有进程 不再引用这个 文件对象了,但是还有进程 在引用 这个文件对象,那么就不能释放这个 文件对象空间。必须要等到 count 为0 才能释放这个文件对象空间。


 所以,一个进程 关闭文件 打开文件,本质上其实就是 把 这个文件对象当中的 count 字段           -- 或者 ++。修改 进程自己的 文件描述符表当中 对应下标当中的指针,比如 置空一个 或者 增加一个 下标位置的 指针。

在 各种语言当中 的 文件流,本质其实就是 用 结构体 或者 类 等等的方式把 fd 文件描述符 封装起来了,其中一定是有  fd 文件描述符 的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Windows 10 下使用Visual Studio 2017 编译CEF SDK

1.下载CEF SDK 由于需要跑在32位的机器&#xff0c;所以选择下载32位的SDKCEF Automated Builds 选择 Current Stable Build (Preferred) &#xff0c;这是当前稳定版本&#xff0c;CEF版本118 下载成功解压 2.下载编译工具 CMake 下载地址&#xff1a;CMake 配置CMake指向…

n-gram语言模型——句子概率分布计算与平滑

n-gram语言模型——句子概率分布计算与平滑 前言 语言模型 等价假设 n元语法 句子概率分布计算方式 数据平滑 Lidstone平滑(1-gram) Laplace平滑(1-gram) 附上两种平滑在1-gram下代码 Lidstone平滑与Laplace平滑(2-gram) 附上两种平滑在2-gram下代码 前言 语言模型…

黑窗口连接远程服务

ssh root192.168.x.x 回车输入密码 查看docker docker ps 停止正在运行的服务 docker stop xxxxx 删除服务 docker rm xxxxx 查看镜像 docker images 删除镜像 docker rmi xxxxx 删除镜像 启动并运行整个服务 docker compose up -d jar包名称 idea 使用tcp方式连接docker 配置d…

【扩散模型】实战:创建一个类别条件扩散模型

创建一个类别条件扩散模型 1. 配置和数据准备2. 创建一个以类别为条件的UNet模型3. 训练和采样 本文介绍一种给扩散模型添加额外条件信息的方法。具体地&#xff0c;将在MNIST数据集上训练一个以类别为条件的扩散模型。并且可以在推理阶段指定想要生成的是哪个数字。 1. 配置和…

Navicat 解放双手的自动运行功能

Navicat 的自动运行功能可以帮助用户自动化重复性和周期性的任务&#xff0c;提高工作效率和数据安全性。用户可以根据自己的需求设置自动运行的任务和计划&#xff0c;以确保数据库管理和数据操作的顺利进行。为帮助用户更便捷、更直观地了解自动运行功能以及电子邮件通知的操…

烟草5G智慧工厂数字孪生可视化平台,赋能烟草工业数字化智慧转型

随着卷烟工厂提质增效需求增强&#xff0c;信息化建设推进及生产制造系统智能化改革发展&#xff0c;各生产单元逐步升级完善数字化&#xff0c;最终实现智能制造成为必然趋势。因此&#xff0c;5G卷烟加工工厂的数字化转型迫在眉睫。中国烟草制造行业正迈向全新的市场经济时代…

win11 idea 错误: 找不到或无法加载主类

买了新电脑win11系统&#xff0c;配置环境之后运行项目&#xff0c;始终运行不起来&#xff0c;一直报 刚开始以为是环境没装好&#xff0c;但是我创建其他项目运行时是正常的 纠结了好久突然发现&#xff0c;是不是因为项目路径中有中文造成的找不到编译文件 最后把项目改为…

汽车标定技术(九)--标定常量与#pragma的趣事

目录 1. 不添加#pragma语句 2. 添加#pragma语句 3. 标定量只给flash空间&#xff0c;不给ram指定空间 4. 总结 在之前不会使用overlay机制的时候&#xff0c;我们想要做汽车标定&#xff0c;标定常量编译出来的地址一般都应该是ram的地址&#xff0c;而且在链接文件中都会指…

HTML5学习系列之简单使用1

HTML5学习系列之简单使用1 前言基础显示学习定义网页标题定义网页元信息定义网页元信息定义文档结构div元素di和classtitlerole注释 总结 前言 下班加班期间的简单学习。 基础显示学习 定义网页标题 <html lang"en"> <head> <title>从今天开始努…

内存缓存系统

胤凯 (oyto.github.io)&#xff0c;欢迎到我的博客阅读。 今天我们围绕一个面试题来实现一个内存缓存系统。 面试题内容 1. 支持设置过期时间&#xff0c;精度到秒 2. 支持设置最大内存&#xff0c;当内存超出时做出合理的处理 3. 支持并发安全 4. 按照以下接口要求实现 typ…

【poi导出excel模板——通过建造者模式+策略模式+函数式接口实现】

poi导出excel模板——通过建造者模式策略模式函数式接口实现 poi导出excel示例优化思路代码实现补充建造者模式策略模式 poi导出excel示例 首先我们现看一下poi如何导出excel&#xff0c;这里举个例子&#xff1a;目前想要导出一个Map<sex,List>信息&#xff0c;sex作为…

使用Dockerfile依赖maven基础镜像部署springboot的程序案例

1、准备springboot Demo代码 就一个controller层代码&#xff0c;返回当前时间及hello world 2、项目根目录下&#xff0c;新建DockerFile文件 注意&#xff0c;等本地配置完毕后&#xff0c;Dockerfile文件需要与项目helloworld同级&#xff0c;这里先放项目里面 3、docker …

【MATLAB源码-第73期】基于matlab的OFDM-IM索引调制系统不同子载波数目误码率对比,对比OFDM系统。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 OFDM-IM索引调制技术是一种新型的无线通信技术&#xff0c;它将正交频分复用&#xff08;OFDM&#xff09;和索引调制&#xff08;IM&#xff09;相结合&#xff0c;以提高频谱效率和系统容量。OFDM-IM索引调制技术的基本思想…

Flink SQL自定义标量函数(Scalar Function)

使用场景&#xff1a; 标量函数即 UDF&#xff0c;⽤于进⼀条数据出⼀条数据的场景。 开发流程&#xff1a; 实现 org.apache.flink.table.functions.ScalarFunction 接⼝实现⼀个或者多个⾃定义的 eval 函数&#xff0c;名称必须叫做 eval&#xff0c;eval ⽅法签名必须是 p…

快速入门安装及使用git与svn的区别常用命令

一、导言 1、什么是svn&#xff1f; SVN是Subversion的简称&#xff0c;是一个集中式版本控制系统。与Git不同&#xff0c;SVN没有分布式的特性。在SVN中&#xff0c;项目的代码仓库位于服务器上&#xff0c;团队成员通过向服务器提交和获取代码来实现版本控制。SVN记录了每个…

Hbuilder打包项目为h5

Hbuilder打包项目为h5 manifest.json 配置 修改 web 配置下的 页面标题、路由模式、运行的基础路径 发行 H5 发行 填入网站标题和网站域名 编译 编译完成之后存放在 unpackage/dist/build/h5 目录下

Day26力扣打卡

打卡记录 搜索旋转排序数组&#xff08;二分&#xff09; 链接 class Solution {int findMin(vector<int> &nums) {int left -1, right nums.size() - 1; // 开区间 (-1, n-1)while (left 1 < right) { // 开区间不为空int mid left (right - left) / 2;if…

医学图像 ABIDE 等数据集 .nii.gz Python格式化显示

nii.gz 文件 .nii.gz 文件通常是医学影像数据的一种常见格式&#xff0c;比如神经影像&#xff08;如脑部MRI&#xff09;。这种文件格式通常是经过gzip压缩的NIfTI格式&#xff08;Neuroimaging Informatics Technology Initiative&#xff09;。 要在Python中查看.nii.gz文…

设备零部件更换ar远程指导系统加强培训效果

随着科技的发展&#xff0c;AR技术已经成为了一种广泛应用的新型技术。AR远程指导系统作为AR技术的一种应用&#xff0c;具有非常广泛的应用前景。 一、应用场景 气象监测AR教学软件适用于多个领域&#xff0c;包括气象、环境、地理等。在教学过程中&#xff0c;软件可以帮助学…

黑客(网络安全)技术——高效自学1.0

前言 前几天发布了一篇 网络安全&#xff08;黑客&#xff09;自学 没想到收到了许多人的私信想要学习网安黑客技术&#xff01;却不知道从哪里开始学起&#xff01;怎么学 今天给大家分享一下&#xff0c;很多人上来就说想学习黑客&#xff0c;但是连方向都没搞清楚就开始学习…