【微软技术栈】C#.NET 正则表达式源生成器

news2024/10/5 18:33:55

本文内容

  1. 已编译的正则表达式
  2. 源生成
  3. 在源生成的文件中
  4. 何时使用

正则表达式 (regex) 是一个字符串,它使开发人员能够表达要搜索的模式,使其成为搜索文本和提取结果作为已搜索字符串子集的一种很常见的方法。 在 .NET 中,System.Text.RegularExpressions 命名空间用于定义 Regex 实例和静态方法,并匹配用户定义的模式。 本文介绍如何使用源生成来生成 Regex 实例以优化性能。

 备注

请尽可能地使用源生成的正则表达式,而不是使用 RegexOptions.Compiled 选项编译正则表达式。 源生成可帮助应用更快地启动、更快地运行且更易剪裁。

1、已编译的正则表达式

编写 new Regex("somepattern") 时,会发生一些事情。 将分析指定的模式,既要确保模式的有效性,又要将其转换为表示已分析正则表达式的内部树。 然后,以各种方式优化树,将模式转换为功能上等效的变体,可以更高效地执行。 该树被写入一种形式,可以解释为一系列操作码和操作数,它们提供有关如何匹配的正则表达式解释器引擎的说明。 执行匹配时,解释器只需遍历这些指令,针对输入文本处理它们。 实例化新的 Regex 实例或调用 Regex 上的一个静态方法时,解释器是采用的默认引擎。

指定 RegexOptions.Compiled 时,将执行所有相同的构造时间工作。 生成的指令将由基于反射发出的编译器进一步转换为 IL 指令,这些指令将写入几个 DynamicMethod。 执行匹配时,将调用这些 DynamicMethod。 此 IL 实质上将完全执行解释器将执行的操作,但专用于所处理的确切模式除外。 例如,如果模式包含 [ac],解释器将看到一个操作码,表示“将当前位置的输入字符与在此集说明中指定的集匹配”,而编译的 IL 将包含有效表示“将目前位置的输入字母与 'a' 或 'c' 相匹配”的代码。 这种特殊的大小写和基于模式知识执行优化的能力是指定 RegexOptions.Compiled 比解释器产生更快的匹配吞吐量的主要原因。

RegexOptions.Compiled 有几个缺点。 最有影响力的是,它产生的构造成本比使用解释器要多得多。 不仅要为解释器支付相同的成本,而且还需要将生成的 RegexNode 树和生成的操作码/操作数编译到 IL 中,这会增加不小的开销。 生成的 IL 还需要在首次使用时进行 JIT 编译,这会导致启动时产生更多费用。 RegexOptions.Compiled 表示首次使用开销与每次后续使用开销之间的基本权衡。 System.Reflection.Emit 的使用也会抑制某些环境中 RegexOptions.Compiled 的使用;某些操作系统不允许动态生成的代码执行,并且在此类系统上,Compiled 将变为无操作。

2、源生成

.NET 7 引入了新的 RegexGenerator 源生成器。 当 C# 编译器重写为“Roslyn”C# 编译器时,它将公开整个编译管道的对象模型以及分析器。 最近,Roslyn 已启用源生成器。 就像分析器一样,源生成器是插入编译器的组件,并且可以提供与分析器相同的所有信息,但除了能够发出诊断信息外,它还可以使用其他源代码来增强编译单元。 .NET 7 SDK 包含一个新的源生成器,用于识别返回 Regex 的分部方法上的新 GeneratedRegexAttribute。 源生成器提供该方法的实现,该方法用于实现 Regex 的所有逻辑。 例如,可能已编写如下所示的代码:

private static readonly Regex s_abcOrDefGeneratedRegex =
    new(pattern: "abc|def",
        options: RegexOptions.Compiled | RegexOptions.IgnoreCase);

private static void EvaluateText(string text)
{
    if (s_abcOrDefGeneratedRegex.IsMatch(text))
    {
        // Take action with matching text
    }
}

现在可以按如下所示重写前面的代码:

[GeneratedRegex("abc|def", RegexOptions.IgnoreCase, "en-US")]
private static partial Regex AbcOrDefGeneratedRegex();

private static void EvaluateText(string text)
{
    if (AbcOrDefGeneratedRegex().IsMatch(text))
    {
        // Take action with matching text
    }
}

