SparkSQL之Optimized LogicalPlan生成过程

news2024/11/19 21:15:02

  经过Analyzer的处理,Unresolved LogicalPlan已经解析成为Analyzed LogicalPlan。Analyzed LogicalPlan中自底向上节点分别对应Relation、Subquery、Filter和Project算子。
  Analyzed LogicalPlan基本上是根据Unresolved LogicalPlan一对一转换过来的,对于SQL语句中的逻辑能够很好地表示。然而,在实际应用中,很多低效的写法会带来执行效率的问题,需要进一步对Analyzed LogicalPlan进行处理,得到更优的逻辑算子树。于是,针对SQL逻辑算子树的优化器Optimizer应运而生。

Optimizer概述

  在分析Rule体系时就已经提到,Optimizer同样继承自RuleExecutor类,本身没有重载RuleExecutor中的execute方法,因此其执行过程仍然是调用其父类RuleExecutor中实现的execute方法。在QueryExecution中,Optimizer会对传入的Analyzed LogicalPlan执行execute方法,启动优化过程。

val optimizedPlan: LogicalPlan = optimizer.execute(analyzed)

  与Analyzer类似,Optimizer的主要机制也依赖重新定义的一系列规则,同样对应RuleExecutor类中的成员变量batches,因此在RuleExecutor执行execute方法时会直接利用这些规则Batch。
  如图1 所示,Optimizer继承自RuleExecutor,而SparkOptimizer又继承自Optimizer。在上述代码中,optimizer即是构造的SparkOptimizer类。从图中可以看出,Optimizer本身定义了12个规则Batch,在SparkOptimizer类中又添加了4个Batch。
请添加图片描述

图1 Optimizer规则

Optimizer 规则体系

  Spark 2.1版本的SparkOptimizer中共实现了16个Batch,其中包含了53条优化规则,本节对这些优化规则进行系统的分析。
(1)Batch Finish Analysis
该Batch包含5条优化规则,分别是EliminateSubqueryAliases、ReplaceExpressions、ComputeCurrentTime、GetCurrentDatabase和RewriteDistinctAggregates,这些规则都只执行一次。
① EliminateSubqueryAliases:消除子查询别名,对应逻辑算子树中的SubqueryAlias节点。一般来讲,Subqueries仅用于提供查询的视角范围(Scope)信息,一旦Analyzer阶段结束,该节点就可以被移除,该优化规则直接将SubqueryAlias替换为其子节点。
② ReplaceExpressions:表达式替换,在逻辑算子树中查找匹配RuntimeReplaceable的表达式并将其替换为能够执行的正常表达式。这条规则通常用来对其他类型的数据库提供兼容的能力,例如,可以用“coalesce”来替换支持“nvl”的表达式。
③ ComputeCurrentTime:计算与当前时间相关的表达式,在同一条SQL语句中可能包含多个计算时间的表达式,即CurrentDate和CurrentTimestamp,且该表达式出现在多个语句中。为避免不一致,ComputeCurrentTime对逻辑算子树中的时间函数计算一次后,将其他同样的函数替换成该计算结果。
④ GetCurrentDatabase:获取当前数据库,在SQL语句中可能会调用CurrentDatabase函数来获取Catalog中的当前数据库,而这个方法没必要在执行阶段再进行计算。GetCurrentDatabase规则执行CurrentDatabase并得到结果,然后用此结果替换所有的CurrentDatabase表达式。
⑤ RewriteDistinctAggregates:重写Distinct聚合操作,对于包含Distinct算子的聚合语句,这条规则将其转换为两个常规的聚合表达式。
严格来讲,Finish Analysis这个Batch中的一些规则更多的是为了得到正确的结果(例如ComputeCurrentTime),并不涉及优化操作,从逻辑上更应该归于Analyzer的分析规则中。但是考虑到Analyzer中会进行一些规范化的操作,因此将EliminateSubqueryAliases和ComputeCurrentTime规则放在优化的部分,实际上真正的优化过程从下一个Batch开始。
(2)Batch Union⇒CombineUnions
  针对Union操作的规则Batch,中间包含一条CombineUnions优化规则。在逻辑算子树中,当相邻的节点都是Union算子时,可以将这些相邻的Union节点合并为一个Union节点。在该规则中,flattenUnion是核心方法,用栈实现了节点的合并。需要注意的是,后续的优化操作可能会将原来不相邻的Union节点变得相邻,因此在后面的规则Batch中又加入了CombineUnions这条规则。
