【Linux】内存文件系统的I/O、重定向
文章目录
- 1. 系统中的文件
- 2. 回顾C中的文件接口
- 3. 文件类的系统调用
- 3.1 open
- 3.2 文件描述符
- 4. IO的基本过程
- 5.重定向
- 5.1 引入重定向
- 5.2 系统中的重定向接口
- 6. 缓冲区问题
- 7. 简单版shell的实现
1. 系统中的文件
在学习完Linux权限后,我们清楚的知道:文件 = 文件内容 + 文件属性,这和进程就很像。所以我们文件的所有操作无非就是对文件内容或属性进行的。
无论何种语言,在访问文件之前,我们都必须打开文件,例如C语言中的fopen。但是我们访问前为什么要打开它呢?
文件在没有被访问的时候,它存储在磁盘上。学习完进程后,我们清楚的知道,访问文件其实是进程在访问。
在下面的代码中,当程序跑起来,进程执行到fopen时,文件才被打开。

进程是在内存中的,最终要被CPU执行;当进程执行文件操作时,但文件在磁盘上,根据冯诺依曼体系,CPU不能访问磁盘。
所以,文件也必须加载到内存中,否则进程访问不了(因为CPU访问不了),所以,打开文件的本质是:将文件加载到内存中。
因为文件 = 内容 + 属性,所以加载时,加载的就是文件的内容或属性。
我们知道一个进程可以打开多个文件,多个进程就可以打开更多的文件。
既然操作系统要管理进程,那么操作系统也要管理加载到内存中的文件(文件是谁加载的?什么时候加载的?要不要释放?…),操作系统管理加载到内存中文件的方式:先描述,再组织!
因此,我们研究打开的文件,是在研究:进程和文件的关系!
文件就可以分为:
- 被打开的文件 - -内存中
- 未被打开的文件 - - 磁盘中
2. 回顾C中的文件接口
在C语言中,我们学习了很多的文件相关接口,例如:

在系统文件中,我们重点关注 w 与 a



执行任何一个程序,进程默认会打开三个输入输出流,分别是stdin, stdout, stderr。

仔细观察发现,这三个流的类型都是FILE*,而且fopen的返回值也是FILE*。
在任何一个语言中,都会提供类似的三个流,既然语言都支持,那这本身并不属于语言的特性,而是属于操作系统所做的工作。
所以各种语言提供的访问文件(键盘、显示器)的接口,本质都是封装的文件类的系统调用接口。
3. 文件类的系统调用
3.1 open

- 参数
第二个参数是标记位,上方列出了常用的选项,它们本质上都是宏,可以组合使用。
但是以前使用多个宏时,我们需要传递多个参数,为什么这里一个int flag就能解决呢?- - - 位图,32个比特位,可以存储32个选项。如果是这样的话,那上方列出的宏选项,它们应该只有一个比特位为1。
举个栗子:

上方代码就通过检查flag对应位置上是0还是1,就可以实现传递多个选项的效果。
下面我们使用一下系统调用open


运行后发现,文件不存在时确实创建了,但是文件的权限为什么是错乱的呢?
因为创建文件和起始权限是操作系统的两个分支功能,操作系统不会在系统调用上让你创建文件时就按照默认权限来,它也没有这个权力。你要告诉系统调用,文件的默认起始权限是什么。
我们需要通过第三个参数mode指定,这不就是权限那里的chmod吗?


权限不是666是因为有 umask,去除umask可在函数中调用 umask(0);
每个进程都有自己的umask,继承自系统,如果你设置了umask,它采用就近原则使用用户设置的,不使用系统的了。
有了open,我是不是就可以自己实现一个touch命令了。
如果是touch命令,则获取命令行中的第二个参数,调用open(argv[1],O_WRONLY | O_CREAT,0666)
不重新设置umask时,文件权限就是664
- 返回值

打开并新建一个文件后,会返回新文件的文件描述符descriptor

上方的代码返回的文件描述符为什么是3呢?- - 后面讲
它有什么用呢?

使用文件描述符可对文件进行操作

修改要写入的内容后