生成的 AbcOrDefGeneratedRegex() 实现同样缓存 Regex 单一实例,因此不需要额外的缓存来使用代码。

 提示

源生成器会忽略标志 RegexOptions.Compiled,因此在源生成的版本中不再需要它。

cached-instance.png

但可以看到,它不仅仅是在执行 new Regex(...)。 相反,源生成器以 C# 代码的形式发出自定义 Regex 派生实现,其逻辑类似于 IL 中 RegexOptions.Compiled 发出的内容。 可以获得 RegexOptions.Compiled 的所有吞吐量性能优势(实际上更多)和 Regex.CompileToAssembly 的启动优势,但没有 CompileToAssembly 的复杂性。 发出的源是项目的一部分,这意味着它也可以轻松查看和调试。

debuggable-source.png

 提示

在 Visual Studio 中,右键单击分部方法声明,然后选择“转到定义”。 或者,也可以在“解决方案资源管理器”中选择项目节点,然后展开“依赖项”>“分析器”>“System.Text.RegularExpressions.Generator”>“System.Text.RegularExpressions.Generator.RegexGenerator”>“RegexGenerator.gcs”,查看从此正则表达式生成器生成的 C# 代码。

可以在其中设置断点,可以逐步执行,并可以使用它作为学习工具来准确了解正则表达式引擎使用输入处理模式的方式。 生成器甚至会生成三斜杠 (XML) 注释,以帮助使表达式一目了然和了解使用的位置。

xml-comments.png

3、在源生成的文件中

使用 .NET 7 时,源生成器和 RegexCompiler 几乎完全重写,从根本上改变了生成的代码的结构。 此方法已扩展,以处理所有构造(有一个警告),RegexCompiler 和源生成器仍遵循新方法以 1:1 的方式彼此映射。 请考虑 (a|bc)d 表达式中某个主要函数的源生成器输出:

private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
    int pos = base.runtextpos;
    int matchStart = pos;
    int capture_starting_pos = 0;
    ReadOnlySpan<char> slice = inputSpan.Slice(pos);

    // 1st capture group.
    //{
        capture_starting_pos = pos;

        // Match with 2 alternative expressions.
        //{
            if (slice.IsEmpty)
            {
                UncaptureUntil(0);
                return false; // The input didn't match.
            }

            switch (slice[0])
            {
                case 'a':
                    pos++;
                    slice = inputSpan.Slice(pos);
                    break;

                case 'b':
                    // Match 'c'.
                    if ((uint)slice.Length < 2 || slice[1] != 'c')
                    {
                        UncaptureUntil(0);
                        return false; // The input didn't match.
                    }

                    pos += 2;
                    slice = inputSpan.Slice(pos);
                    break;

                default:
                    UncaptureUntil(0);
                    return false; // The input didn't match.
            }
        //}

        base.Capture(1, capture_starting_pos, pos);
    //}

    // Match 'd'.
    if (slice.IsEmpty || slice[0] != 'd')
    {
        UncaptureUntil(0);
        return false; // The input didn't match.
    }

    // The input matched.
    pos++;
    base.runtextpos = pos;
    base.Capture(0, matchStart, pos);
    return true;

    // <summary>Undo captures until it reaches the specified capture position.</summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    void UncaptureUntil(int capturePosition)
    {
        while (base.Crawlpos() > capturePosition)
        {
            base.Uncapture();
        }
    }
}

源生成的代码的目标易于理解,具有易于遵循的结构,具有解释每个步骤正在执行的操作的注释,并且通常根据指导原则发出代码,即生成器应发出代码,就像人类编写代码一样。 即使涉及回溯,回溯的结构也会成为代码结构的一部分,而不是依赖堆栈来指示下一步的跳转位置。 例如,以下是表达式为 [ab]*[bc] 时生成的相同匹配函数的代码:

private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
    int pos = base.runtextpos;
    int matchStart = pos;
    int charloop_starting_pos = 0, charloop_ending_pos = 0;
    ReadOnlySpan<char> slice = inputSpan.Slice(pos);
    
    // Match a character in the set [ab] greedily any number of times.
    //{
        charloop_starting_pos = pos;

        int iteration = slice.IndexOfAnyExcept('a', 'b');
        if (iteration < 0)
        {
            iteration = slice.Length;
        }

        slice = slice.Slice(iteration);
        pos += iteration;

        charloop_ending_pos = pos;
        goto CharLoopEnd;

        CharLoopBacktrack:

        if (Utilities.s_hasTimeout)
        {
            base.CheckTimeout();
        }

        if (charloop_starting_pos >= charloop_ending_pos ||
            (charloop_ending_pos = inputSpan.Slice(
                charloop_starting_pos, charloop_ending_pos - charloop_starting_pos)
                .LastIndexOfAny('b', 'c')) < 0)
        {
            return false; // The input didn't match.
        }
        charloop_ending_pos += charloop_starting_pos;
        pos = charloop_ending_pos;
        slice = inputSpan.Slice(pos);

        CharLoopEnd:
    //}

    // Advance the next matching position.
    if (base.runtextpos < pos)
    {
        base.runtextpos = pos;
    }

    // Match a character in the set [bc].
    if (slice.IsEmpty || !char.IsBetween(slice[0], 'b', 'c'))
    {
        goto CharLoopBacktrack;
    }

    // The input matched.
    pos++;
    base.runtextpos = pos;
    base.Capture(0, matchStart, pos);
    return true;
}

可以在代码中看到回溯的结构,其中发出了一个 CharLoopBacktrack 标签(用于回溯到的位置)和一个 goto(用于在正则表达式后续部分失败时跳转到该位置)。

如果查看实现 RegexCompiler 的代码和源生成器,它们看起来非常相似:类似命名的方法、类似的调用结构,甚至整个实现中的类似注释。 在大多数情况下,它们会生成相同的代码,尽管一个在 IL 中,一个在 C# 中。 当然,C# 编译器负责将 C# 转换为 IL,因此这两种情况下生成的 IL 可能不相同。 源生成器依赖于在各种情况下,利用 C# 编译器进一步优化各种 C# 构造的事实。 因此,与 RegexCompiler 相比,源生成器将生成一些更优化的匹配代码。 例如,在前面的一个示例中,可以看到发出 switch 语句的源生成器,其中一个分支用于 'a',另一个分支用于 'b'。 由于 C# 编译器非常擅长优化 switch 语句,可以使用多种策略来有效地优化语句,因此源生成器具有一种 RegexCompiler 无法进行的特殊优化。 对于交替,源生成器将查看所有分支,如果可以证明每个分支都以不同的起始字符开头,它将在第一个字符上发出 switch 语句,并避免为该交替输出任何回溯代码。

下面是一个稍微复杂的相关示例。 在 .NET 7 中,将更深入地分析交替,以确定是否可以采用某种方式重构它们,从而使它们更容易被回溯引擎优化,这将导致更简单的源生成的代码。 其中一种优化支持从分支中提取常见前缀,如果交替是原子性的,则排序并不重要,重新排序分支以允许更多此类提取。 可以看到这对以下工作日模式 Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday 的影响,该模式生成如下匹配函数:

