Linux第七讲:基础IO
Linux第七讲:基础IO
- 1.什么是文件
- 2.文件操作的复习
- 2.1文件基本操作复习
- 2.2将信息输出到显示器,你有哪种方法
- 2.3stdin、stdout、stderror
- 2.4细节问题讲解
- 3.系统文件IO
- 3.1open函数使用
- 3.1.1理解标志位
- 3.1.2权限问题
- 3.1.3write和read接口介绍
- 3.1.4谈谈fd以及封装
- 3.1.5常见标志位的使用
- 3.2系统级别文件访问接口底层的运行原理(3.1.4问题一解释)
- 4.重定向
- 4.1文件描述符的分配规则
- 4.2文件重定向原理(dup2接口)
- 4.3向自定义shell中添加重定向操作
- 5.标准错误
- 6.理解“Linux下一切皆文件”
- 7.缓冲区
- 7.1缓冲区的概念
- 7.2缓冲区的作用
- 7.3深入理解缓冲区
- 8.libc库(C标准库)的简单实现
要理解系统级别的IO,我们要先做一些预备知识:
1.什么是文件
1.狭义上讲,文件被保存在磁盘中,而磁盘是外设,既是输入设备,又是输出设备,而磁盘对外设的输入和输出,简称IO
2.广义上讲,Linux下,一切皆文件,包括键盘、显示器、网卡、磁盘都是抽象化的过程,这些以后会讲
3.文件包括内容 + 属性,所以对于0kb的空文件,也是会占用磁盘空间的,因要要存储文件的属性,而所有对于文件的操作,本质就是对文件内容和文件属性的操作
4.从系统角度上讲,我们写的fopen、fclose函数,只有当进程运行,执行到fopen/fclose代码时,才会打开文件,对文件进行操作,所以对文件的操作,本质是进程对文件的操作,而要对文件进行操作,就必须要通过操作系统,我们写的fopen等操作,底层其实封装了操作系统对于文件的系统调用
5.当进程运行时,会打开很多的文件,也会关闭很多的文件,所以操作系统要把打开的文件管理起来,先描述,在组织,进行管理!
文件分为“内存级”文件(加载到内存,也就是被打开的文件)和“磁盘级”文件,我们下面讲的全部都是针对于内存级文件,磁盘级文件的讲解在后面会加以说明
2.文件操作的复习
2.1文件基本操作复习
int main()
{FILE* fp = fopen("log.txt", "w");if(fp == NULL){perror("fopen");exit(2);}const char* msg = "hello world!\n";int count = strlen(msg);while(count--){//每次向fp中写入1个msg长度的字符,从msg中写入fwrite(msg, 1, strlen(msg), fp); }//rewind(fp);char buf[1024];while(1){//每次从fp中读取1次,读取msg长度的字符,写入到buf中size_t s = fread(buf, 1, strlen(msg), fp);if(s > 0){buf[s] = '\0';printf("%s", buf);}if(!feof(fp)) break;//feof可以检测是否读取到了文件结尾}//fclose(fp);return 0;
}
上面的代码中,文件是以写入状态打开的,所以并不能执行读操作,但是我们需要知道读操作的操作过程
有了上面的知识,我们可以实现简单的cat命令:
int main(int argc, char*argv[])
{if(argc != 2){printf("argc error\n");return 1;}FILE* fp = fopen(argv[1], "r");if(!fp){perror("fopen!\n");exit(2);}char buf[1024];while(1){int s = fread(buf, 1, sizeof(buf), fp);if(s > 0){buf[s] = '\0';printf("%s", buf);}if(feof(fp)) break;}fclose(fp);return 0;
}
这样就可以快速拿到文件中的内容了:

2.2将信息输出到显示器,你有哪种方法
int main()
{printf("hello printf\n");const char* msg = "hello fwrite\n";fwrite(msg, 1, strlen(msg), stdout);fprintf(stdout, "hello fprintf\n");return 0;
}
2.3stdin、stdout、stderror
C语言默认会打开三种流,stdin、stdout、stderror,而这三种流的类型都是FILE*指针,分别指向标准输入设备、标准输出设备,stderror后面再讲,它们是全局的,所以默认会打开,这个后面再讲

2.4细节问题讲解

当我们输出重定向向log文件中进行字符输出时,发现log文件中的内容被清空了,这是因为fopen的w操作会先将文件中的内容清空:

所以说,输出重定向操作想要将内容输出到文件中,首先要打开文件,而底层封装的是fopen的w操作,所以首先会进行文件数据的清空,我们可以使用追加操作使得文件内容不被清空:
int main()
{FILE* fp = fopen("log.txt", "a");//a选项为追加if(!fp) exit(2);const char* msg = "追加数据\n";fprintf(fp, "%s", msg);fclose(fp);return 0;
}

对于输出重定向,也有追加操作,可见底层封装的是fopen a接口:

3.系统文件IO
下面我们看一下系统级别的IO:
3.1open函数使用


要学会open函数的使用,首先需要了解该函数的参数:
3.1.1理解标志位

open函数的使用如下:

3.1.2权限问题

上面的代码运行之后,生成的文件的权限是乱码,这是因为open接口的第三个参数需要传入权限,我们没传,我们对代码进行修改:
int main()
{int fp = open("log.txt", O_CREAT | O_WRONLY, 0666);if(fp < 0) exit(1);return 0;
}

这里的权限就变了,但是我们不是传入的是一个0666权限码吗,为什么设置的权限是0664呢,这是因为有mask权限掩码的存在,这个之前讲过,自己复习,我们需要使用umask对mask进行设置:
int main()
{umask(0);int fp = open("log.txt", O_CREAT | O_WRONLY, 0666);if(fp < 0) exit(1);return 0;
}

3.1.3write和read接口介绍
write和read是系统级别的写入和读取接口,我们先来介绍write:
ssize_t write(int fd, const void *buf, size_t count);
1.fd:文件描述符,open函数打开文件成功的返回值
2.buf:要写入的数据的指针
3.count:要写入的字节数

所以说,系统底层都是进行二进制写入/读取的,语言只是进行了封装,fputs和fprintf函数底层会将传入的int类型的数据转化为字符数据,再写入到文件中,而fwrite会直接将int类型的二进制表示写入到文件中
然后我们再来看read:
ssize_t read(int fd, void *buf, size_t count);
1.fd:文件描述符
2.buf:读取的数据存放的位置
3.count最大读取的数据大小
int main()
{umask(0);int fd = open("log.txt", O_RDONLY, 0666);if(fd < 0) exit(1);char buf[1024];memset(buf, 0, sizeof(buf));read(fd, buf, sizeof(buf));printf("%s", buf);return 0;
}
3.1.4谈谈fd以及封装
我们知道open函数打开文件成功的返回值是fd,那么fd究竟是什么呢?:

然后我们需要谈谈封装,stdin、stdout、stderr是C语言提供的标准流,而C++也有自己的标准流:cin、cout、cerr,Python、Go等语言都有,那么封装的意义是什么呢?:
对于不同的系统:Windows、MacOS、Linux等,底层的实现是不同的,那么操作系统对于文件的操作也是不同的,所以就需要使用条件编译来对不同的系统做出不同的操作,比如:在Linux下,C语言的printf执行的是write1接口,在Windows下,执行的是write2接口,这样对于不同的平台,只要是使用该语言,都能够实现运行,标准流也是如此,也需要访问文件,也需要进行条件编译调用不同的接口,而实现可移植性的根本目的在于让语言被更多人使用,增加市场占有率!
3.1.5常见标志位的使用
1.O_CREAT:如果没有找到目标文件,默认创建一个目标名称文件
2.O_WRONLY:write only,只写
3.O_RDONLY:read only,只读
4.O_APPEND:追加
5.O_TRUNC:truncate,清除
对于清除标志位,我们需要做一个特殊情况的讲解:
int main()
{umask(0);int fp = open("log.txt", O_CREAT | O_WRONLY, 0666);if(fp < 0) exit(1);const char* msg = "hello world!\n";write(fp, msg, strlen(msg));write(fp, msg, strlen(msg));write(fp, msg, strlen(msg));write(fp, msg, strlen(msg));write(fp, msg, strlen(msg));return 0;
}
我们先向文件中写入5串字符
int main()
{umask(0);int fp = open("log.txt", O_CREAT | O_WRONLY, 0666);if(fp < 0) exit(1);const char* msg = "12345\n";write(fp, msg, strlen(msg));return 0;
}

当我们再次写入时,会发现,12345竟然覆盖式地写入到了文件中,这是因为没有传入清除标志位,需要进行传入:
int fp = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
3.2系统级别文件访问接口底层的运行原理(3.1.4问题一解释)

4.重定向
4.1文件描述符的分配规则
我们知道了标准流分别占据了0、1、2三个位置,那么我们删除这三个标准流,会发生什么呢?:
1.删除stdin:
int main()
{close(0);umask(0);int fd1 = open("log1.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);printf("fd1 = %d\n", fd1);return 0;
}
输出结果为:fd1 = 0
int main()
{close(2);umask(0);int fd1 = open("log1.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);printf("fd1 = %d\n", fd1);return 0;
}

什么也没输出,但是结果被保存在了log1.txt文件中

int main()
{close(2);umask(0);int fd1 = open("log1.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);printf("fd1 = %d\n", fd1);return 0;
}
结果为:fd1 = 2
输出结论:文件描述符的分配原则是:最小的,没有被使用的,作为新的fd给用户,而关闭了fd2,也就是stdout,那么log1.txt就变成了fd2,所以输出的结果就被保存到了log1.txt文件中了!
4.2文件重定向原理(dup2接口)
我们上面并没有关闭fd1,当我们讲fd1关闭时,我们看看会发生什么:
int main()
{//close(0);close(1);//close(2);umask(0);int fd1 = open("log1.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);printf("fd1 = %d\n", fd1);close(fd1);return 0;
}
结果就是,文件中什么也没有,也没有打印出任何信息,这个情况下面讲缓冲区时再说
文件重定向操作利用的想法和文件描述符的分配原则的思想很相似,这里我们要认识一个dup2接口:


4.3向自定义shell中添加重定向操作
知道了重定向原理,我们需要向自定义shell中添加重定向操作:
//5.指令的执行
//重定向的状态
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3int redir = NONE_REDIR;//假设开始时没有重定向
std::string filename;//保存需要进行重定向时,进行重定向的文件名称int Execute()
{pid_t id = fork();if(id == 0){int fd = -1;if(redir == INPUT_REDIR){fd = open(filename.c_str(), O_RDONLY);if(fd < 0) exit(0);dup2(fd, 0);close(fd);}else if(redir == OUTPUT_REDIR){fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);if(fd < 0) exit(1);dup2(fd, 1);close(fd);}else if(redir == APPEND_REDIR){fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);if(fd < 0) exit(2);dup2(fd, 1);close(fd);}else{}execvp(g_argv[0], g_argv);exit(1);}int status = 0;pid_t rid = waitpid(-1, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}return 0;
}//3.重定向分析
//删除cmd数组中,end位置之后的空格
void TrimSpace(char cmd[], int& end)
{while(isspace(cmd[end])) end++;
}void RedirCheck(char cmd[])
{//重定向操作的实现,首先要分析是哪种重定向操作//然后要找出重定向操作的文件名称//对于不同的重定向操作,进行不同的代码实现redir = NONE_REDIR;filename.clear();int start = 0;int end = strlen(cmd)-1;while(start <= end){if(cmd[end] == '<'){//<cmd[end++] = 0;TrimSpace(cmd, end);redir = INPUT_REDIR;filename = end+cmd;break;}else if(cmd[end] == '>'){if(cmd[end-1] == '>'){//>>cmd[end-1] = 0;redir = APPEND_REDIR;}else{//>redir = OUTPUT_REDIR;}cmd[end++] = 0;TrimSpace(cmd, end);filename = end+cmd;break;}else{end--;}}
}int main()
{Init_ENV();while(true){//1.输出命令行提示符PrintCommandPrompt();//2.获取用户输入的命令char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline, sizeof(commandline))) continue;//如果获取失败,重新获取//3.重定向分析RedirCheck(commandline);//4.命令行解析CommandPrase(commandline);//Print();//5.对于内建命令,要进行特殊处理if(CheckAndExecBuiltin()) continue;//如果是内建命令的话,就不需要下面的指令执行了//6.指令的执行Execute();}return 0;
}
但是这里有一个问题:echo “XXX” > log.txt命令无法正常执行,因为echo是内建命令,对于内建命令的重定向,不是要更改shell的标准流吗,那么这不就出现问题了吗:

5.标准错误
我们之前着重说的都是stdin和stdout,那么stderr是个什么东西呢?:
int main()
{//向标准输出中进行打印std::cout << "hello cout" << std::endl;printf("hello printf\n");//向标准错误中进行打印std::cerr << "hello err" << std::endl;fprintf(stderr, "hello stderr\n");return 0;
}

我们看到,当我们运行程序时,stderr和stdout打印的信息都在显示器上,其实stdout和stderr指向的是同一个文件指针,这么作的目的在于:可以根据重定向操作,将常规信息和错误信息进行分离!,这样就可以只拿到错误信息/常规信息了,分离的方式为:

如果我们向将err和out信息打印到同一个文件,通用做法为:


6.理解“Linux下一切皆文件”
在windows下是文件的东西,在Linux下也是文件,在windows下不是文件的东西,在Linux下也会被抽象成文件,比如进程、磁盘、显示器、键盘等硬件设备也被抽象成了文件:

7.缓冲区
7.1缓冲区的概念
我们之前留有一个问题:为什么将fd关闭了之后,就不会向文件中输出任何东西了呢?

我们由此引入缓冲区的概念:

所以说,当我们将fd关闭之后,而进程却没有结束,不满足缓冲区的刷新规则,当进程结束之后需要进行缓冲区刷新时,由于fd已经关闭,所以不知道向哪里进行刷新,所以文件就拿不到想要的数据!
7.2缓冲区的作用
那么我们为什么需要缓冲区呢?:
1.系统调用是有成本的,如果我们每次打印数据,都是直接向硬盘中进行打印的话,那么多次对硬盘的访问操作是非常消耗效率的,而如果我们先将数据写入到缓冲区中,缓冲区达到一定的数据存储之后再将数据通过一次系统调用写入到硬盘中,那么效率就会大大提升
2.对于上层用户、使用者来说,操作系统只需要使用一次系统调用,将需要读取的数据先放在缓冲区中,用户使用时直接在缓冲区中拿数据,比每次访问缓冲区拿数据要方便的多
3.而且,当向缓冲区中写入数据时,写入完成,就可以进行函数返回了,如果直接向硬件中进行写入时,如果有多个进程都需要向同一个硬件中写入,就需要进行等待,浪费不必要的时间
4.所以说,缓冲区的存在,既提高的使用者的效率,又提高了系统对文件的读写效率
7.3深入理解缓冲区
int main()
{//库函数的打印printf("hello printf\n");fprintf(stdout, "hello fprintf\n");const char* msg1 = "hello fwrite\n";fwrite(msg1, strlen(msg1), 1, stdout);//系统调用const char* msg2 = "hello write\n";write(1, msg2, strlen(msg2));fork();return 0;
}

当我们在进程结束之前创建子进程,然后再将输出内容重定向到文件中,可以看出,多打了很多东西,我们解释一下:

而如果直接向显示器中打印的话,行刷新,遇到\n刷新,所以就会打印4串字符:

8.libc库(C标准库)的简单实现
我们并不能像C标准那样实现非常复杂的标准库,但是我们可以进行简单实现,重点是在于理解库缓冲区、函数调用的底层机制、刷新缓冲区的刷新机制:
#include "mystdio.h"
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>//1.打开文件
//设计为静态函数好处:
//1.外部文件无法调用BuyFile函数
//2.避免命名冲突
static MyFile* BuyFile(int fd, int flag)
{MyFile* f = (MyFile*)malloc(sizeof(MyFile));f->fileno = fd;f->flag = flag;f->bufferlen = 0;f->flush_method = LINE_FLUSH;memset(f->outbuffer, 0, sizeof(f->outbuffer));return f;
}MyFile* MyFopen(const char* file, const char* mode)
{int fd = -1;int flag = 0;if(strcmp(mode, "r")){flag = O_RDONLY;fd = open(file, flag);}else if(strcmp(mode, "w")){flag = O_CREAT | O_WRONLY | O_TRUNC;fd = open(file, flag, 0666);}else if(strcmp(mode, "a")){flag = O_CREAT | O_APPEND | O_WRONLY;fd = open(file, flag, 0666);}else{}if(fd < 0) return NULL;return BuyFile(fd, flag);
}//2.写入文件
int MyFwrite(MyFile* file, void* str, int len)
{//1.写入其实就是一个拷贝memcpy(file->outbuffer+file->bufferlen, str, len);file->bufferlen += len;//2.检查是否需要进行缓冲区的刷新if((file->flush_method & LINE_FLUSH) && file->outbuffer[file->bufferlen-1] == '\n'){MyFFlush(file);}return 0;
}//3.缓冲区的刷新
void MyFFlush(MyFile* file)
{if(file->bufferlen <= 0) return;//缓冲区的刷新其实是将数据从用户中拷贝到内核文件缓冲区中int n = write(file->fileno, file->outbuffer, file->bufferlen);(void)n;file->bufferlen = 0;
}void MyFclose(MyFile* file)
{if(file->fileno < 0) return;MyFFlush(file);close(file->fileno);free(file);
}
这样的简单实现还是有一点小问题,在MyFclose函数中,我们的刷新策略是:使用write进行刷新,但是write的作用其实是将数据从库缓冲区拷贝到文件内核缓冲区,至于是否正常刷新到显示器中,还是要看操作系统,所以我们需要使用fsync函数进行刷新,该函数可以直接将数据从缓冲区刷新到磁盘:

#include "mystdio.h"
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>//1.打开文件
//设计为静态函数好处:
//1.外部文件无法调用BuyFile函数
//2.避免命名冲突
static MyFile* BuyFile(int fd, int flag)
{MyFile* f = (MyFile*)malloc(sizeof(MyFile));f->fileno = fd;f->flag = flag;f->bufferlen = 0;f->flush_method = LINE_FLUSH;memset(f->outbuffer, 0, sizeof(f->outbuffer));return f;
}MyFile* MyFopen(const char* file, const char* mode)
{int fd = -1;int flag = 0;if(strcmp(mode, "r")){flag = O_RDONLY;fd = open(file, flag);}else if(strcmp(mode, "w")){flag = O_CREAT | O_WRONLY | O_TRUNC;fd = open(file, flag, 0666);}else if(strcmp(mode, "a")){flag = O_CREAT | O_APPEND | O_WRONLY;fd = open(file, flag, 0666);}else{}if(fd < 0) return NULL;return BuyFile(fd, flag);
}//2.写入文件
int MyFwrite(MyFile* file, void* str, int len)
{//1.写入其实就是一个拷贝memcpy(file->outbuffer+file->bufferlen, str, len);file->bufferlen += len;//2.检查是否需要进行缓冲区的刷新if((file->flush_method & LINE_FLUSH) && file->outbuffer[file->bufferlen-1] == '\n'){MyFFlush(file);}return 0;
}//3.缓冲区的刷新
void MyFFlush(MyFile* file)
{if(file->bufferlen <= 0) return;//缓冲区的刷新其实是将数据从用户中拷贝到内核文件缓冲区中int n = write(file->fileno, file->outbuffer, file->bufferlen);(void)n;file->bufferlen = 0;
}void MyFclose(MyFile* file)
{if(file->fileno < 0) return;fsync(file->fileno);close(file->fileno);free(file);
}
相关文章:
Linux第七讲:基础IO
Linux第七讲:基础IO 1.什么是文件2.文件操作的复习2.1文件基本操作复习2.2将信息输出到显示器,你有哪种方法2.3stdin、stdout、stderror2.4细节问题讲解 3.系统文件IO3.1open函数使用3.1.1理解标志位3.1.2权限问题3.1.3write和read接口介绍3.1.4谈谈fd以…...
【GIT】重新初始化远程仓库
有的时候我们克隆远端仓库会出错: git clone --depth 1 git116.*.*.*:/srv/customs.git D:\dev\projects\kdy\customs11\customs Cloning into D:\dev\projects\kdy\customs11\customs... remote: Enumerating objects: 1494, done. remote: Counting objects: 100…...
力扣热题 100:多维动态规划专题经典题解析
系列文章目录 力扣热题 100:哈希专题三道题详细解析(JAVA) 力扣热题 100:双指针专题四道题详细解析(JAVA) 力扣热题 100:滑动窗口专题两道题详细解析(JAVA) 力扣热题 100:子串专题三道题详细解析(JAVA) 力…...
【Unity】在项目中使用VisualScripting
1. 在packagemanager添加插件 2. 在设置中进行初始化。 Edit > Project Settings > Visual Scripting Initialize Visual Scripting You must select Initialize Visual Scripting the first time you use Visual Scripting in a project. Initialize Visual Scripting …...
Pytest自动化测试框架pytest-xdist分布式测试插件
平常我们功能测试用例非常多时,比如有1千条用例,假设每个用例执行需要1分钟,如果单个测试人员执行需要1000分钟才能跑完; 当项目非常紧急时,会需要协调多个测试资源来把任务分成两部分,于是执行时间缩短一…...
文件解析漏洞靶场解析全集详解
lls解析漏洞 目录解析 在网站的下面将一个1.asp文件夹,在里面建一个2.txt文件在里面写入<% -now()%>这个显示时间的代码,再将文件名改为2.jpg。 发现2.jpg文件以asp形式执行 畸形文件解析 将2.jpg文件移到网站的下面与1.asp并列,将名…...
C语言数据结构:数组
1. 数组(Array) 1.1 定义 数组是一种线性数据结构,由相同类型的元素组成,这些元素在内存中按顺序存储。数组的大小在声明时确定,且不可动态改变。 1.2 类型细分 根据维度和用途,数组可以分为以下几种类型…...
LeetCode-移动零
一、题目描述 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0]示例 2: 输入: nums […...
PDF Reader
Acrobat Reader...
孔夫子根剧关键字获取在售商品 API
要使用孔夫子旧书网根据关键字获取在售商品的 API,需要以下步骤1: 注册与认证:在孔夫子旧书网的开发者平台注册一个账号,登录后创建一个新的应用,以获取 API 密钥(key)和调用密钥(s…...
Qt的QToolButton设置弹出QMenu下拉菜单
在Qt中,使用QToolButton显示下拉菜单可以通过以下步骤实现: 基本实现步骤 创建QToolButton:实例化一个QToolButton对象。创建QMenu:实例化一个QMenu作为下拉菜单。添加菜单项:通过QMenu::addAction方法添加动作&…...
【一次成功】Win10本地化单机部署k8s v1.31.2版本及可视化看板
【一次成功】Win10本地化单机部署k8s v1.31.2版本及可视化看板 零、安装清单一、安装Docker Desktop软件1.1 安装前<启用或关闭Windows功能> 中的描红的三项1.2 查看软件版本1.3 配置Docker镜像 二、更新装Docker Desktop三、安装 k8s3.1 点击启动安装3.2 查看状态3.3 查…...
Elasticsearch Java High Level Client [7.17] 使用
es 的 HighLevelClient存在es源代码的引用,结合springboot使用时,会存在es版本的冲突,这里记录下解决冲突和使用方式(es已经不建议使用这个了)。 注意es服务端的版本需要与client的版本对齐,否则返回数据可…...
Vue项目搜索引擎优化(SEO)终极指南:从原理到实战
文章目录 1. SEO基础与Vue项目的挑战1.1 为什么Vue项目需要特殊SEO处理?1.2 搜索引擎爬虫工作原理 2. 服务端渲染(SSR)解决方案2.1 Nuxt.js框架实战原理代码实现流程图 2.2 自定义SSR实现 3. 静态站点生成(SSG)技术3.1…...
LeetCode:93. 复原 IP 地址(DFS Java)
目录 93. 复原 IP 地址 题目描述: 实现代码与解析: DFS 原理思路: 93. 复原 IP 地址 题目描述: 有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0)…...
Spring Boot 中实现全局 Token 验证的两种方式
文章目录 学习文章:Spring Boot 中实现全局 Token 验证的两种方式 一、为什么需要全局 Token 验证?二、使用拦截器实现全局 Token 验证1. 创建 Token 验证拦截器2. 注册拦截器3. 测试拦截器 三、使用过滤器实现全局 Token 验证1. 创建 Token 验证过滤器2…...
【性能测试】Jmeter下载安装、环境配置-小白使用手册(1)
本篇文章主要包含Jmeter的下载安装、环境配置 添加线程组、结果树、HTTP请求、请求头设置。JSON提取器的使用,用户自定义变量 目录 一:引入 1:软件介绍 2:工作原理 3:安装Jmeter 4:启动方式 …...
【Matlab仿真】如何解决三相交流信号源输出波形失真问题?
问题描述 如标题所示,在搭建simulink模型过程中,明明模型搭建的没有问题,但是输出的波形却不是理想的正弦波,影响问题分析。 问题分析 以三相交流信号源输出波形为例,输出信号理应为三相正弦量,但是仿真…...
Fiora聊天系统本地化部署:Docker搭建与远程在线聊天的实践指南
文章目录 前言1.关于Fiora2.安装Docker3.本地部署Fiora4.使用Fiora5.cpolar内网穿透工具安装6.创建远程连接公网地址7.固定Uptime Kuma公网地址 前言 这个通讯软件泛滥的时代,每天都在刷着同样的朋友圈、看着千篇一律的表情包,是不是觉得有点腻了&#…...
metersphere接口测试(1)使用MeterSphere进行接口测试
文章目录 前言接口文档单接口测试环境配置梳理接口测试场景测试接口 接口自动化怎么写复用性高的自动化测试用例 总结 前言 大汉堡工作第203天,本篇记录我第一次接触接口测试任务,最近有些懈怠啊~ 接口文档 首先就是接口地址,接口测试时用…...
【实战ES】实战 Elasticsearch:快速上手与深度实践-8.2.2成本优化与冷热数据分离
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 8.2.2AWS OpenSearch Serverless 成本优化与冷热数据分离深度实践1. 成本构成分析与优化机会识别1.1 Serverless模式成本分布1.2 冷热数据特征分析数据特征矩阵 2. 冷热数据…...
MTK Android12 安装app添加密码锁限制
提示:通过安装前输入密码的需求,来熟悉了解PMS 基本的安装流程 文章目录 一、需求实现需求原因提醒 二、UML图-类图三、参考资料四、实现效果五、需求修改点修改文件及路径具体修改内容 六、源码流程分析PMS的复杂性代码量实现aidl 接口PackageManagerSe…...
Redis 集合(Set)
Redis 集合(Set) Redis 是一款高性能的键值数据库,以其高性能、易用性以及丰富的数据结构而广受欢迎。在 Redis 中,集合(Set)是一种重要的数据结构,它支持多种操作,如添加、删除、查找元素,以及集合间的运算。本文将详细介绍 Redis 集合的特点、操作和应用场景。 Redi…...
[数据结构]堆详解
目录 一、堆的概念及结构 二、堆的实现 1.堆的定义 2堆的初始化 3堆的插入 编辑 4.堆的删除 5堆的其他操作 6代码合集 三、堆的应用 (一)堆排序(重点) (二)TOP-K问题 一、堆的概念及结构 堆的…...
基于Python+Vue开发的旅游景区管理系统源码+运行步骤
项目简介 该项目是基于PythonVue开发的旅游景区管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Python的旅游景…...
SpringBoot使用Logback日志框架与综合实例
日志框架的使用,系列文章: 《SpringBoot使用Logback日志框架与综合实例》 《SpringBoot使用@Slf4j注解实现日志输出》 《Log4j2日志记录框架的使用教程与简单实例》 《SpringBoot使用AspectJ实现AOP记录接口:请求日志、响应日志、异常日志》 《SpringBoot使用AspectJ的@Arou…...
LInux中常用的网络命令
配置 IP 地址 1.1 配置 IP 地址 IP 地址是计算机在互联网中唯一的地址编码。每台计算机如果需要接入网络和其他计算机进行数据通信,就必须配置唯一的公网 IP 地址。 配置 IP 地址有两种方法: 1)setup 工具 2)vi /etc/sysconf…...
怎么实现: 大语言模型微调案例
怎么实现: 大语言模型微调案例 目录 怎么实现: 大语言模型微调案例输入一个反常识的问题:首都在北京天安门之后对输出模型进行测试:首都在北京天安门微调代码:测试微调模型代码:微调输出模型结构输出模型参数大小对比Qwen 2.5_0.5:53MB输出模型:951MB 是一样的,没有进行…...
快速学习Bootstrap前端框架
什么是 Bootstrap? Bootstrap 是一个开源的前端框架,用于快速开发响应式(Responsive)和美观的网页。它包含: ✅ HTML 组件(导航栏、按钮、表单等) ✅ CSS 样式(网格系统、排版、颜色等) ✅ JavaScript 交互(模态框、轮播图、工具提示等) 官网:Bootstrap The mo…...
KICK第四讲Linux 系统下安装 GCC 编译器全指南
Linux 系统下安装 GCC 编译器全指南 GCC(GNU Compiler Collection)是 Linux 系统下最常用的编译器之一,支持 C/C、Java 等多种编程语言。本文将介绍不同 Linux 发行版下的安装方法,帮助开发者快速配置开发环境。 一、使用包管理…...