为什么此时写到文件中的内容,没有清空之前的内容呢? - - (因为你只告诉open系统调用,只写,没有就创建,没告诉我要清空。)
清空/截断的选项:O_TRUNC ,带上该选项,写入前就会清空。

新增式的写,需要带上选项:O_APPEND
有了上面的知识后,我们就可以清楚的知道:fopen -> open,fclose -> close,fwrite->write,fread -> read;w、a、r就对应相应的宏,所以语言层的文件库函数,本质上就是封装了文件的系统调用接口。
3.2 文件描述符

open的返回值(文件描述符)为什么从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));//输入到标准输出中}return 0;}
所以这个文件描述符到底是什么呢?- - 连续的数字,是数组下标吗?
下面我们从内核的角度看一下

内核源代码中确实存在文件描述符表

所以,每次我们在open时,当前进程会去自己的文件描述符表中的 结构体指针数组 中从前向后找空位置。
那么,在系统层面,文件描述符就是访问文件的唯一方式。
但是我们平常在使用C标准库提供的文件访问函数时,并没有看到相应的fd呀?- - 这是因为库中进行了封装,全部封装到FILE中去了!


上面所说的我都能理解,可是键盘、显示器也能被当作文件来看吗?Linux下一切皆文件又如何理解呢?
对于硬件来说,它们都有一个特点,它们统一叫做外设。操作系统是通过类似于链表的方式将各个外设管理起来的。

但对于很多外设(struct device)来说,它们的属性可以相同,但是属性的内容可以不同;可是键盘就是键盘、显示器就是显示器,对这两个外设进行操作的方法只有两个:读和写(IO),所以它们的方法一定不同,因为不同的设备访问对应硬件的方式是不同的。那是怎么做到统一以文件的视角来访问的呢?
- 对于每一种设备,都要给其定义读写方法。
例如:键盘定义了读和写的方法,但是只需要实现读方法;显示器定义了读和写的方法,但是只需要实现写方法。对于未实现的方法,就让它为空。- 在对设备进行管理时,只需要在其struct file中设置两个函数指针,让其指向对应的方法(不关心方法的实现),屏蔽了底层的差异。
- 从struct file向上开始,所有访问硬件的方法,统一叫做读和写,struct file就相当于做了一次封装。
- Linux系统对外部只需要提供struct file对象,这叫做虚拟文件系统vfs。
- 那这个struct file是怎么让用户看到的呢?通过文件描述符,让进程看到。
- 因为所有用户的行为,都会被转化为进程
- 站在进程角度,它只需要通过fd,找到对应的struct file,执行struct file中对应的方法即可完成对设备的操作。
所以Linux中一切皆文件是对进程而言的- 在一个结构体当中,一切皆文件,但是底层却有不同硬件,这种模式在C++ 中叫做多态。(struct file就是父类,因为struct file都一样;硬件的struct device就是子类,子类实现了不同的读写方法;继承就是产生了上下层关系),这就是C语言中实现多态的一种形式。
内核源代码

4. IO的基本过程
- 写文件

其实write做的工作就是将要写的内容,拷贝到文件的内核缓冲区即可。
- 读文件
先从磁盘读到文件的内核缓冲区,在调用read函数拷贝到指定位置
- 修改文件
修改的本质:也是先读取,在写入(先将文件加载到文件的内核缓冲区,在内核中修改,然后再将修改后的内容从缓冲区加载到外设中)
存在缓冲区的原因:内存的操作快,外设的操作慢
5.重定向
5.1 引入重定向
先看一个现象

为什么关掉0以后,我自己打开的文件就是0了呢?
因为进程打开文件,需要给进程分配新的fd,fd的分配规则:最小的,没有被使用的fd!会从上往下扫描文件描述符表,找未被使用的,最小的。
如果我把1号关了,此时fd1就应该为1;由于printf底层封装了fprintf,fprintf中有FILE*参数,默认fd为1,所以只会向1号描述符中打印,不管1指向哪里,那么就应该将内容打印到log1.txt中!

但是log1.txt中怎么什么都没有呢?