private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
    int pos = base.runtextpos;
    int matchStart = pos;
    ReadOnlySpan<char> slice = inputSpan.Slice(pos);

    // Match with 5 alternative expressions, atomically.
    {
        if (slice.IsEmpty)
        {
            return false; // The input didn't match.
        }

        switch (slice[0])
        {
            case 'M':
                // Match the string "onday".
                if (!slice.Slice(1).StartsWith("onday"))
                {
                    return false; // The input didn't match.
                }

                pos += 6;
                slice = inputSpan.Slice(pos);
                break;

            case 'T':
                // Match with 2 alternative expressions, atomically.
                {
                    if ((uint)slice.Length < 2)
                    {
                        return false; // The input didn't match.
                    }

                    switch (slice[1])
                    {
                        case 'u':
                            // Match the string "esday".
                            if (!slice.Slice(2).StartsWith("esday"))
                            {
                                return false; // The input didn't match.
                            }

                            pos += 7;
                            slice = inputSpan.Slice(pos);
                            break;

                        case 'h':
                            // Match the string "ursday".
                            if (!slice.Slice(2).StartsWith("ursday"))
                            {
                                return false; // The input didn't match.
                            }

                            pos += 8;
                            slice = inputSpan.Slice(pos);
                            break;

                        default:
                            return false; // The input didn't match.
                    }
                }

                break;

            case 'W':
                // Match the string "ednesday".
                if (!slice.Slice(1).StartsWith("ednesday"))
                {
                    return false; // The input didn't match.
                }

                pos += 9;
                slice = inputSpan.Slice(pos);
                break;

            case 'F':
                // Match the string "riday".
                if (!slice.Slice(1).StartsWith("riday"))
                {
                    return false; // The input didn't match.
                }

                pos += 6;
                slice = inputSpan.Slice(pos);
                break;

            case 'S':
                // Match with 2 alternative expressions, atomically.
                {
                    if ((uint)slice.Length < 2)
                    {
                        return false; // The input didn't match.
                    }

                    switch (slice[1])
                    {
                        case 'a':
                            // Match the string "turday".
                            if (!slice.Slice(2).StartsWith("turday"))
                            {
                                return false; // The input didn't match.
                            }

                            pos += 8;
                            slice = inputSpan.Slice(pos);
                            break;

                        case 'u':
                            // Match the string "nday".
                            if (!slice.Slice(2).StartsWith("nday"))
                            {
                                return false; // The input didn't match.
                            }

                            pos += 6;
                            slice = inputSpan.Slice(pos);
                            break;

                        default:
                            return false; // The input didn't match.
                    }
                }

                break;
                
            default:
                return false; // The input didn't match.
        }
    }

    // The input matched.
    base.runtextpos = pos;
    base.Capture(0, matchStart, pos);
    return true;
}

请注意 Thursday 是如何被重新排序为紧跟在 Tuesday 之后的,以及 Tuesday/Thursday 对和 Saturday/Sunday 对如何以多级开关结束。 在极端情况下,如果你要创建许多不同单词的长交替,源生成器最终会发出 trie^1 的逻辑等效项,从而读取每个字符并 switch(切换)到用于处理单词的其余部分的分支。 这是匹配字词的一种非常高效的方法,也是源生成器在此处执行的操作。

同时,源生成器还有其他问题需要解决,直接输出到 IL 时根本不存在这些问题。 如果你回过头来看看几个代码示例,可以看到一些大括号有些奇怪地注释掉了。这不是一个错误。 源生成器认识到,如果未注释掉这些大括号,则回溯的结构依赖于从范围外跳到该范围内定义的标签;此类标签对于此类 goto 不可见,代码将无法编译。 因此,源生成器需要避免在方式上存在范围。 在某些情况下,只需按照此处所述注释掉范围。 在其他不可能的情况下,如果这样做会有问题,有时可能会避免需要范围的构造(例如多语句 if 块)。

源生成器处理所有 RegexCompiler 句柄,但有一个例外。 与处理 RegexOptions.IgnoreCase 一样,实现现在使用大小写表在构造时生成集,以及 IgnoreCase 向后引用匹配需要查阅该大小写表的方式。 该表是 System.Text.RegularExpressions.dll 的内部表,至少目前,该程序集的外部代码(包括源生成器发出的代码)没有访问该表的权限。 这使得处理 IgnoreCase 向后引用在源生成器中成为一个挑战,并且它们不受支持。 这是 RegexCompiler 支持的源生成器不支持的一个构造。 如果尝试使用具有其中之一(这种情况很少见)的模式,源生成器不会发出自定义实现,而是回退到缓存常规 Regex 实例:

cached-instance-fallback.png

此外,RegexCompiler 和源生成器都不支持新的 RegexOptions.NonBacktracking。 如果指定 RegexOptions.Compiled | RegexOptions.NonBacktracking,则只会忽略 Compiled 标志,如果将 NonBacktracking 指定给源生成器,它将同样回退到缓存常规 Regex 实例。

4、何时使用

一般指导是,如果可以使用源生成器,请使用它。 如果当前在 C# 中使用 Regex,并且在编译时有已知的参数,特别是如果你已经在使用 RegexOptions.Compiled(因为正则表达式已被确定为一个热点,可以从更快的吞吐量中受益),则应更倾向于使用源生成器。 源生成器将为正则表达式提供以下优势:

  • RegexOptions.Compiled 的所有吞吐量优势。
  • 在运行时不必执行所有正则表达式解析、分析和编译的启动优势。
  • 将提前编译与为正则表达式生成的代码一起使用的选项。
  • 更好的可调试性和对正则表达式的理解。
  • 通过剪裁与 RegexCompiler 关联的大量代码(甚至可能是反射发出自身),可以减小剪裁后的应用程序的大小。