(3)Batch Subquery⇒OptimizeSubqueries
该Batch目前只包含OptimizeSubqueries这一条优化规则。当SQL语句包含子查询时,会在逻辑算子树上生成SubqueryExpression表达式。OptimizeSubqueries优化规则在遇到Subquery-Expression表达式时,进一步递归调用Optimizer对该表达式的子计划并进行优化。
(4)Batch ReplaceOperators
该Batch中的优化规则主要用来执行算子的替换操作。在SQL语句中,某些查询算子可以直接改写为已有的算子,避免进行重复的逻辑转换。Replace Operators中包含ReplaceIntersectWithSemiJoin、ReplaceExceptWithAntiJoin和ReplaceDistinctWithAggregate这3条优化规则。
① ReplaceIntersectWithSemiJoin:将Intersect操作算子替换为Left-Semi Join操作算子,从逻辑上来看,这两种算子是等价的。需要注意的是,ReplaceIntersectWithSemiJoin优化规则仅适用于INTERSECTDISTINCT类型的语句,而不适用于INTERSECTALL语句。此外,该优化规则执行之前必须消除重复的属性,避免生成的Join条件不正确。
② ReplaceExceptWithAntiJoin:将Except操作算子替换为Left-Anti Join操作算子,从逻辑上来看,这两种算子是等价的。与上一条优化规则一样,ReplaceExceptWithAntiJoin优化规则仅适用于EXCEPTDISTINCT类型的语句,而不适用于EXCEPTALL语句。此外,该优化规则执行之前必须消除重复的属性,避免生成的Join条件不正确。
③ ReplaceDistinctWithAggregate:该优化规则会将Distinct算子转换为Aggregate语句。在某些SQL语句中,Select直接进行Distinct操作,这种情况下可以将其直接转换为聚合操作。ReplaceDistinctWithAggregate规则会将Distinct算子替换为对应的Group By语句。
从以上描述中可以看出,ReplaceOperators主要针对的是集合类型的操作算子。
(5)Batch Aggregate
该Batch主要用来处理聚合算子中的逻辑,包括RemoveLiteralFromGroupExpressions和RemoveRepetitionFromGroupExpressions两条规则。RemoveLiteralFromGroupExpressions优化规则用来删除Group By语句中的常数,这些常数对于结果无影响,但是会导致分组数目变多。此外,如果Group By语句中全部是常数,则会将其替换为一个简单的常数0表达式。RemoveRepetitionFromGroupExpressions优化规则将重复的表达式从Group By语句中删除,同样对结果无影响。
(6)Batch Operator Optimizations
类似Analyzer中的Operator解析规则,该Batch包含了Optimizer中数量最多同时也是最常用的各种优化规则,共31条。从整体来看,这31条优化规则(如表1所示)可以分为3个模块:算子下推(Operator Push Down)、算子组合(Operator Combine)、常量折叠与长度削减(Constant Folding and Strength Reduction)。
  算子下推:算子下推是数据库中常用的优化方式,表1中所列的前8条规则都属于算子下推的模块。顾名思义,算子下推所执行的优化操作主要是将逻辑算子树中上层的算子节点尽量下推,使其靠近叶子节点,这样能够在不同程度上减少后续处理的数据量甚至简化后续的处理逻辑。以常见的列剪裁(ColumnPruning)优化为例,假设数据表中有A、B、C 3列,但是查询语句中只涉及A、B两列,那么ColumnPruning将会在读取数据后剪裁出这两列。又如Lim itPushDown优化规则,能够将LocalLimit算子下推到Union All和Outer Join操作算子的下方,减少这两种算子在实际计算过程中需要处理的数据量。
  算子组合:算子组合类型的优化规则将逻辑算子树中能够进行组合的算子尽量整合在一起,避免多次计算,以提高性能。表1中间6条规则(从CollapseRepartition到CombineUnions)都属于算子组合类型的优化。可以看到这些规则主要针对的是重分区(repartition)算子、投影(Project)算子、过滤(Filter)算子、Window算子、Limit算子和Union算子,其中CombineUnions在之前已经提到过。需要注意的是,这些规则主要针对的是算子相邻的情况。
