文章目录

  • 1.打印命令提示符
  • 2.获取用户输入指令
  • 3.重定向分析
  • 4.命令行参数表,环境变量表,初始化
  • 5.命令解析
  • 6.命令执行
    • 6.1.创建子进程
    • 6.2 处理内建命令
    • 6.3 文件重定向
  • 7.源码

前言

在实现shell的时候我们先创建自己myshell目录,在目录中创建myshell.cc文件,因为shell本来是用c语言写的,但为了方便我们这里使用c和c++混编。

首先我们做一个整体框架:

int main()
{//shell启动的时候从系统获得环境变量//我们的环境变量信息应该统一从父shell取InitEnv();while(1){//1.打印提示信息//printf("[%s@%s %s]#", GetUserName(),GetHostName(),GetPwd());PrintCommandPrompt();//2.获取命令行提示符char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline,sizeof(commandline))){continue;}//3.重定向分析 "ls -a > file.txt" -> "ls -a" "file.txt"//> >> < 判断重定向的方向//> 输出   >> 拼接输出  < 输入RedirCheck(commandline);//printf("redir = %d filename = %s\n", redir, filename.c_str());//4.命令行分析if(!CommandParse(commandline)){continue;}//PrintArgv();//5.检测是否是内键命令if(CheckAndExecBuiltin()){continue;}//6.执行命令Execute();}//清理工作clear();return 0;
}

1.打印命令提示符

首先我们需要给用户显示提示信息,就像我们在使用shell时所看到的提示信息一样,如下:
在这里插入图片描述

对它进行分析:
在这里插入图片描述
所以我们可以这样定义宏,一个是方便打印的,一个是命令长度:

#define FORMAT "[%s@%s %s]#"
#define COMMAND_SIZE 1024

对于用户名,主机号我们可以通过getenv从环境变量中得到,但是获取当前路径我们不能使用getenv,因为我们myshell的环境变量使用的是父进程的环境变量,我们在当前使用cd命令切换路径环境变量中的pwd并不会有改变。

所以获取当前路径,我们可以使用getcwd,直接查询操作系统的文件系统,获取当前进程的工作目录的绝对路径。而不用依赖环境变量。当然我们最好单独设计一个GetPwd把它封装起来,这样也方便把当前路径加入到环境变量中。如下:

//for test
char cwd[1024];
char cwdenv[1024];//last exit code
int lastcode = 0;const char* GetPwd()
{//const char* pwd = getenv("PWD");const char* pwd = getcwd(cwd,sizeof(cwd));if(pwd != NULL){snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",pwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}const char* GetUserName()
{const char* name = getenv("USER");return name == NULL ? "None" : name; 
}const char* GetHostName()
{const char* hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
}
const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "" : home;
}std::string DirName(const char* pwd)
{
#define SLASH "/"std::string dir = pwd;if(dir == SLASH){return SLASH;}auto pos = dir.rfind(SLASH);if(pos == std::string::npos){return "BUG";}return dir.substr(pos + 1);
}void MakeCommandLine(char cmd_prompt[],int size)
{//snprintf(cmd_prompt,size,"[%s@%s %s]#",GetUserName(),GetHostName(),GetPwd());//snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
}void PrintCommandPrompt()
{char prompt[COMMAND_SIZE];MakeCommandLine(prompt,sizeof(prompt));printf("%s",prompt);fflush(stdout);
}

2.获取用户输入指令

获取用户输入,因为用户输入的命令行参数是一个字符串,中间含有空格。所以我们不用scanf,cin进行输入。这里我们使用fgets

bool GetCommandLine(char* out,int size)
{ char* c = fgets(out, size,stdin);if(c == nullptr){return false; }//去掉\nc[strlen(out) -1] = 0;if(strlen(c) == 0){return false;}return true;
}

3.重定向分析

在用户输入的指令中可能含有重定向操作,所以我们要提前特殊处理一下字符串,并把它做一个分割。