fflush(stdout)后为什么又有了呢? - - 跟缓冲区相关,后面讲。
为什么本来应该向显示器中打印的内容,最终却写到了文件中呢?
因为在上层的调用中,fprintf、fwrite等向stdout中打印的所有调用,它们只认文件描述符1。 1号描述符表并没有变,我们只改变了1中的内容,这就叫做重定向
所以,重定向的原理就是:更改文件描述符表中特定下标中的内容。重定向的过程中,上层代码毫不知情!
5.2 系统中的重定向接口
dup2:系统重定向接口

本质就是用新的fd覆盖到指定位置! 被覆盖的将被关掉。
- 输出/追加重定向

那追加重定向不就是只需要将选项 O_TRUNC换成O_APPEND了吗?
- 输入重定向

在写我们自己的shell时,思考一下,程序替换会影响重定向的结果吗?
- 不会,因为程序替换仅仅是替换进程所对应的代码和数据,必要时修改mm_struct中页表的映射关系。对于一个进程“上层”的东西(task_struct、file_struct、mm_struct等)都不会修改,依旧使用重定向以后的内容。
6. 缓冲区问题

在上面的内容中,我们遗留了一个问题。
那就是为什么我重定向以后它并没有直接给我写到指定文件中,而fflush(stdout)后就写进去了呢?
- 首先我们要知道,在C语言中我们使用的printf、scanf、fprintf、fscanf、fwrite、fread等都要求有一个FILE*的指针。
- 所以,在调用这些函数进行操作时,它并没有直接调用系统调用read、write直接拷贝到文件的内核缓冲区,因为频繁的
调用系统调用的成本太高了,效率低。- 所以怎么能提高效率呢?
- 通过用户级缓冲区!! 你printf、fprintf等只需要将内容拷贝到用户级缓冲区中任务就完成了,无非就是在拷贝的过程中进行一下格式化;等用户级缓冲区攒了足够多的数量,在统一调用系统调用写入到文件的内核缓冲区,提高了效率。
- 该缓冲区在FILE结构体中,刷新的本质就是从用户级缓冲区拷贝到内核的文件缓冲区。
用户级缓冲区有以下几种刷新方案:
- 显示器文件:行刷新
- 普通文件:缓冲区写满再刷新
- 不缓冲(语言级无需刷新)
我们将最开始的代码修改一下会发现,如果我不调用任何的close或者调用fclose,内容可以正常打印出来,这是为什么呢?

因为,当一个进程退出的时候,会自动刷新自己的缓冲区(所有的FILE对象内部,包括stdin、stdout、stderr);fclose是C语言级的,调用它关闭FILE时,也会自动刷新。
那close(fd)后,为什么不会刷新呢?

此时尽管“表面上”是向显示器中打,应该是行刷新,那么我不自己刷新应该也可以显示出来呀? - - 此时不是行刷新,因为显示器文件早就关闭了,1中放的是普通文件,应执行写满刷新的策略。
- 那操作系统是什么时候将文件内核缓冲区的内容刷新到外设中的呢?我能不能控制呢?
- 通过系统调用fsync
- 一个简单的题目

