【Linux】基础IO-文件描述符
【Linux】基础IO
- C语言的文件接口
- 文件的初步理解
- 文件IO的系统接口
- 打开文件
- write
- read
- 文件描述符fd
- 语言层的fd
- 文件描述符的分配规则
- 重定向和缓冲区的理解
- 重定向
- 缓冲区
- 作用
- 刷新策略
- C语言的缓冲区
- 模拟实现重定向
- 检查是否是重定向
- 执行命令
- 0、1、2的作用
C语言的文件接口
这里我们简单回顾一下C语言的文件操作
使用 “w” 模式打开文件 log.txt,如果文件不存在就创建文件;文件存在,打开文件后清空内容
使用 fprintf 向文件中写入字符串
int main()
{ const char* message = "hello Linux!"; FILE* fp = fopen("log.txt", "w"); fprintf(fp, "%s\n", message); fclose(fp); return 0;
}
每次使用 w 模式打开文件都会清空文件内容,如果先要追加内容就要使用 a 模式打开文件
FILE* fp = fopen("log.txt", "a");
C语言文件操作:
- 使用 w 模式打开文件时,文件不存在就创建;文件存在就清空内容
- 使用 a 模式打开文件时,会在文件原有内容基础上追加内容
Linux 的重定向操作符与此有异曲同工之妙:
- >:将数据重定向到指定文件,如果文件不存在就创建,存在就清空内容
- >>:将数据追加到指定文件
由此我们可知,重定向也是在打开文件,将数据写入到文件
文件的初步理解
虽然我们可以使用接口来进行文件相关的操作,但是我们真的理解文件吗?
我们通过上面的程序打开了文件,其而程序启动后就会被加载为进程,所以打开文件的本质就是进程打开了文件,是 CPU 执行我们写的代码
而一个进程可以打开多个文件吗?当然可以,就是多调用几次接口嘛。也就是说一个进程可以打开多个文件,而系统中的进程肯定不止一个,那么系统内就会存在大量的文件。所以就需要操作系统将这些文件统一管理起来,学习了进程后,我们猜想可能会有类似于 PCB 的数据结构,管理这些数据结构就是管理文件。关于这一点我们后面就知道了,这里先放一放
当我们创建一个空文件时,它会占用空间吗?会占用,虽然它的内容是空的,但是还是需要空间来储存文件属性的,例如文件创建时间,文件类型等。也就是说文件=属性+内容
文件IO的系统接口
除了C语言的文件接口,我们还可以使用系统接口进行文件访问
打开文件
#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:标记位,打开文件的方式,为什么是一个 int 呢?——这个 int 是位图,打开方式可以是多个,传递时进行或运算就可以将多种打开方式一并传递给 flags
- O_RDONLY:只读打开
- O_WRONLY:只写打开
- O_RDWR:读写打开
- 这三个常量,必须指定一个且只能指定一个
- O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
- O_APPEND:追加写
- O_TRUNC:打开时清空文件内容
mode:定义新创建文件的权限,例如要求所有人可以读写:0666
返回值:
失败返回 -1;成功返回文件描述符号,这里先不管,作用类似于C语言的FILE*,用来找到打开的文件
write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
fd:文件描述符,可以找到打开的文件
buf:缓冲区首地址,可以理解为要写入的数据的首地址
count:要写入多少字节的数据
返回值:实际写了多少字节数据
现在将我们上面代码中的C语言接口替换为系统调用接口
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h> int main()
{ const char* message = "hello Linux!\n";// 打开 int fd = open("log.txt", O_WRONLY | O_CREAT, 0666); // 写入数据write(fd, message, strlen(message)); close(fd); // 关闭文件描述符指向的文件 return 0;
}
read
与 write 的使用类似
int main()
{const char* message = "hello Linux!\n";// 只读打开int fd = open("log.txt", O_RDONLY);char buf[1024];// 将数据读入到 bufread(fd, buf, strlen(message));printf("%s",buf);return 0;
}
以上就是关于文件的系统接口的简单使用,我们可以感觉到和C语言文件接口的使用很相似。
而实际上语言是给我们用户层面使用的,而用户是不被允许直接修改操作系统的数据的,所以才会有系统调用接口供我们使用,而C语言的文件接口就是封装的系统调用接口
文件描述符fd
可以多打开几个文件,看看这 fd 的值到底是怎么样的:
int main()
{ int fd1 = open("fd1.txt", O_WRONLY|O_CREAT); printf("%d\n", fd1); int fd2 = open("fd2.txt", O_WRONLY|O_CREAT); printf("%d\n", fd2); int fd3 = open("fd3.txt", O_WRONLY|O_CREAT); printf("%d\n", fd3); close(fd1);close(fd2);close(fd3);return 0;
}
可以看到我们打开的三个文件的描述符分别是 3、4、5,难道文件描述符是从 3 开始的吗?——当然不是,文件描述符是从 0 开始的,而 0、1、2 这三个描述符已经被其他文件占用了,它们分别是:
- 0:标准输入,键盘
- 1:标准输出,显示器
- 2:标准错误,显示器
在C语言中,我们知道,当程序启动时,会自动打开三个文件:stdin、stdout、stderr,这就是对标的 0、1、2 三个文件描述符
那么说了这么多,文件描述符它到底是什么呢?这里我们就要回归到操作系统是怎么组织管理文件这个问题了
实际上,打开一个文件后,就会在系统内核中创建一个名为 strcut file 的数据结构,file 中储存着文件的属性
而文件=属性+内容,打开文件的同时,会开辟一块空间来当作文件的缓冲区,而打开文件后,无论是读还是写,都会将文件的内容从硬盘加载到缓冲区,file 会指向这一块空间
系统中不可能只存在一个打开的文件,不同的文件的 file 会相互链接在一起,形成一个链表
这么多的文件,一个进程可能打开很多文件,那么进程如何知道自己打开的是哪一个文件呢?
进程 PCB 中,存在一个指针 struct files_struct* files,指向了一张表 files_struct。这张表最重要的部分就是包含了一个指针数组 struct flie* fd_array[],数组中每个元素都是一个指针,指向了打开文件,这个数组就叫文件描述符表
而数组的下标就是文件描述符,凭借文件描述符就可以找到打开的文件
现在我们理解了什么是文件描述符,那么为什么 0、1、2 这三个东西是默认打开的?它们又不是文件,而是硬件啊?
这些硬件在冯诺依曼体系中属于IO外设,虽然是硬件,但是在 Linux 看来:一切皆文件
一切皆文件,这些外设也可以抽象为一个个数据结构 struct device,其中存储着设备的名称、类型、状态等等。也就是说 device 中存储的是设备的属性,那么就可以被 struct file 指向,因为 file 中存储的就是文件的属性
而想使用这些设备,例如读取键盘的数据,向屏幕写入数据等等,需要特定的方法,这些方法由驱动层提供。而我们想从缓冲区写出数据到设备或者将设备的数据读入到缓冲区,势必离不开这些方法。所以这些方法也应该放到 file 中,这些方法是以函数指针的形式存入到 file 中的,且是一个集合,称作方法集
这样加一层后,虽然每个设备的方法、属性不同,但是都被封装到了 file 中,在 file 层面来看是没有这些不同的
所以现在我们理解了外设可以被加载为文件,我们可以通过一些手段查看一下 0、1、2 指向的设备
先启动一个进程并获取它的 pid
int main()
{pid_t pid = getpid(); while(1) { sleep(1); printf("pid:%d\n", pid); } return 0;
}
然后到 etc/proc/进程id 目录下,可以看到有一个 fd 文件夹,里面就存放着此进程打开的文件的 fd
可以看到,确实有0、1、2 三个文件描述符,它们都指向了一个设备,这是因为我这里的机器是云服务器,所以指向的设备是同一个终端。此时我们可以编写代码,向这个设备写入数据,就会看到终端有数据输出
int main()
{const char* msg = "hello!\n";int fd = open("/dev/pts/1", O_WRONLY);if (fd < 0) return -1; else{ while(1) { sleep(1); write(fd,msg, strlen(msg)); } } return 0;
}
语言层的fd
了解了什么是 fd 之后,我们知道,文件的操作离不开 fd。而其他语言的文件操作是对系统调用接口的封装,所以肯定离不开对 fd 进行封装
例如C语言,打开文件时会返回一个指针 FILE*,后面对文件的操作离不开这个指针。FILE 就是C语言封装的一个结构体,里面包含了文件描述符 fd
我们可以把 FILE* 中的数据打印出来看看
int main()
{ FILE* pf1 = fopen("fd1.txt", "w"); printf("pf1:%d\n", pf1->_fileno); FILE* pf2 = fopen("fd2.txt", "w"); printf("pf2:%d\n", pf2->_fileno); FILE* pf3 = fopen("fd3.txt", "w"); printf("pf3:%d\n", pf3->_fileno); fclose(pf1); fclose(pf2); fclose(pf3); return 0;
}
可以看到,和之前使用系统调用打印出来的文件描述符一样
文件描述符的分配规则
我们已经知道,进程启动时会默认打开三个文件,0、1、2就会被占用,后面打开的文件的描述符都是从 3 开始
如果一开始就把 0、1、2这三个文件关掉,再再打开我们自己的文件,那么文件描述符又会怎么分配呢?
尝试关闭0、2,然后打开文件并打印文件描述符
int main()
{ close(0); close(2); int fd1 = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd1 < 0) return 1; int fd2 = open("log2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd2< 0) return 1; printf("this is fd1:%d\n", fd1); fprintf(stdout, "this is fd2:%d\n", fd2); close(fd1); close(fd2); return 0;
}
从代码运行结果来看,文件描述符的分配规则是这样的:当一个新文件打开时,会从文件描述符表 fd_array 寻找最小的没有被占用的下标,作为新文件的文件描述符
那为什么只关闭0、2,不关闭1呢?因为 1 是标准输出,关闭了就看不到打印结果了。但是我们可以通过别的方式查看:关闭 1 以后,新打开一个文件,理论上这个文件的文件描述符就会分配 1,然后我们向这个文件中写入数据,不就可以查看结果了么
int main() { close(1);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) return 1; printf("this is fd:%d\n", fd);fprintf(stdout, "this is fd:%d\n", fd);fflush(stdout); // 刷新缓冲区 close(fd); return 0;
}
但是很奇怪的一点是:printf 是向标准输出写入数据,fprintf 我们也规定向标准输出写入数据,但是怎么都写到了 log.txt 中了?
上面已经说过,C语言的文件接口都是封装的系统调用,其中 stdout 是一个结构体FILE对象的指针,FILE结构体封装了文件描述符 fd,且 stdout 的 fd 为1。这样 stdout 就可以通过 fd 找到文件描述符为 1 的文件,但是此时文件描述符 1 指向的文件已经被我们更改了,不再指向标准输出,而是指向文件 log.txt
所以我们向 stdout 中写入数据,stdout 又拿着 fd 找到了文件 log.txt,将数据写入到文件中
那么代码中的 fflush(stdout) 又是什么作用呢?可不可以去掉呢?如果去掉,数据就会丢失
这是为什么呢?——文件在内核中有缓冲区,而C语言也有语言层的缓冲区,通常叫做用户级缓冲区,存在于结构体FILE中。我们使用语言层的文件接口写数据时,默认是向用户级缓冲区写数据,然后达成某些条件时,就会被刷新到内核层的缓冲区,而系统调用的 close 关闭文件时,会将内核缓冲区的数据刷入到硬盘,然后关闭文件
如果使用 close 关闭文件时数据还留在用户级缓冲区没有刷新,然后内核级缓冲区是空的,系统调用将内核缓冲区数据刷新到文件,导致了数据的丢失
重定向和缓冲区的理解
重定向
上面我们把本来要写入到标准输出的数据写到了文件中,这不就是重定向吗?但是这个重定向的实现方式有点挫啊,有没有优雅一点的方法呢?有的,系统中有个接口,可以更改文件描述符指向的内容
#include <unistd.h>
int dup2(int oldfd, int newfd);
描述:
简单来说,就是将 oldfd 的内容拷贝到 newfd,那么 oldfd 和 newfd 都会指向同一个文件。所以说,重定向的本质就是文件描述符下标内容的拷贝
例如,进程中默认打开 0、1、2三个文件,我们又打开自己的文件 log.txt,文件描述符为 fd。如果我们想把写入到标准输出的内容重定向到文件,就可以把文件描述符表中,fd 对应的内容拷贝到 1 号下标中,这样 1 号文件描述符指向的文件就是 log.txt 了,向标准输出写数据就是向 log.txt 写内容
实践一下:
int main() { // 打开 log.txtint fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) return 1; // 重定向dup2(fd, 1); // 写入数据到 stdprintf("this is fd:%d\n", fd); fprintf(stdout, "this is fd:%d\n", fd); fflush(stdout);close(fd); return 0;
}
缓冲区
作用
为什么语言层也要设置一个用户级缓冲区呢?我不可以直接将数据写到内核缓冲区吗?这不是多此一举吗?其实缓冲区也是有妙用的:
- 解耦:把用户和操作系统解耦,用户只需要将数据写到缓冲区即可,不需要操心怎么把数据搞到内核缓冲区,再刷新到硬盘
- 提高效率:多了一层不是还要拷贝吗?为什么还提高了效率了?——提高的是用户使用的效率
- 用户只需要向缓冲区读写数据即可,效率更高
- 系统调用是有消耗的,越少调用,效率越高。如果先把数据丢到用户级缓冲区,达成某个条件时,直接使用一次系统调用即可将数据刷新到内核缓冲区;内核缓冲区同理,先缓存数据,然后一次刷新到硬盘文件即可,极大减少了频繁使用系统调用的消耗
刷新策略
- 立即刷新:例如C语言的 fflush,系统调用的 fsync,都可以刷新缓冲区
- 行刷新:这个比较特殊,显示器采用的策略就是这样的,因为这样更符合用户的使用习惯
- 满了才刷新:普通文件
特殊情况:
- 程序结束,会自动刷新缓冲区。之前进程说过的 exit 会刷新缓冲区,_exit 则不会
- 强制刷新,类似于立即刷新
此时我们写一个代码,先使用语言层接口向标准输出打印数据,再使用系统调用打印数据。标准输出也就是屏幕,这里采用的刷新策略是行刷新
int main()
{ // C printf("hello,printf!\n");fprintf(stdout, "hello, fprintf!\n");// sysytem callconst char* msg = "hello write!\n"; write(1, msg, strlen(msg));return 0;
}
可以看到,确实是按照我们输出的顺序一行一行地刷新的。那么我们再将这些数据进行重定向输出到 log.txt 文件看看效果
输出的顺序发生了变化,这是为什么呢?这是因为我们进行了重定向输出,本质还是打开了 log.txt 文件,因为是文件,所以缓冲区刷新策略变为了满了才刷新
printf 和 fprintf 都是语言层的接口,所以默认将数据写入了用户级缓冲区,而 write 是系统接口,直接将数据写入到了内核缓冲区
代码中还有一个细节,没有关闭文件。根据刷新策略,程序结束时,用户级缓冲区的数据会被刷新到内核缓冲区,接着内核缓冲区的数据被刷新到硬盘的文件中。所以打印的顺序发生了变化
这时候我们再来添加一条语句,分别将数据输出到屏幕和文件
int main()
{ // C printf("hello,printf!\n");fprintf(stdout, "hello, fprintf!\n");// sysytem callconst char* msg = "hello write!\n"; write(1, msg, strlen(msg));fork(); // 创建子进程return 0;
}
输出到屏幕:
没什么变化,再来看看重定向输出到文件
为什么 printf 和 fprintf 多打印了一遍呢?问题肯定出在 fork() 上,创建了子进程
上面我们说过,在程序结束之前,printf 和 fprintf 一开始是在用户级缓冲区中的。然后父进程创建子进程,此时程序也要结束了,那么父子进程都会刷新用户级的缓冲区,也就是刷新了两次数据到内核缓冲区,所以才会有两对 printf 和 fprintf 的输出
C语言的缓冲区
说了这么多,我们可以看一下C语言的缓冲区长什么样子
在 /usr/include/stdio.h
而 struct _IO_FILE 在 /usr/include/libio.h
模拟实现重定向
把我们之前写的 shell-链接,模拟实现一下重定向的实现
检查是否是重定向
如果输入的命令是这样 ls -a -l > log.txt
在获取到用户输入命令后,需要进行检查是否具有重定向符号
// 获取用户输入命令
// ...CheckRedir(usercommand);// 分割命令行字符串
// ...
我们需要获取命令的重定向相关信息,例如重定向的类型,重定向的文件
// 重定向相关 #define No_Redir 0 #define In_Redir 1 // < #define Out_Redir 2 // > #define Add_Redir 3 // >>int redir_type = No_Redir; // 类型 char* filename = NULL; // 重定向文件名
接下来编写 CheckRedir
- 遍历字符串,寻找重定向符号
>
>>
<
- 找到之后,修改相应的重定向类型,然后把重定向符号改为 0,断开命令
- 跳过重定向后面的空格,寻找文件名
void CheckRedir(char cmd[])
{// 寻找重定向符号int pos = 0;int end = strlen(cmd);while(pos < end){// ls -a -l > log.txtif (cmd[pos] == '>'){if (cmd[pos + 1] == '>'){// 追加重定向redir_type = Add_Redir;cmd[pos++] = 0;pos++; // 跳过两个>// 跳过空格SkipSpace(cmd, pos);filename = cmd + pos;}else{// 输出重定向redir_type = Out_Redir;cmd[pos++] = 0;SkipSpace(cmd, pos);filename = cmd + pos;}}else if (cmd[pos] == '<'){// 输入重定向redir_type = In_Redir;cmd[pos++] = 0;// 跳过空格,寻找文件名SkipSpace(cmd, pos);filename = cmd + pos;}else{pos++;}}
}
这里写一个跳过空格的宏函数
#define SkipSpace(cmd, pos) do{\while(1)\{\if(cmd[pos] == ' ') pos++;\else break;\}\}while(0)
先把下面的代码屏蔽,测试一下:
看上去是没什么问题,那么接下来写执行重定向的命令
执行命令
在子进程执行命令之前,打开相关文件,然后调用 dup2 即可
void ExecuteCmd()
{pid_t id = fork();if (id < 0) exit(1);else if (id == 0){// 重定向,打开文件if (filename != NULL){if (redir_type == In_Redir){// 输入重定向int fd = open(filename, O_RDONLY);dup2(fd, 0);}else if (redir_type == Out_Redir){// 输出重定向int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);dup2(fd, 1); }else if (redir_type == Add_Redir){// 追加重定向int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);}}// childexecvp(gArgv[0], gArgv);exit(errno); // 执行失败}// fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){// wait sucesslastcode = WEXITSTATUS(status);if (lastcode)printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);}
}
测试:
输出重定向
追加重定向
0、1、2的作用
这里我们总结一下 0、1、2 的作用。我们写的程序不就是对数据进行操作吗,例如计算、存储等。那么这些数据从哪里来,又到哪里去呢?
- 数据从标准输入来,也就是0
- 数据要输出给用户看,也就是输出到标准输出1
那么 2 又有什么作用呢?1 和 2 不都是指向的标准输出吗?
我们写的程序,输出信息一般有两种:正确的和错误的。一般进行打印时,都会打印在屏幕上,如下:
int main()
{ fprintf(stdout, "hello fprintf, stdout\n"); fprintf(stderr, "hello fprintf, stderrt\n"); return 0;
}
如果我们使用>
,将输出信息重定向到 log.txt 文件中,会发生奇怪的现象:
向 stderr 写入的数据并没有输出到文件中,依然输出在了屏幕。这是为什么呢?——因为>
符号默认情况下是标准输出重定向,也就是只把文件描述符表中 1 号下标的内容修改,指向了 log.txt 文件;而 2 号下标的内容依然指向屏幕
那么如何把 2 号下标的内容也重定向呢?正确写法是这样的:
其中 1>log.txt 就不说了,而 2>&1 的意思就是把 1 号下标的内容拷贝给 2 号下标。那么 1、2都会指向 log.txt 文件
还可以这样,把正确信息和错误信息输出到两个不同文件中
C语言的 perror 接口,默认就是向 stderr,也就是系统的 2 中写入数据
到这里我们也可以体会到 2 的作用了,就是可以将程序输出的正确信息和错误信息输入到不同文件,使用重定向即可分离输出
相关文章:
【Linux】基础IO-文件描述符
【Linux】基础IO C语言的文件接口文件的初步理解文件IO的系统接口打开文件writeread 文件描述符fd语言层的fd文件描述符的分配规则重定向和缓冲区的理解重定向缓冲区作用刷新策略C语言的缓冲区 模拟实现重定向检查是否是重定向执行命令 0、1、2的作用 C语言的文件接口 这里我们…...
IDEA2024创建一个spingboot项目
以下是创建一个基本的 Spring Boot 项目的步骤和示例: 初始化一个springboot工程其实有许多方法,笔者这里挑了一个最快捷的方式搭建一个项目。我们直接通过官方平台(start.spring.io)进行配置,然后下载压缩包就可以获取…...
第R4周:LSTM-火灾温度预测(TensorFlow版)
>- **🍨 本文为[🔗365天深度学习训练营]中的学习记录博客** >- **🍖 原作者:[K同学啊]** 往期文章可查阅: 深度学习总结 任务说明:数据集中提供了火灾温度(Tem1)、一氧化碳浓度…...
OpenCV相机标定与3D重建(5)鱼眼镜头畸变校正的函数estimateNewCameraMatrixForUndistortRectify()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 估计用于去畸变或校正的新相机内参矩阵。 cv::fisheye::estimateNewCameraMatrixForUndistortRectify 是 OpenCV 中用于鱼眼镜头畸变校正的一个函…...
RabbitMQ入门
目录 MQ 相关概念 什么是MQ MQ 的作用 什么是RabbitMQ RabbitMQ的安装 安装 erlang 安装 RabbitMQ 安装 RabbitMQ 管理界面 开放云服务器端口 访问 RabbitMQ 管理界面 RabbitMQ 的用户角色 RabbitMQ的工作流程 Producer 和 Consumer Connection 和 Channel Vi…...
电商项目高级篇06-缓存
电商项目高级篇06-缓存 1、docker下启动redis2、项目整合redis 缓存 流程图: data cache.load(id);//从缓存加载数据 If(data null){ data db.load(id);//从数据库加载数据 cache.put(id,data);//保存到 cache 中 } return data;在我们的单体项目中可以用Map作…...
英伟达发布 Edify 3D 生成模型,可以在两分钟内生成详细的、可用于生产的 3D 资源、生成有组织的 UV 贴图、4K 纹理和 PBR 材质。
英伟达发布 Edify 3D 生成模型,可以利用 Agents 自动判断提示词场景中需要的模型,生成后将他们组合为一个场景。 Edify 3D 可以在两分钟内生成详细的、可用于生产的 3D 资源、生成有组织的 UV 贴图、4K 纹理和 PBR 材质。 相关链接 论文:htt…...
数字电路——触发器2(集成触发器,相互转化)
集成触发器基于RS触发器和钟控触发器,想要了解可以参考文章RS和钟控触发器。 一、集成触发器 这里介绍的集成触发器是将其他类型的触发器与RS触发器相结合 1.1 集成D触发器 1.逻辑符号 区分同步和异步工作: 当同步时,和都为1,…...
拥抱极简主义前端开发:NoCss.js 引领无 CSS 编程潮流
在前端开发的世界里,我们总是在不断追寻更高效、更简洁的方式来构建令人惊艳的用户界面。而今天,我要向大家隆重介绍一款具有创新性的工具 ——NoCss.js,它将彻底颠覆你对传统前端开发的认知,引领我们进入一个全新的无 CSS 编程时…...
CentOS 7 安装部署 KVM
1.关闭虚拟机 打开相关选项 打开虚拟机centos7 连接xshell 测试网络,现在就是没问题的,因为我们要使用网络源 安装 GNOME 桌面环境 安装KVM 模块 安装KVM 调试工具 构建虚拟机的命令行工具 qemu 组件,创建磁盘、启动虚拟机等 输入这条命令,…...
【es6】原生js在页面上画矩形添加选中状态高亮及显示调整大小控制框(三)
接上篇文章,这篇实现下选中当前元素显示调整大小的控制框,点击document取消元素的选中高亮状态效果。 实现效果 代码逻辑 动态生成控制按钮矩形,并设置响应的css // 动态添加一个调整位置的按钮addScaleBtn(target) {const w target.offsetWidth;con…...
适用于学校、医院等低压用电场所的智能安全配电装置
引言 电力,作为一种清洁且高效的能源,极大地促进了现代生活的便捷与舒适。然而,与此同时,因使用不当或维护缺失等问题,漏电、触电事件以及电气火灾频发,对人们的生命安全和财产安全构成了严重威胁…...
通信原理实验:抽样定理实验
目录 一、实验目的和要求 二、实验内容和原理 实验器材 实验原理 三、实验步骤 (一)实验项目一:抽样信号观测及抽样定理验证 四、实验记录与处理 结论: 辅助学习资料: 五、实验结果及分析 一、实验目的和要求 了解抽样定理在通信系统中的重要性。掌握自然抽样及…...
Http 请求协议
HTTP的请求协议 请求数据格式: 请求行 请求数据的第一行,包含请求方式、资源路径、协议及版本。 请求头 从请求数据的第二行,以key: value的格式 常见的请求头 Host:请求的主机名,如:localhost:8080&#x…...
Java中的JSONObject详解
文章目录 Java中的JSONObject详解一、引言二、JSONObject的创建与基本操作1、创建JSONObject2、添加键值对3、获取值 三、JSONObject的高级特性1、遍历JSONObject2、从字符串创建JSONObject3、JSONObject与JSONArray的结合使用4、更新和删除键值对 四、错误处理1. 键值存在性检…...
day01
Hm-Footer.vue <template><div class"hm-footer">我是hm-footer</div></template><script>export default {}</script><style>.hm-footer{height:100px;line-height:100px;text-align:center;font-size:30px;background-…...
shell查看服务器的内存和CPU,实时使用情况
要查看服务器的内存和 CPU 实时使用情况,可以使用以下方法和命令: 1. 使用 top 运行 top 命令以显示实时的系统性能信息,包括 CPU 和内存使用情况。 top按 q 退出。输出内容包括: CPU 使用率:位于顶部,标…...
【后端面试总结】MySQL索引
数据库索引不只一种实现方法,但是其中最具代表性,也是我们面试中遇到最多的无疑是B树。 索引为什么选择B树 数据量很大的查找,是不能直接放入内存的,而是需要什么数据就通过磁盘IO去获得。 红黑树,AVL树等二叉查找树…...
vue3 reactive响应式实现源码
Vue 3 的 reactive 是基于 JavaScript 的 Proxy 实现的,因此它通过代理机制来拦截对象的操作,从而实现响应式数据的追踪。下面是 Vue 3 的 reactive 源码简化版。 Vue 3 reactive 源码简化版 首先,我们需要了解 reactive 是如何工作的&…...
STL之算法概览
目录 算法概览 算法分析与复杂度标识O() STL算法总览 质变算法mutating algorithms----会改变操作对象之值 非质变算法nonmutating algorithms----不改变操作对象之值 STL算法的一般形式 算法的泛化过程 算法概览 算法,问题之解法也。 以有限的步骤࿰…...
数据库中的视图
数据库中的视图 什么是视图创建视图使⽤视图修改数据注意事项 删除视图视图的优点 什么是视图 视图是⼀个虚拟的表,它是基于⼀个或多个基本表或其他视图的查询结果集。视图本⾝不存储数 据,⽽是通过执⾏查询来动态⽣成数据。⽤户可以像操作普通表⼀样使…...
【设计模式】【行为型模式(Behavioral Patterns)】之责任链模式(Chain of Responsibility Pattern)
1. 设计模式原理说明 责任链模式(Chain of Responsibility Pattern) 是一种行为设计模式,它允许你将请求沿着处理者链进行发送。每个处理者都可以处理请求,或者将其传递给链上的下一个处理者。这种模式使得多个对象都有机会处理请…...
Angular面试题汇总系列一
1. 如何理解Angular Signal Angular Signals is a system that granularly tracks how and where your state is used throughout an application, allowing the framework to optimize rendering updates. 什么是信号 信号是一个值的包装器,可以在该值发生变化时…...
【面试分享】主流编程语言的内存回收机制及其优缺点
以下是几种主流编程语言的内存回收机制及其优缺点: 一、Java 内存回收机制: Java 使用自动内存管理,主要通过垃圾回收器(Garbage Collector,GC)来回收不再被使用的对象所占用的内存。Java 的垃圾回收器会定…...
Java中的多线程
文章目录 Java中的多线程一、引言二、多线程的创建和启动1、继承Thread类2、实现Runnable接口 三、线程的常用方法1、currentThread()和getName()2、sleep()和yield()3、join() 四、线程优先级五、使用示例六、总结 Java中的多线程 一、引言 在Java中,多线程编程是…...
TypeError: issubclass() arg 1 must be a class
TypeError: issubclass() arg 1 must be a class 报错代码: import spacy 原因: 库版本错误, 解决方法: pip install typing-inspect0.8.0 typing_extensions4.5.0 感谢作者: langchain TypeError: issubclass() …...
C语言实例之9斐波那契数列实现
1. 斐波那契数列简介 斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多・斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为 “兔子数列”。 它的特点是从第三…...
Flink 常用问题及常用配置(有用)
一、Flink 常用问题及常用配置 参数 示例 说明 execution.checkpointing.interval 3min Checkpoint 触发间隔 state.backend rocksdb / filesystem 用于设置statebackend类型, 默认会以内存为statebackend(无法支持大状态) taskmanager.memory.jvm-overhead.max 204…...
什么是内网穿透开发
文章目录 前言实现内网穿透的常见技术方法1. 反向代理与端口映射2. 第三方内网穿透服务3. 自建穿透服务4. VPN(虚拟专用网络) 内网穿透开发的关键点1. 安全性2. 性能3. 合法性和合规性 适用场景 前言 内网穿透开发是指将位于内网或防火墙后的应用服务&a…...
RabbitMQ简单应用
概念 RabbitMQ 是一种流行的开源消息代理(Message Broker)软件,它实现了高级消息队列协议(AMQP - Advanced Message Queuing Protocol)。RabbitMQ 通过高效的消息传递机制,主要应用于分布式系统中解耦应用…...
创建HTTPS网站
每天,我们都会听到网络上发生身份盗窃和数据侵权的案例,这导致用户对自己访问的网站更加怀疑。他们开始更加了解自己将个人信息放在哪里以及信任哪些类型的网站。了解如何使网站使用HTTPS变得比以往任何时候都更加重要。 解读缩略词:HTTP与HT…...
【DL笔记】神经网络轻量化(CV方向)的一些论文记录
现在大模型爆火,但俺这种组里只有10系显卡的下水道科研老鼠也要混毕业的,于是选择做小模型(x)。本人纯科研飞舞一个,刚入学有段时间爱看论文,今天有空把那会看到论文总结下。 轻量化,相关文章的…...
计算(a+b)/c的值
计算(ab)/c的值 C语言代码C语言代码Java语言代码Python语言代码 💐The Begin💐点点关注,收藏不迷路💐 给定3个整数a、b、c,计算表达式(ab)/c的值,/是整除运算。 输入 输入仅一行&…...
11.26作业
#include "test.h" #include <myhead.h>int main(int argc, const char *argv[]) {Student students[100]; // 假设最多有100个学生int select 0;int n 0; // 学生数量menu();while (1) {printf("请输入你想要的功能:");scanf("%…...
AdaPipe:动态规划解决显存和GPU在LLM计算中出现气泡问题
目录 AdaPipe:动态规划解决显存和GPU在LLM计算中出现气泡问题 0-5表示不同数据 大的方块表示:管道,便于理解了想成GPU 黄色方块表示显存 Stage表示Attention和FFN layer(Projection和MLP) 重计算和分区策略:细化了Attention和FFN layer Transformer中的管道 AdaPi…...
C++设计模式之组合模式中如何实现同一层部件的有序性
在组合模式中,为了实现同一层上部件的有序性,可以采取以下几种设计方法: 1. 使用有序集合 使用有序集合(如 std::list、std::vector 或其他有序容器)来存储和管理子部件。这种方法可以确保子部件按照特定顺序排列&am…...
QT QRadioButton控件 全面详解
本系列文章全面的介绍了QT中的57种控件的使用方法以及示例,包括 Button(PushButton、toolButton、radioButton、checkBox、commandLinkButton、buttonBox)、Layouts(verticalLayout、horizontalLayout、gridLayout、formLayout)、Spacers(verticalSpacer、horizontalSpacer)、…...
【IEEE独立出版 | 厦门大学主办】第四届人工智能、机器人和通信国际会议(ICAIRC 2024,12月27-29日)
第四届人工智能、机器人和通信国际会议(ICAIRC 2024) 2024 4th International Conference on Artificial Intelligence, Robotics, and Communication 重要信息 会议官网:www.icairc.net 三轮截稿时间:2024年11月30日23:59 录…...
Dubbo的RPC泛化调用
目录 一、RPC泛化调用的应用场景 二、Dubbo RPC泛化调用的实现原理 三、Dubbo RPC泛化调用的实现步骤 四、示例代码 五、泛化调用怎么发现提供该接口的服务及服务的IP和端口? Dubbo的RPC泛化调用是一种在调用方没有服务方提供的API的情况下,对服务方…...
Java面试题、八股文学习之JVM篇
1.对象一定分配在堆中吗?有没有了解逃逸分析技术? 对象不一定总是分配在堆中。在Java等一些高级编程语言中,对象的分配位置可以通过编译器或运行时系统的优化来决定。其中,逃逸分析(Escape Analysis)是用于…...
Apache Maven Assembly 插件简介
Apache Maven Assembly 插件是一个强大的工具,允许您以多种格式(如 ZIP、TAR 和 JAR)创建项目的分发包。 该插件特别适用于将项目与其依赖项、配置文件和其他必要资源一起打包。 通过使用 Maven Assembly 插件,您可以将项目作为…...
3174、清除数字
3174、[简单] 清除数字 1、题目描述 给你一个字符串 s 。你的任务是重复以下操作删除 所有 数字字符: 删除 第一个数字字符 以及它左边 最近 的 非数字 字符。 请你返回删除所有数字字符以后剩下的字符串。 2、解题思路 遍历字符串: 我们需要逐个遍…...
【C#】C# resx方式实现多语言切换(静态切换)
1. 效果 中文界面 英文界面 2. 步骤 1. 添加resx文件 2. Form1.en-GB.resx内容 3. Form1.zh-CN.resx内容 4. Form1.cs修改(重点) using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using Syste…...
鸿蒙系统ubuntu开发环境搭建
在RISC-V等平台移植鸿蒙系统OpenHarmony,需要使用linux环境进行代码的编译,为兼顾日常办公需要,可采用WindowsUbuntu虚拟机的混合开发的环境,通过网络及文件夹共享,在主机和虚拟机之间共享文件数据。 工具准备&#x…...
TCP/IP协议攻击与防范
一、TCP/IP协议攻击介绍 1.1 Internet的结构 LAN:局域网 WAN:广域网 WLAN:无线局域网 私有IP地址与公有IP地址? 私有地址:A类:10.0.0.0~10.255.255.255 B类:172.16.0.0~172.31.255.255…...
1 ISP一键下载
BOOT0BOOT1启动模式说明0X用户Flash用户闪存存储器,也就是Flash启动10系统存储器系统存储器启动,串口下载11SRAM启动SRAM启动,用于在SRAM中调试代码 闪存存储器 是STM32 的内置FLASH,一般使用JTAG或者SWD模式下载程序时,就是下载…...
vue的理解
什么是vue vue是一套用于构建用户界面的渐进式框架,与其他框架不同的是,vue被设计为可以自底向上逐层应用,它也是创建单页面应用的web应用框架。vue的核心库只关注视图层,不仅易上手,还便于与第三方库或既有项目整合。…...
【Leetcode】3206.交替组1
题目描述: https://leetcode.cn/problems/alternating-groups-i/description/?envTypedaily-question&envId2024-11-26 题目示例: 解题思路 思路一: 1.如果color.size()小于等于2,则构不成环,直接返回结果…...
极狐GitLab 17.6 正式发布几十项与 DevSecOps 相关的功能【二】
GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料: 极狐GitLab 官网极狐…...
oracle小技巧-解决特殊密码字符而导致的exp错误
在使用oracle数据库的时候,我们经常会利用exp工具对某些表进行导出。但有些时候,因我们用户密码为安全性设有特殊字符,导致exp导出时候报:“EXP-00056和ORA-12154”,今天我们就分享下如何通过设置符号隔离的小技巧解决…...