请添加图片描述

表1 Batch Operator Optimizations中的规则

  常量折叠与长度削减:对于逻辑算子树中涉及某些常量的节点,可以在实际执行之前就完成静态处理。常量折叠与长度削减类型的优化规则主要针对的就是这种情况。表1中的后17条优化规则都属于这种类型。例如,在ConstantFolding规则中,对于能够foldable(可折叠)的表达式会直接在EmptyRow上执行evaluate操作,从而构造新的Literal表达式;PruneFilters优化规则会详细地分析过滤条件,对总是能够返回true或false的过滤条件进行特别的处理。
(7)Batch Check Cartesian Products⇒CheckCartesianProducts
该Batch只有CheckCartesianProducts这一条优化规则,用来检测逻辑算子树中是否存在笛卡儿积类型的Join操作。如果存在这样的操作,而SQL语句中没有显示地使用cross join表达式,则会抛出异常。CheckCartesianProducts规则必须在ReorderJoin规则执行之后才能执行,确保所有的Join条件收集完毕。需要注意的是,当“spark.sql.crossJoin.enabled”参数设置为true时,该规则会被忽略。
(8)Batch DecimalOptim izations⇒DecimalAggregates
该Batch只有DecimalAggregates这一条优化规则,用于处理聚合操作中与Decimal类型相关的问题。一般情况下,如果聚合查询中涉及浮点数的精度处理,性能就会受到很大的影响。对于固定精度的Decimal类型,DecimalAggregates规则将其当作unscaled Long类型来执行,这样可以加速聚合操作的速度。
(9)Batch Typed Filter Optimization⇒CombineTypedFilters
该Batch仅包含CombineTypedFilters这一条优化规则,用来对特定情况下的过滤条件进行合并。当逻辑算子树中存在两个TypedFilter过滤条件且针对同类型的对象条件时,CombineTypedFilters优化规则会将它们合并到同一个过滤函数中。
(10)Batch LocalRelation⇒ConvertToLocalRelation|PropagateEmptyRelation
该Batch主要用来优化与LocalRelation相关的逻辑算子树,包含ConvertToLocalRelation和PropagateEmptyRelation两条优化规则。ConvertToLocalRelation将LocalRelation上的本地操作(不涉及数据交互)转换为另一个LocalRelation,目前该规则实现较为简单,仅处理Project投影操作。PropagateEmptyRelation优化规则会将包含空的LocalRelation进行折叠。
(11)Batch OptimizeCodegen⇒OptimizeCodegen
该Batch只有OptimizeCodegen这一条优化规则,用来对生成的代码进行优化。OptimizeCodegen规则主要针对的是casewhen语句,当casewhen语句中的分支数目不超过配置中的最大数目时,该表达式才能执行代码生成。
(12)Batch RewriteSubquery⇒RewritePredicateSubquery|CollapseProject
该Batch主要用来优化子查询,目前包含RewritePredicateSubquery和CollapseProject两条优化规则。RewritePredicateSubquery将特定的子查询谓词逻辑转换为left-semi/anti join操作。其中,EXISTS和NOTEXISTS算子分别对应semi和anti类型的Join,过滤条件会被当作Join的条件;IN和NOT IN也分别对应semi和anti类型的Join,过滤条件和选择的列都会被当作join的条件。CollapseProject优化规则比较简单,类似CombineTypedFilters优化规则,会将两个相邻的Project算子组合在一起并执行别名替换,整合成一个统一的表达式。
(13)Batch OptimizeMetadataOnly Query⇒OptimizeMetadataOnlyQuery
该Batch仅执行一次,只有OptimizeMetadataOnlyQuery这一条规则,用来优化执行过程中只需查找分区级别元数据的语句。需要注意的是,OptimizeMetadataOnlyQuery优化规则适用于扫描的所有列都是分区列且包含聚合算子的情形,而且聚合算子需要满足以下情况之一:聚合表达式是分区列;分区列的聚合函数有DISTINCT算子;分区列的聚合函数中是否有DISTINCT算子不影响结果。
(14)Batch Extract Python UDF from Aggregate⇒ExtractPythonUDFFrom Aggregate
该Batch仅执行一次,只有ExtractPythonUDFFrom Aggregate这一条规则,用来提取出聚合操作中的Python UDF函数。该规则主要针对的是采用PySpark提交查询的情形,将参与聚合的Python自定义函数提取出来,在聚合操作完成之后再执行。
(15)Batch Prune FileSource TablePartitions⇒PruneFileSourcePartitions
该Batch仅执行一次,只有PruneFileSourcePartitions这一条规则,用来对数据文件中的分区进行剪裁操作。当数据文件中定义了分区信息且逻辑算子树中的LogicalRelation节点上方存在过滤算子时,PruneFileSourcePartitions优化规则会尽可能地将过滤算子下推到存储层,这样可以避免读入无关的数据分区。
(16)Batch User Provided Optimizers⇒ExperimentalMethods.extraOptimizations
顾名思义,该Batch用于支持用户自定义的优化规则,其中ExperimentalMethods的extraOptim izations队列默认为空。可以看到,Spark SQL在逻辑算子树的转换阶段是高度可扩展的,用户只需要继承Rule[LogicalPlan]虚类,实现相应的转换逻辑就可以注册到优化规则队列中应用执行。