既然是重定向,也就是我们打开需要重向到的那个文件,所以我们需要获取打开方式和文件名。

重定向有三种:

  •   <:输入重定向(以读的方式打开文件)
    
  •   >:输出重定向(以写的方式打开文件)
    
  •   >>:追加重定向(以追加的方式打开文件)
    

所以我们可以使用宏来标记这些情况。

//3.关于重定向的内容
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUR_REDIR 2
#define APPEND_REDIR 3int redir = NONE_REDIR;
string filename;
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;//"ls -a -l > file.txt" //> >>  <while(end > start){if(cmd[end] == '<'){cmd[end++] = 0;TrimSpace(cmd,end);redir = INPUT_REDIR;filename = cmd + end;break;}else if(cmd[end] == '>'){//>>if(cmd[end - 1] == '>'){cmd[end-1] = 0;redir = APPEND_REDIR;}//>else{redir = OUTPUR_REDIR;}cmd[end++] = 0;TrimSpace(cmd,end);filename = cmd + end;break;}else{end--;}}}

4.命令行参数表,环境变量表,初始化

shell中有两张表命令行参数表和环境变量表,实质都是字符串数组。

  • 命令行参数表:用来储存用户输入的命令行参数。
  • 环境变量表:用来储存当前进程的属性和状态。

所以我们可以这样做一个全局变量:

//shell自定义的全局变量
//1.命令行参数表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;//2.环境变量表
#define MAXENVS 100
char* g_env[MAXENVS];
int g_envs = 0;

环境变量表需要我们在程序启动时就将它导入, 当然程序启动后环境变量默认是父进程的,所以我们可以重新开辟空间把原环境变量的数据拷贝过来,然后再把environ更新为新的地址。具体实现请参考下文源码。

void InitEnv()
{extern char** environ;memset(g_env,0,sizeof(g_env));g_envs = 0;//配置文件//获取环境变量for(int i = 0;environ[i];i++){//申请空间g_env[i] = (char*)malloc(strlen(environ[i]) + 1);strcpy(g_env[i],environ[i]);g_envs++;}//for testg_env[g_envs++] = (char*)"MIHAYOU=666"; g_env[g_envs] = NULL; //导成环境变量for(int i = 0;g_env[i];i++){putenv(g_env[i]);}environ = g_env;
}

5.命令解析

刚才我们获取到了用户的输入得到一个字符串,需要把它一个一个按空格分开,来得到一张命令行参数表。方便后面做进程替换。

//命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char* commandline)
{
#define SEP " "g_argc = 0;g_argv[g_argc++] = strtok(commandline,SEP);while((bool)(g_argv[g_argc++] = strtok(nullptr,SEP)));//会多一个减掉g_argc--;return g_argc > 0 ? true : false;
}

6.命令执行

6.1.创建子进程

shell执行命令的实质就是进程替换,我们在做进程替换的时候不想结束父进程,那么需要我们创建一个新的子进程,让子进程来做替换。

6.2 处理内建命令

有一些命令比如cd,是一个内建命令,子进程是无法完成的,需要系统来执行。我们可以使用chdir来完成,chdir函数声明如下:

bool Cd()
{if(g_argc == 1){string home = GetHome();if(home.empty()){return true;}chdir(home.c_str());}else{string where = g_argv[1];chdir(where.c_str());}return true;
}

它的作用是进入某个目录,需要传一个目录的路径。

这里也支持echo的一些操作