如果在调用函数时不加 \n,即使不重定向,也是上图所示的打印效果。
7. 简单版shell的实现
#include<cstdio>
#include<stdlib.h>
#include<string>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
using namespace std;const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;
//存储命令行参数的两个全局变量
char* g_argv[argvnum];
int g_argc;
//存shell自己的环境变量
char* g_env[envnum];//如果这两个设置为局部变量,则会写入一个空白
//局部变量销毁,环境变量表中存的是这两个变量的地址,所以就是空
char pwd[basesize];
char pwdenv[basesize];//存储之前的退出码
int lastcode = 0;//重定向相关全局变量
#define NonRedir 0
#define InputRedir 1
#define OutputRedir 2
#define AppendRedir 3
int redir = NonRedir;
char* filename = nullptr;
//去除空格
#define TrimSpace(pos)\do {\while(isspace(*pos)){\pos++;\}\}while(0)\
string GetName()
{string username = getenv("USER");return username.empty() ? "None" : username;
}string GetHostName()
{string hostname = getenv("HOSTNAME");return hostname.empty() ? "None": hostname;
}bool repalcePwd()
{for(int i = 0; g_env[i]; i++){if(strncmp(g_env[i],pwdenv,3) == 0){g_env[i] = pwdenv;return true;}}return false;
}string GetPwd()
{if(getcwd(pwd,sizeof(pwd)) == nullptr)return "Node";//将当前的工作路径,保存至环境变量中snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);//putenv(pwdenv); //将新的pwd,添加到系统的环境变量表中repalcePwd();//将新的pwd,添加到自己的环境变量表中return pwd;// string pwd = getenv("PWD");
}string LastDir()
{string pwd = GetPwd();if(pwd == "/" || pwd == "None")return pwd;//寻找最后一个文件夹int pos = pwd.rfind("/");return pwd.substr(pos+1);
}//1.显示命令行提示符
void ShowCommandLine()
{char command_line[basesize];snprintf(command_line,basesize,"[%s@%s %s]#",\GetName().c_str(),GetHostName().c_str(),LastDir().c_str());printf("%s",command_line); fflush(stdout);
}//2.读取命令行参数
bool GetCommandLine(char command_buffer[],int size)
{//读取一行的用户输入char* ret = fgets(command_buffer,size,stdin);if(ret == NULL){return false; //获取输入失败}//处理回车键command_buffer[strlen(command_buffer) - 1] = '\0';if(strlen(command_buffer) == 0)return false;return true;
}void debug()
{printf("argc:%d\n",g_argc);for(int i=0; g_argv[i]; i++){printf("[%d] = %s\n",i,g_argv[i]);}
}void CheckRedir(char command_line[],int len)
{int end = len - 1;while(end >= 0){if(command_line[end] == '<'){redir = InputRedir;command_line[end] = '\0';filename = &command_line[end+1];//过滤空格TrimSpace(filename);break;}else if(command_line[end] == '>'){if(command_line[end - 1] == '>'){redir = AppendRedir;command_line[end] = '\0';command_line[end-1] = '\0';filename = &command_line[end+1];//过滤空格TrimSpace(filename);break;}else {redir = OutputRedir;command_line[end] = '\0';filename = &command_line[end+1];//过滤空格TrimSpace(filename);break;}}else {end--;}}
}void InitCommand()
{//命令行清空memset(g_argv,0,sizeof(g_argv));g_argc = 0;//每次检查是否有重定向前先清空redir = NonRedir;filename = nullptr;
}void AnalyCommand(char command_line[])
{const char sep[10] = " ";//指定分隔符g_argv[g_argc++] = strtok(command_line,sep);//先提取第一个//strtok读取失败返回nullwhile((g_argv[g_argc++] = strtok(nullptr,sep)));//依次提取后面的g_argc--;//个数要-1
}void AnalyzeCommandLine(char command_line[])
{InitCommand();//printf("redir before:%s\n",command_line);//检查重定向CheckRedir(command_line,strlen(command_line));// printf("redir:%d\n",redir);// printf("filename:%s\n",filename);// printf("redir after:%s\n",command_line);AnalyCommand(command_line);
}void Redir()
{//程序替换不会影响重定向,因为内核数据结构中的file_struct没变//程序替换,替换的是代码和数据。int fd = -1;if(redir == InputRedir){if(filename){fd = open(filename,O_RDONLY);if(fd < 0){exit(3);}dup2(fd,0);}else {exit(2);}}else if(redir == OutputRedir){if(filename){fd = open(filename,O_CREAT | O_WRONLY | O_TRUNC,0666);if(fd < 0){exit(5);}dup2(fd,1);}else {exit(4);}}else if(redir == AppendRedir){if(filename){fd = open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);if(fd < 0){exit(7);}dup2(fd,1);}else {exit(6);}}else {//没有重定向//do nothing}
}
bool ExecCommandLine()
{//shell创建子进程执行任务pid_t id = fork();if(id < 0)return false;if(id == 0){//child执行命令//重定向应由子进程做Redir();execvpe(g_argv[0],g_argv,g_env);exit(1);//执行失败,退出码为1}int status = 0;pid_t rid = waitpid(id,&status,0); //阻塞式等待if(rid > 0){//等待成功//1.子进程正常结束if(WIFEXITED(status)){lastcode = WEXITSTATUS(status);}else {lastcode = 99;}return true;}return false;
}void AddEnv(const char* str)
{int index = 0;while(g_env[index]){index++;}g_env[index] = (char*)malloc(strlen(str) + 1);strncpy(g_env[index++],str,strlen(str)+1);g_env[index] = nullptr;
}bool CheckAndExecBuildCommand() //判断是否式内建命令
{//枚举几个内建命令if(strcmp(g_argv[0],"cd") == 0){if(g_argc == 2){chdir(g_argv[1]);lastcode = 0;}else{lastcode = 1;}return true;}else if(strcmp(g_argv[0],"export") == 0){if(g_argc == 2){AddEnv(g_argv[1]);lastcode = 0;}else{lastcode = 2;}return true;}else if(strcmp(g_argv[0], "env") == 0){for(int i = 0; g_env[i]; i++){printf("%s\n",g_env[i]);}lastcode = 0;return true;}else if(strcmp(g_argv[0],"echo") == 0){if(g_argc == 2){if(g_argv[1][0] == '$'){if(g_argv[1][1] == '?'){printf("lastcdoe:%d\n",lastcode);lastcode = 0;}}else{printf("%s\n",g_argv[1]);lastcode = 0;}}else{lastcode = 3;}return true;}return false;
}void InitEnv()
{extern char** environ;//从系统shell中获取环境变量int index = 0;while(environ[index]){int len = strlen(environ[index]);g_env[index] = (char*)malloc(len + 1);strncpy(g_env[index],environ[index],len + 1);index++;}g_env[index] = nullptr;
}int main()
{InitEnv();char command_buffer[basesize];while(true){ //1.显示命令行提示符ShowCommandLine();//slsleep(2);//printf("\n");//2.读取命令行参数if( !GetCommandLine(command_buffer,basesize) ){continue; }//3.解析命令行参数AnalyzeCommandLine(command_buffer);//debug();if(CheckAndExecBuildCommand()) //判断是否式内建命令{continue;}//4.执行命令ExecCommandLine();}return 0;
}
相关文章:
【Linux】内存文件系统的I/O、重定向
文章目录 1. 系统中的文件2. 回顾C中的文件接口3. 文件类的系统调用3.1 open3.2 文件描述符 4. IO的基本过程5.重定向5.1 引入重定向5.2 系统中的重定向接口 6. 缓冲区问题7. 简单版shell的实现 1. 系统中的文件 在学习完Linux权限后,我们清楚的知道:文…...
力扣10.18
1463. 摘樱桃 II 给你一个 rows x cols 的矩阵 grid 来表示一块樱桃地。 grid 中每个格子的数字表示你能获得的樱桃数目。 你有两个机器人帮你收集樱桃,机器人 1 从左上角格子 (0,0) 出发,机器人 2 从右上角格子 (0, cols-1) 出发。 请你按照如下规则…...
cs木马图形化界面出现问题处理
一个月多月没用cs木马了,发现打开客户端之后显示不出图形化界面,且出现下面这样的报错。 、 最后发现是java版本的问题,kali的java自动更新了。把原来的openjdk11改到了openjdk23。 解决方法: 输入: sudo update-…...
数据结构与算法 - 树 #数的概念 #二叉树 #堆 - 堆的实现/堆排序/TOP-K问题
文章目录 前言 一、树 (一)、概念 1、树的定义 (二)、树的定义 1、树为什么是递归定义的? 2、如何定义树(如何表达一棵树) 解决方案一:假设我们得知该树的度 解决方案二:顺序表 解决方案三:左孩子右兄弟表示法 二、二叉…...
Git推送被拒
今天开发完成一个新的需求,将自己的分支合并到test分支后,推送到远程仓库,结果显示推送被拒: 原因是因为有人更新了test分支的代码,我在合并之前没有拉取最新的test分支代码,所以他提示我“推送前需要合并…...
Jmeter进行http接口测试
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 本文主要针对http接口进行测试,使用jmeter工具实现。 Jmeter工具设计之初是用于做性能测试的,它在实现对各种接口的调用方面已经做的比较…...
工业相机详解及选型
工业相机相对于传统的民用相机而言,具有搞图像稳定性,传输能力和高抗干扰能力等,目前市面上的工业相机大多数是基于CCD(Charge Coupled Device)或CMOS(Complementary Metal Oxide Semiconductor)芯片的相机。 一,工业相机的分类 …...
RAID 矩阵
在架构设计中,RAID矩阵(RAID Log)是一个用于项目管理和风险管理的工具,帮助团队有效管理和跟踪项目中可能影响成功交付的关键因素。与存储技术中的 RAID 不同,这里的 RAID 是一个缩写,代表: R:…...
详细分析Redisson分布式锁中的renewExpiration()方法
目录 一、Redisson分布式锁的续期 整体分析 具体步骤和逻辑分析 为什么需要递归调用? 定时任务的生命周期? 一、Redisson分布式锁的续期 Redisson是一个基于Redis的Java分布式锁实现。它允许多个进程或线程之间安全地共享资源。为了实现这一点&…...
实验3,网络地址转换
实验3:网络地址转换 实验目的及要求: 通过实验,掌握NAT技术的工作原理,了解三种不同类型NAT技术的主要作用以及各自的主要应用环境。能够完成静态NAT和复用NAT技术的应用,并熟练掌握NAT技术相关的配置命令。 实验设…...
Java 中的 String 字符串是不可变的
文章目录 什么是不可变字符串?举个例子直观理解 不可变的原理1. 内部实现2. 字符串常量池3. 线程安全 为什么要设计成不可变?什么时候用可变字符串?示例 总结推荐阅读文章 在 Java 编程中,字符串(String)是…...
计算机网络架构实例
小型企业网络 1. 终端设备: - 员工的台式电脑和笔记本电脑,用于日常办公,如文档处理、邮件收发、业务软件使用等。 - 智能手机和平板电脑,方便员工在外出或移动办公时也能接入公司网络,查看邮件和处理紧急事务。 2.…...
Chrome与Firefox浏览器HTTP自动跳转HTTPS的解决方案
一、背景介绍 随着网络安全意识的不断提高,越来越多的网站开始采用HTTPS协议,以确保数据传输的安全性。然而,有时用户在浏览网页时,可能会遇到HTTP请求被自动跳转至HTTPS的情况导致网站打不开,提示安全问题࿰…...
众数信科荣登“2024 CHINA AIGC 100”榜单
2024年10月17日,由非凡产研推出的「2024 CHINA AIGC 100」榜单隆重发布,众数信科凭借领先的企业AI智能体解决方案能力荣登榜单。 非凡产研AIGC 100 评选旨在挖掘国内具有高潜力的AI应用,为AI产业的高质量发展注入新动力。榜单覆盖了教育、医疗…...
【AI知识】距离度量和相似性度量的常见算法
本文介绍一些AI中常见的距离度量和相似性度量算法: 1. 欧几里得距离(Euclidean Distance) 欧几里得距离是最常见的距离度量方法,用来计算两个向量之间的“直线距离”,也被成为L2范数。 公式如下,其中 x…...
LeetCode1004.最大连续1的个数
题目链接:1004. 最大连续1的个数 III - 力扣(LeetCode) 1.常规解法(会超时) 遍历数组,当元素是1时个数加一,当元素是0时且已有的0的个数不超过题目限制时,个数加一,若上…...
Parallels Desktop20虚拟机软件能让你在Mac上无缝运行Windows
Code 生成器:Parallels Desktop 20最新版本虚拟机的奇妙世界 🌟【轻松跨越操作系统界限】🌟 你是否常常感到在Mac和Windows之间切换太麻烦?Parallels Desktop 20最新版,让你不再为跨系统操作而烦恼。这款虚拟机软件能让…...
Golang | Leetcode Golang题解之第476题数字的补数
题目: 题解: func findComplement(num int) int {highBit : 0for i : 1; i < 30; i {if num < 1<<i {break}highBit i}mask : 1<<(highBit1) - 1return num ^ mask }...
Spring 实现 3 种异步流式接口,干掉接口超时烦恼
大家好,我是小富~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某…...
字节 HLLM 论文阅读
github连接:https://github.com/bytedance/HLLM 探讨问题: 推荐LLM的三个关键问题: LLM预训练权重通常被认为是对世界知识的概括,其对于推荐系统的价值?对推荐任务进行微调的必要性?LLM是否可以在推荐系统…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...