当与源生成器无法为其生成自定义实现的 RegexOptions.NonBacktracking 等选项一起使用时,它仍将发出描述实现的缓存和 XML 注释,使其很有价值。 源生成器的主要缺点是它会向程序集发出其他代码,因此可能会增加大小。 应用程序中的正则表达式越多,它们就越大,将为它们发出的代码就越多。 在某些情况下,同样 RegexOptions.Compiled 可能不是必要的,源生成器也可能不是必要的。 例如,如果你有一个仅需要很少且吞吐量无关紧要的正则表达式,那么仅依赖解释器进行零星使用可能更有益。

 重要

.NET 7 包含一个分析器,用于标识可转换为源生成器的 Regex 的使用,还包含一个为你执行转换的修复程序:

convert-to-regexgenerator.png

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vuex使用一文搞懂

什么是Vuex Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 Vuex 是什么&#xff1f; | Vuex (vuejs.org) 图片来源&#xff0c;vuex官方文档 vuex安装 …

<蓝桥杯软件赛>零基础备赛20周--第5周--杂题-2

报名明年4月蓝桥杯软件赛的同学们&#xff0c;如果你是大一零基础&#xff0c;目前懵懂中&#xff0c;不知该怎么办&#xff0c;可以看看本博客系列&#xff1a;备赛20周合集 20周的完整安排请点击&#xff1a;20周计划 每周发1个博客&#xff0c;共20周&#xff08;读者可以按…

无线城市WiFi解决方案【完整Word】

wx供重浩&#xff1a;创享日记 获取完整无水印高清Word版 文章目录 第1章 项目背景1.1“无线城市”的定义1.2 国内外“无线城市”发展概况1.3 典型案例分析1.4 建设无线城市的必要性1.5 无线城市能为政府带来的价值 第2章 项目需求分析2.1 无线城市的现状分析2.2 无线城市的总体…

HackTheBox-Starting Point--Tier 2---Base

文章目录 一 题目二 过程记录2.1 打点2.2 权限获取2.3 横向移动2.4 权限提升 一 题目 Tags Web、Vulnerability Assessment、Custom Applications、Source Code Analysis、Authentication、Apache、PHP、Reconnaissance、Web Site Structure Discovery、SUDO Exploitation、Au…

GPIO实验:ARM汇编代码实现LED灯亮灭控制

GPIO实验&#xff1a;ARM汇编代码实现LED灯亮灭控制 一、 汇编工程模板Makefile分析 NAMEasm-led #指定编译的源文件名字 CROSS_COMPILE arm-linux-gnueabihf- #指定交叉编译工具链前缀CC $(CROSS_COMPILE)gcc #指定gcc名字LD $(CROSS_COMPILE)ld #指定链接器名字…

计算机网络第一章(计算机网络开篇)

目录 一.什么是计算机网络1.0 何为计算机网络1.1 什么是Internet?1.2 互联网与互连网1.3 互联网基础结构发展的三个阶段 二.什么是网络协议2.1 协议的三要素2.2 internet协议标准 三. 互联网的组成3.1 边缘部分3.11 端系统之间的通信 3.2 核心部分3.21 数据交换技术 四. 计算机…

2023双十一:实体门店闯入,第二战场全面开战

“闺女&#xff0c;吃饺子了吗&#xff1f;”11月8日&#xff0c;立冬&#xff0c;忙碌一天的陈曦回家路上接到母亲电话&#xff0c;才想起来家里冷冻水饺没了&#xff0c;又不想再去超市&#xff0c;直接打开美团买菜买了两袋&#xff0c;回家就煮了吃。当然&#xff0c;最终她…

【GIT】Git中的Gui介绍,使用Git中的ssh协议介绍,使用使用idea集成Git

目录 一&#xff0c;Git中的Gui介绍&#xff0c;使用 二&#xff0c;Git中的ssh介绍&#xff0c;使用 三&#xff0c;使用idea集成Git 一&#xff0c;Git中的Gui介绍&#xff0c;使用 1.1什么是Gui&#xff1f; 图形化管理工具是一种通过可视化界面来操作计算机系统或应用程…

管理员模式运行cmd或则bat文件的时候,出现路径错误的问题