Optimized LogicalPlan的生成过程

  上述内容对SparkOptimizer中的优化规则进行了系统概述,现在回到案例对应的Analyzed LogicalPlan。接下来,将会重点分析Optimzer对该逻辑算子树进行优化处理的具体流程。
  对于案例生成的Analyzed LogicalPlan,首先执行的是Finish Analysis这个Batch中的Eliminate-SubqueryAliases优化规则,用来消除子查询别名的情形。
  EliminateSubqueryAliases优化规则的实现逻辑如以下代码所示,可以看到,该规则的实现非常简单,直接将SubqueryAlias逻辑算子树节点替换为其子节点。经过EliminateSubqueryAliases规则优化后的逻辑算子树如图2所示。可见SubqueryAlias节点被删除,Filter节点直接作用于Relation节点。

object EliminateSubqueryAliases extends Rule[LogicalPlan] {
	def apply(plan: LogicalPlan): LogicalPlan = plan transformUp {
		case SubqueryAlias(_, child, _) = child 
	}
}

请添加图片描述

图2 Optimized LogicalPlan生成的第1步

  第2步优化将匹配OperatorOptimizations这个Batch中的InferFiltersFromConstraints优化规则,用来增加过滤条件。InferFiltersFromConstraints优化规则会对当前节点的约束条件进行分析,生成额外的过滤条件列表,这些过滤条件不会与当前算子或其子节点现有的过滤条件重叠,具体实现如以下代码片段所示(注:案例逻辑算子树中不涉及Join查询语句,因此这里的代码片段中未包含Join算子的匹配部分)。