void Echo()
{if(g_argc == 2){//echo "hello"//echo $?//echo $PATHstring opt = g_argv[1];if(opt == "$?"){cout << lastcode << endl;}else if(opt[0] == '$'){string env_name = opt.substr(1);const char* env_value = getenv(env_name.c_str());if(env_value){cout << env_value << endl;}}else{cout << opt << endl;}}
}
bool CheckAndExecBuiltin()
{string cmd = g_argv[0];if(cmd  == "cd"){Cd();return true;}else if(cmd == "echo"){Echo();return true;}return false;
}

6.3 文件重定向

  • pcd文件数组:储存了这个pcb打开的所有文件信息地址。
  • 文件描述符(记fd):pcb的文件数组中的一个下标,0下标的文件为标准输入流,1下标的文件为标准输出流,2下标的文件为标准错误流,这三个都是系统默认打开的文件。
  • 重定向:系统在对文件进行操作时只认fd,所以重定向的实质就是一个fd位置的信息被其他fd的信息覆盖。

" >,>>,指的都是从原来的标准输出(fd=1)重定向到某个文件,< 从原来的标准输入(fd=0)重定向到某个文件。所以这里我们只需要打开新的文件并获取到它的fd,然后使用dup2把新文件的地址信息覆盖到fd=1的文件上就行。然后关闭新文件的fd。"

int Execute()
{pid_t id = fork();if(id == 0){int fd = 0;//子进程检测重定向if(redir == INPUT_REDIR){fd = open(filename.c_str(), O_RDONLY);if(fd < 0){exit(1);}dup2(fd,0);close(fd);}else if(redir == OUTPUR_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC,0666);if(fd < 0){exit(2);}dup2(fd,1);close(fd);}else if(redir == APPEND_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY |O_APPEND,0666);if(fd < 0){exit(3);}dup2(fd,1);close(fd);}else{}//进程替换不会改变重定向的内容//子进程执行execvp(g_argv[0],g_argv);//程序替换后面的程序就不执行了exit(1);}//父进程等待int status = 0;pid_t rid = waitpid(id,&status,0);////取消报警//(void)rid;if(rid > 0){lastcode = WEXITSTATUS(status);}return 0;
}

7.源码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]#"//shell自定义的全局变量
//1.命令行参数表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;//2.环境变量表
#define MAXENVS 100
char* g_env[MAXENVS];
int g_envs = 0;//for test
char cwd[1024];
char cwdenv[1024];//last exit code
int lastcode = 0;//3.关于重定向的内容
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUR_REDIR 2
#define APPEND_REDIR 3int redir = NONE_REDIR;
string filename;const char* GetUserName()
{const char* name = getenv("USER");return name == NULL ? "None" : name; 
}const char* GetHostName()
{const char* hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
}const char* GetPwd()
{//const char* pwd = getenv("PWD");const char* pwd = getcwd(cwd,sizeof(cwd));if(pwd != NULL){snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",pwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "" : home;
}std::string DirName(const char* pwd)
{
#define SLASH "/"std::string dir = pwd;if(dir == SLASH){return SLASH;}auto pos = dir.rfind(SLASH);if(pos == std::string::npos){return "BUG";}return dir.substr(pos + 1);
}void MakeCommandLine(char cmd_prompt[],int size)
{//snprintf(cmd_prompt,size,"[%s@%s %s]#",GetUserName(),GetHostName(),GetPwd());//snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
}void PrintCommandPrompt()
{char prompt[COMMAND_SIZE];MakeCommandLine(prompt,sizeof(prompt));printf("%s",prompt);fflush(stdout);
}bool GetCommandLine(char* out,int size)
{ char* c = fgets(out, size,stdin);if(c == nullptr){return false; }//去掉\nc[strlen(out) -1] = 0;if(strlen(c) == 0){return false;}return true;
}//命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char* commandline)
{
#define SEP " "g_argc = 0;g_argv[g_argc++] = strtok(commandline,SEP);while((bool)(g_argv[g_argc++] = strtok(nullptr,SEP)));//会多一个减掉g_argc--;return g_argc > 0 ? true : false;
}void InitEnv()
{extern char** environ;memset(g_env,0,sizeof(g_env));g_envs = 0;//配置文件//获取环境变量for(int i = 0;environ[i];i++){//申请空间g_env[i] = (char*)malloc(strlen(environ[i]) + 1);strcpy(g_env[i],environ[i]);g_envs++;}//for testg_env[g_envs++] = (char*)"MIHAYOU=666"; g_env[g_envs] = NULL; //导成环境变量for(int i = 0;g_env[i];i++){putenv(g_env[i]);}environ = g_env;
}bool Cd()
{if(g_argc == 1){string home = GetHome();if(home.empty()){return true;}chdir(home.c_str());}else{string where = g_argv[1];chdir(where.c_str());}return true;
}void Echo()
{if(g_argc == 2){//echo "hello"//echo $?//echo $PATHstring opt = g_argv[1];if(opt == "$?"){cout << lastcode << endl;}else if(opt[0] == '$'){string env_name = opt.substr(1);const char* env_value = getenv(env_name.c_str());if(env_value){cout << env_value << endl;}}else{cout << opt << endl;}}
}bool CheckAndExecBuiltin()
{string cmd = g_argv[0];if(cmd  == "cd"){Cd();return true;}else if(cmd == "echo"){Echo();return true;}return false;
}void PrintArgv()
{for(int i = 0;g_argv[i];i++){printf("argv[%d]->%s\n", i,g_argv[i]);}printf("size = %d\n", g_argc);
}void clear()
{for(int i = 0;g_env[i];i++){free(g_env[i]);g_env[i] = NULL;}
}int Execute()
{pid_t id = fork();if(id == 0){int fd = 0;//子进程检测重定向if(redir == INPUT_REDIR){fd = open(filename.c_str(), O_RDONLY);if(fd < 0){exit(1);}dup2(fd,0);close(fd);}else if(redir == OUTPUR_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC,0666);if(fd < 0){exit(2);}dup2(fd,1);close(fd);}else if(redir == APPEND_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY |O_APPEND,0666);if(fd < 0){exit(3);}dup2(fd,1);close(fd);}else{}//进程替换不会改变重定向的内容//子进程执行execvp(g_argv[0],g_argv);//程序替换后面的程序就不执行了exit(1);}//父进程等待int status = 0;pid_t rid = waitpid(id,&status,0);////取消报警//(void)rid;if(rid > 0){lastcode = WEXITSTATUS(status);}return 0;
}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;//"ls -a -l > file.txt" //> >>  <while(end > start){if(cmd[end] == '<'){cmd[end++] = 0;TrimSpace(cmd,end);redir = INPUT_REDIR;filename = cmd + end;break;}else if(cmd[end] == '>'){//>>if(cmd[end - 1] == '>'){cmd[end-1] = 0;redir = APPEND_REDIR;}//>else{redir = OUTPUR_REDIR;}cmd[end++] = 0;TrimSpace(cmd,end);filename = cmd + end;break;}else{end--;}}}int main()
{//shell启动的时候从系统获得环境变量//我们的环境变量信息应该统一从父shell取InitEnv();while(1){//1.打印提示信息//printf("[%s@%s %s]#", GetUserName(),GetHostName(),GetPwd());PrintCommandPrompt();//2.获取命令行提示符char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline,sizeof(commandline))){continue;}//3.重定向分析 "ls -a > file.txt" -> "ls -a" "file.txt"//> >> < 判断重定向的方向//> 输出   >> 拼接输出  < 输入RedirCheck(commandline);//printf("redir = %d filename = %s\n", redir, filename.c_str());//4.命令行分析if(!CommandParse(commandline)){continue;}//PrintArgv();//5.检测是否是内键命令if(CheckAndExecBuiltin()){continue;}//6.执行命令Execute();}//清理工作clear();return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Boost库智能指针boost::shared_ptr详解和常用场景使用错误示例以及解决方法

1、Boost智能指针 —— boost::shared_ptr 详解一、什么是 boost::shared_ptr boost::shared_ptr 是 Boost 库中实现的一个智能指针模板类&#xff0c;用于管理动态分配的对象生命周期&#xff0c;采用引用计数机制。多个 shared_ptr 实例可以共享同一个对象的所有权&#xff0…

科学分析指南,如何快速找到并清理磁盘的无用文件

随着时间的推移&#xff0c;系统中会积累大量的临时文件、缓存文件、不再需要的安装包或其他大型文件。磁盘清理可以删除这些不必要的文件&#xff0c;从而释放宝贵的磁盘空间。它无需安装&#xff0c;插上 U 盘就能直接使用。只需勾选需要扫描的磁盘&#xff0c;点击“开始分析…

Laravel 系统版本查看及artisan管理员密码找回方法针对各个版本通用方法及原理-优雅草卓伊凡

Laravel 系统版本查看及artisan管理员密码找回方法针对各个版本通用方法及原理-优雅草卓伊凡一、查看 Laravel 版本的方法优雅草蜻蜓T会议系统专业版 最近又有客户要了&#xff0c;但是发现 密码不对 管理员账户密码不对&#xff0c;卓伊凡必须处理下&#xff0c;这里顺便讲解密…

针对大规模语言模型的上下文工程技术调研与总结(翻译并摘要)

针对大规模语言模型的上下文工程技术调研与总结声明摘要部分翻译介绍部分翻译相关工作部分翻译并摘要为什么使用上下文工程&#xff08;翻译并摘要&#xff09;基础组件&#xff08;翻译并摘要&#xff09;RAG&#xff08;翻译并摘要简单介绍一下个人认为比较好的技术&#xff…

QT配置Quazip外部库

1.下载QuaZip源码网址&#xff1a;https://sourceforge.net/projects/quazip/  注&#xff1a;下载->解压->打开.pro文件2.编译QuaZip源码2.1配置zlib注&#xff1a;QuaZip需zlib的支持&#xff0c;我们需要引用zlib找到本地安装Qt目录下zlib目录&#xff1a;注&#x…

从C++开始的编程生活(4)——类的定义、访问限定符、类域、类的实例化和this指针

前言 本系列文章承接C语言的学习&#xff0c;需要有C语言的基础才能学会哦~ 第3篇主要讲的是有关于C的类的定义、访问限定符、类域、类的实例化和this指针。 C才起步&#xff0c;都很简单呢&#xff01; 目录 前言 类 基本语法 访问限定符 基本语法 类域 类的实例化 内…

AD域控制器虚拟化的安全加固最佳实践

以下是AD域控制器虚拟化安全加固的7项核心实践&#xff0c;结合最新Windows Server 2022特性与虚拟化环境需求&#xff1a;基础架构强化‌ 采用静态IP分配并确保所有域控节点DNS指向主DC&#xff08;如192.168.1.10&#xff09;‌ 虚拟机模板需预配置林/域功能级别为Windows Se…

java解析nc气象数据

1.1pom.xml<dependency><groupId>edu.ucar</groupId><artifactId>netcdfAll</artifactId><version>5.4.1</version></dependency>1.2 netcdf使用/** param type 0 ,1, 2 wind 1 or 2 其他 0 .* return Map* */public Map i…

STC8H8K64U SKDIP28芯片频率占空比PWM波形

/****PWM输出任意周期占空比波形*******/ #include "STC8H.h" // #include "intrins.h" // #define uchar unsigned char // #define uint unsig…

【RK3576】【Android14】USB开发调试

获取更多相关的【RK3576】【Android14】驱动开发&#xff0c;可收藏系列博文&#xff0c;持续更新中&#xff1a; 【RK3576】Android 14 驱动开发实战指南 硬件接口 RK3576支持两个USB3.0控制器 驱动开发 dts配置 在“Android14/kernel-6.1/arch/arm64/boot/dts/rockchip/rk…

20. TaskExecutor与ResourceManager心跳

20. TaskExecutor与ResourceManager心跳 现在&#xff0c;需要回过头看 ResourceManager是如何产生心跳管理服务的。cluster.initializeServices 方法的 heartbeatServices createHeartbeatServices(configuration);产生一个 HeartbeatServicesImpljobmanager的 resourceManag…

OS19.【Linux】进程状态(上)

目录 1.情景引入 2.操作系统学科对进程状态的分类 运行状态 基于时间片的轮转调度算法 阻塞状态 等待IO设备的例子 等待其他进程中需要获取的数据 进程唤醒 挂起状态(全称为阻塞挂起状态) 简单谈谈虚拟内存管理 就绪状态 笔面试题 3.Linux对进程状态的分类 R和S状…

如何优雅地修改项目的 Android 版本(API 级别)

引言 在 Android 开发的日常迭代中&#xff0c;我们经常需要升级或降级项目的 minSdkVersion、targetSdkVersion 与 compileSdkVersion。升级可以解锁新特性和性能优化&#xff1b;降级则可能为了兼容旧机型或快速验证问题。本文将手把手演示在 Android Studio 里修改 Android …

GNU Radio多类信号多种参数数据集生成技巧

参考我的这篇博客&#xff0c;我想自制一个多信号数据集&#xff1a; 【多雷达信号硬件模拟】 3台USRP1台VSG信号发生器模拟多雷达信号&#xff0c;1台USRP产生高斯噪声模拟更多信道环境&#xff0c;1台USRP采集信号 需要在多个波段对四种信号进行参数设置&#xff0c;带宽有…

Ansible + Shell 服务器巡检脚本

脚本概述这是一个用于服务器日常巡检的 Shell 脚本&#xff0c;主要功能包括&#xff1a;检查多台主机的网络连通性 监控CPU、内存和磁盘使用率 生成详细的巡检报告 通过企业微信发送告警通知核心技术点1. 主机批量管理使用Ansible工具远程执行命令和脚本 通过主机…

Linux-rpm和yum

一、RPMRPM&#xff08;Red Hat Package Manager&#xff09;是一个用于管理 Red Hat 系列 Linux 发行版&#xff08;如 RHEL、CentOS、Fedora&#xff09;软件包的工具。RPM 允许用户以统一的格式来安装、卸载、升级和查询软件包。它是 .rpm 文件的主要工具&#xff0c;后缀名…

手推OpenGL相机的正交投影矩阵和透视投影矩阵(附源码)

概述计算OpenGL的正交投影矩阵和透视投影矩阵是有现成函数的。自己手推不是为了重复造轮子。手推一遍&#xff0c;可以极大的加强对这两个矩阵的理解。同时也可以满足一下自己求知欲。正交投影矩阵手推正交投影矩阵源码 WGMatrix4x4 WGMatrix4x4::BuildOrtho(double l, double …

【跨国数仓迁移最佳实践2】MaxCompute SQL执行引擎对复杂类型处理全面重构,保障客户从BigQuery平滑迁移

本系列文章将围绕东南亚头部科技集团的真实迁移历程展开&#xff0c;逐步拆解 BigQuery 迁移至 MaxCompute 过程中的关键挑战与技术创新。本篇为第二篇&#xff0c;跨国数仓迁移背后 MaxCompute 的统一存储格式创新。 注&#xff1a;客户背景为东南亚头部科技集团&#xff0c;…

react(基础篇)

React由Meta公司研发&#xff0c;用于构建Web和原生交互界面的库。 React 官方中文文档 查看JSX &#xff08;一&#xff09;React组件 用户界面的一部分&#xff0c;通俗的来讲&#xff0c;最小的元素组成的单元&#xff0c;可以实现部分逻辑与功能 房子的门就可以看成一个…

数据结构-哈希表(一)哈希函数、哈希表介绍、优缺点

哈希表 哈希函数哈希表使用了哈希函数来完成key到地址的快速映射&#xff0c;所以在了解哈希表之前&#xff0c;需要先明白哈希函数的概念和特点。 哈希函数的定义 哈希函数 哈希函数是一种将任意长度输入的数据&#xff0c;转换成固定长度输出的算法哈希函数H可以表示为yH(x) …