最近在使用Comfyui, 不清楚啥原因&#xff0c;有时候Git无法访问&#xff0c;有时候文件夹无法访问的。就想把它的运行bat命令直接用 管理员模式运行&#xff0c;给到最高的权限&#xff0c;试试。但就这么简单的问题&#xff0c;搜了半天&#xff0c;都是一大堆不靠谱的教程&a…

统信UOS Linux操作系统下怎么删除某个程序在开始菜单或桌面的快捷方式

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 引言 统信操作系统的开始菜单包罗万象&#xff0c;将所有应用的快捷方式都放在了开始菜单内。 虽然提供了分类展示的能力&#xff0c;但无论是分类方式还是未分类方式&#xff0c;都不能像windows一样将这…

《剪映短视频剪辑从入门到精通》:最好别跟我说你精通剪映短视频剪辑

《剪映短视频剪辑从入门到精通》&#xff1a;最好别跟我说你精通剪映短视频剪辑 毫无疑问&#xff0c;如今短视频已经彻底冲入了所有大众的视野&#xff0c;已经成为了我们生活中不可或缺的一部分。本文将从一个全新的视角&#xff0c;重新带领大家学习剪映短视频剪辑&#xff…

论文速览 | arxiv 2023, 马氏距离感知训练在分布外检测中的应用

注1:本文系“最新论文速览”系列之一,致力于简洁清晰地介绍、解读最新的顶会/顶刊论文。 OOD论文速览 | arxiv 2023, Mahalanobis-Aware Training for Out-of-Distribution Detection 该论文旨在提出一种改进的深度学习模型训练方法,以提高对分布外(OOD)样本的检测能力。…

商业计划书PPT怎么做?这个AI软件一键在线生成,做PPT再也不求人!

商业计划书是一份重要的书面文件&#xff0c;它通常被用作商业估值、筹资和进一步扩大业务的基础。一个好的商业计划书能够让团队向投资者、潜在客户和业务合作伙伴展示其企业的价值&#xff0c;并且清楚地阐述企业的产品或服务能够如何满足市场需求。作为商业计划书的重要组成…

职场必备技能-BI数据可视化,后悔没早学

数据分析能力的上限会影响到一个人职业生涯的上限&#xff0c;由此可见数据分析能力在职场技能中的重要性。而数据可视化则是将数据分析结果快速传递给职场人&#xff0c;让职场人足以利用这些信息去运营决策&#xff0c;提供业务水平、提高业绩。这么重要的职场必备技能&#…

多级缓存之实现多级缓存

多级缓存的实现离不开Nginx编程&#xff0c;而Nginx编程又离不开OpenResty。 1. OpenResty快速入门 我们希望达到的多级缓存架构如图&#xff1a; 其中&#xff1a; windows上的nginx用来做反向代理服务&#xff0c;将前端的查询商品的ajax请求代理到OpenResty集群 OpenRest…

操作系统·处理机调度死锁

3.1 处理机调度概述 3.1.1 处理机调度概述 高级调度 (High level Scheduling)决定把外存上哪些作业调入内存、创建进程、分配资源。高级调度又称作业调度、长程调度或宏观调度。只在批处理系统中有高级调度。 中级调度 (Middle level Scheduling)完成进程的部分或全部在内、…

华为ssl vpn配置案例

t先在命令行输入命令 v-gateway sslvpn interface GigabitEthernet1/0/2 private 打开在命令行建立的sslvpn名称 直接开网络权限最大的模式&#xff1a;网络扩展 建立用户完成后点击上面的应用&#xff1a; 用命令行加策略&#xff1a; security-policy default action p…

【Docker】Docker 网络

引言 Docker是一个开源的应用容器引擎&#xff0c;它允许开发者将应用及其依赖打包到一个可移植的容器中&#xff0c;然后发布到任何流行的Linux机器或Windows机器上&#xff0c;也可以实现虚拟化。Docker的主要优势之一是其网络功能&#xff0c;而网络功能的核心就是网络驱动…

【Mysql】联表查询

目录 表&#xff1a; 思路&#xff1a; inner join right join left join ​编辑 表&#xff1a; student表 class表 思路&#xff1a; 1.分析查找的字段来自哪些表 2.确定使用哪种连接查询 3.确定交叉点 比如student表的name与class表的name是相等的 inner join …