object InferFiltersFromConstraints extends Rule[LogicalPlan] with PredicateHelper {
	def apply(plan: LogicalPlan): LogicalPlan = plan transform {
		case filter @ Filter(condition, child) => 
			val newFilters = filter.constraints -- 
				(child.constraints ++ splitConjunctivePredicates(condition))
			if (newFilters.nonEmpty) {
				Filter(And(newFilters.reduce(And), condition), child)
			} else {
				filter
			}
	}
}

  从上述代码逻辑可知,对于上一步生成的逻辑算子树中的Filter节点,会构造新的过滤条件(newFilter)。当新的过滤条件不为空时,会与现有的过滤条件进行整合,构造新的Filter逻辑算子节点。
  经过InferFiltersFromConstraints规则优化之后的逻辑算子树如图3所示,Filter逻辑算子树节点中多了“isnotnull(age#0L)”这个过滤条件。该过滤条件来自于Filter中的约束信息,用来确保筛选出来的数据age字段不为null。
  最后一步,上述逻辑算子树会匹配Operator Optimizations这个Batch中的ConstantFolding优化规则,对LogicalPlan中可以折叠的表达式进行静态计算直接得到结果,简化表达式。

object ConstantFoldingFolding extends Rule[LogicalPlan] {
	def apply(plan: LogicalPlan): LogicalPlan = plan transform {
		case q: LogicalPlan => q transformExpressionsDown {
			case l: Literal => l
			case e if e.foldable => Literal.create(e.eval(EmptyRow), e.dataType)
		}
	}
}

Optimized LogicalPlan生成的第2步

图3 Optimized LogicalPlan生成的第2步

  在ConstantFolding规则中,如果LogicalPlan中的表达式可以折叠(foldable为true),那么会将EmptyRow作为参数传递到其eval方法中直接计算,然后根据计算结果构造Literal常量表达式。经过该规则优化后的逻辑算子树如图4所示。
Optimized LogicalPlan生成的第3步
图4 Optimized LogicalPlan生成的第3步

  可见,Filter过滤条件中的“cast(18,bigint)”表达式经过计算成为“Literal(18,bigint)”表达式,即输出的结果为18。在原来的Cast表达式中,其子节点Literal表达式的foldable值为true,因此Cast表达式本身的foldable值也为true,在匹配该优化规则时,Cast表达式会被直接计算。
  经过上述步骤,Spark SQL逻辑算子树生成、分析与优化的整个阶段都执行完毕。最终生成的逻辑算子树包含Relation节点、Filter节点和Project节点,同时每个节点中又包含了由对应表达式构成的树。

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

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

相关文章

小白也能看得懂的Jmeter性能测试中服务端资源监控技术

操作步骤: 1、安装插件管理器 插件管理器的作用:可以提供扩展插件的在线安装升级和卸载。因为我们需要在线安装监控插件,首先我们就要先安装插件管理器。 插件管理器的下载地址:https://jmeter-plugins.org/install/Install/ 如…

ESP32网络开发实例-远程Web串口监视器

远程Web串口监视器 文章目录 远程Web串口监视器1、应用介绍2、软件准备3、硬件准备4、代码实现在本文中,我们将构建一个 ESP32 网络服务器,用作远程串行监视器。 基于 Web 的串行监视器的工作方式与通常用于调试目的的 Arduino IDE 串行监视器的工作方式相同。 1、应用介绍 …

信息检索策略和技巧

指定检索策略并检索 确定检索词 检索课题:查找与“新型冠状病毒疫苗研制进展”有关的学术论文 检索式(2019-nCoV or 2019新型冠状病毒 or nCov-2019 or SARS-CoV-2 or COVID-19) and (疫苗 or 预防针 or 防疫针 or vaccin or vaccine) 扩展检索词的方式 同义词…

人工智能轨道交通行业周刊-第66期(2023.11.20-11.26)

本期关键词:智能铁鞋、TFDS、道岔密贴检查、Agent、Q*假说 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro轨道世界铁路那些事铁路…

JVM字节码文件的相关概述解读

Java全能学习面试指南:https://javaxiaobear.cn 1、字节码文件 从下面这个图就可以看出,字节码文件是可以跨平台使用的 想要让一个Java程序正确地运行在JVM中,Java源码就必须要被编译为符合JVM规范的字节码。 https://docs.oracle.com/java…

【Leetcode合集】1457. 二叉树中的伪回文路径

1457. 二叉树中的伪回文路径 1457. 二叉树中的伪回文路径 代码仓库地址: https://github.com/slience-me/Leetcode 个人博客 :https://slienceme.xyz 给你一棵二叉树,每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 「伪回文」的&am…

Nodejs 第二十章(fs 上)

概述 在 Node.js 中,fs 模块是文件系统模块(File System module)的缩写,它提供了与文件系统进行交互的各种功能。通过 fs 模块,你可以执行诸如读取文件、写入文件、更改文件权限、创建目录等操作,Node.js …

【腾讯云云上实验室】向量数据库相亲社交应用实践

快速入口 👉向量数据库_大模型知识库_向量数据存储_向量数据检索- 腾讯云 (tencent.com) 文章目录 前言1. 向量数据库概念及原理1.1 向量数据库概念1.2 向量数据库核心原理1.3 向量数据库优缺点1.4 向量数据库与传统数据库的区别 2. 腾讯云向量数据库的基本特性及优…

为什么,word文件在只读模式下,仍然能编辑?

Word文档设置了只读模式,是可以编辑的,但是当我们进行保存的时候就会发现,word提示需要重命名并选择新路径才能够保存。 这种操作,即使可以编辑文字,但是原文件是不会受到影响的,编辑之后的word文件会保存到…

竞赛选题 题目:基于卷积神经网络的手写字符识别 - 深度学习

文章目录 0 前言1 简介2 LeNet-5 模型的介绍2.1 结构解析2.2 C1层2.3 S2层S2层和C3层连接 2.4 F6与C5层 3 写数字识别算法模型的构建3.1 输入层设计3.2 激活函数的选取3.3 卷积层设计3.4 降采样层3.5 输出层设计 4 网络模型的总体结构5 部分实现代码6 在线手写识别7 最后 0 前言…

Spring 拾枝杂谈—Spring原生容器结构剖析(通俗易懂)

目录 一、前言 二、Spring快速入门 1.简介 : 2. 入门实例 : 三、Spring容器结构分析 1.bean配置信息的存储 : 2.bean对象的存储 : 3.bean-id的快捷访问 : 四、总结 一、前言 开门见山,11.25日开始我们正式进入Java框架—Spring的学习,此前&…

midjourney过时了?如何使用基于LCM的绘图技术画出你心中的画卷。

生成 AI 艺术在近年来迅速发展,吸引了数百万用户。然而,传统的生成 AI 艺术需要等待几秒钟或几分钟才能生成,这对于快节奏的现代社会来说并不理想。 近日,中国清华大学和 AI 代码共享平台 HuggingFace 联合开发了一项新的机器学习…

群晖NAS基础设置

群晖NAS基础设置 最近一直在玩群晖NAS系统,有一些基础的配置跟大家分享一下 开启ssh登录 1.开启方法 控制面板—>终端和SNMP—>终端机 2.使用ssh软件登录 这里我用SecureCRT登录 进入ssh 3.进入root用户 starstar-nas:~$ sudo su -l root Password: ro…

Python----函数的数据 拆包(元组和字典)

Python拆包: 就是把元组或字典中的数据单独的拆分出来,然后赋予给其他的变量。 拆包: 对于函数中的多个返回数据, 去掉 元组, 列表 或者字典 直接获取里面数据的过程。 元组的拆包过程 def func():# 经过一系列操作返回一个元组return 100, 200 …

【Qt绘制仪表盘】

目的 使用Qt的绘制事件绘制一个仪表盘 思路 需要创建一个带绘制事件的控件重写绘制事件显示 实现 以下是实现代码&#xff0c;可复制到程序到&#xff0c;直接运行。 .h // GaugeWidget.h #ifndef GAUGEWIDGET_H #define GAUGEWIDGET_H#include <QWidget>class Ga…

redis事务及管道

目录 redis事务 1、redis事务命令 2、示例 redis管道 1、管道命令 2、示例 redis事务 在Redis中&#xff0c;事务是一组命令的有序队列&#xff0c;可以一次执行多个命令&#xff0c;本质是一组命令的集合。一个事务中的所有命令都会序列化&#xff0c;按顺序地串行化执…

【JavaEE】多线程 (1)

目录 1. 认识线程&#xff08;Thread&#xff09; 1) 线程是什么 2) 为啥要有线程 3) 进程和线程的区别 2.第⼀个多线程程序 3.多线程的其他创建方式 方法二:实现 Runnable 接⼝ 方法三:匿名内部类 方法四:实现Runable, 重写run, 匿名内部类 方法五:使用lambda表达式…

Leetcode—35.搜索插入位置【简单】

2023每日刷题&#xff08;四十&#xff09; Leetcode—35.搜索插入位置 实现代码 int lower_bound(int* arr, int numsSize, int tar) {int left 0, right numsSize;int mid;// 左闭右开[left, right)while(left < right) {mid left (right - left) / 2;if(arr[mid] &…

4G执法记录仪在大型安保集团,保安集团、蓝天救援队中的 应用,行为规范化,人员定位,考勤打卡,应急指挥调度

【智能化升级】揭秘4G/5G执法记录仪在安保与救援领域如何重塑行业标准与效率 在快速发展的社会当中&#xff0c;大型安保集团、保安集团和蓝天救援队所肩负的任务日益繁重与复杂。无论是在平时的治安巡查、安保执勤&#xff0c;还是在突发公共事件的应急响应中&#xff0c;如何…

C++ STL-----容器

STL容器就是将运用最广泛的一些数据结构实现出来 常用的数据结构&#xff1a;数组, 链表,树, 栈, 队列, 集合, 映射表 等 这些容器分为序列式容器和关联式容器两种: 序列式容器:强调值的排序&#xff0c;序列式容器中的每个元素均有固定的位置。 关联式容器:二叉树结构&…