【Spark On Hive】—— 基于电商数据分析的项目实战

在这里插入图片描述
在这里插入图片描述

文章目录

    • Spark On Hive 详解
        • 一、项目配置
            • 1. 创建工程
            • 2. 配置文件
            • 3. 工程目录
        • 二、代码实现
            • 2.1 Class SparkFactory
            • 2.2 Object SparkFactory

Spark On Hive 详解

本文基于Spark重构基于Hive的电商数据分析的项目需求,在重构的同时对Spark On Hive的全流程进行详细的讲解。

一、项目配置
1. 创建工程

首先,创建一个空的Maven工程,在创建之后,我们需要检查一系列配置,以保证JDK版本的一致性。同时,我们需要创建出Scala的编码环境。具体可参考以下文章:
Maven工程配置与常见问题解决指南

Scala01 —— Scala基础

2. 配置文件

2.1 在Spark On Hive的项目中,我们需要有两个核心配置文件。

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.ybg</groupId><artifactId>warehouse_ebs_2</artifactId><version>1.0</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spark.version>3.1.2</spark.version><spark.scala.version>2.12</spark.scala.version><hadoop.version>3.1.3</hadoop.version><mysql.version>8.0.33</mysql.version><hive.version>3.1.2</hive.version><hbase.version>2.3.5</hbase.version><jackson.version>2.10.0</jackson.version></properties><dependencies><!-- spark-core --><dependency><groupId>org.apache.spark</groupId><artifactId>spark-core_${spark.scala.version}</artifactId><version>${spark.version}</version></dependency><!-- spark-sql --><dependency><groupId>org.apache.spark</groupId><artifactId>spark-sql_${spark.scala.version}</artifactId><version>${spark.version}</version></dependency><!-- spark-hive --><dependency><groupId>org.apache.spark</groupId><artifactId>spark-hive_${spark.scala.version}</artifactId><version>${spark.version}</version></dependency><!-- hadoop-common --><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-common</artifactId><version>${hadoop.version}</version></dependency><!-- mysql --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>${mysql.version}</version></dependency><!-- hive-exec --><dependency><groupId>org.apache.hive</groupId><artifactId>hive-exec</artifactId><version>${hive.version}</version><exclusions><exclusion><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId></exclusion></exclusions></dependency><!-- HBase 驱动 --><dependency><groupId>org.apache.hbase</groupId><artifactId>hbase-client</artifactId><version>${hbase.version}</version></dependency><!-- jackson-databind --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>${jackson.version}</version></dependency><!-- jackson-databind --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${jackson.version}</version></dependency></dependencies></project>
  • log4j.properties
    log4j.properties 文件的主要作用是配置日志系统的行为,包括控制日志信息的输出和实现滚动事件日志
log4j.rootLogger=ERROR, stdout, logfile
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
----------------------- 滚动事件日志代码 -----------------------
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.DatePattern='.'yyyy-MM-dd
log4j.appender.logfile.append=true
---------------------------------------------------------------
log4j.appender.logfile.File=log/spark_first.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

2.2 组件核心配置文件
在这里插入图片描述
在工程的resources目录下,需要存放在虚拟机中大数据服务的核心组件的配置文件,以便于Spark On Hive中调用大数据组件服务能够正常进行。

3. 工程目录
二、代码实现
  • 创建数据校验方法 check:
    用于确保配置项的值有效。
    检查值是否为 null。
    对字符串类型的值进行非空和正则表达式匹配校验。

  • 创建配置设置方法 set:
    先校验配置项名称和值的有效性。
    使用 SparkConf.set 方法设置有效的配置项和值。

  • 单例对象 SparkFactory:
    提供基础配置方法,如设置应用名称、主节点等。
    提供 baseConfig 方法集中进行基础配置。
    提供 end 方法返回配置好的 SparkSession 实例。

  • 在 SparkFactory 类中实现上述方法:
    定义 build 方法,返回包含 check 和 set 方法的 Builder 对象。
    在 Builder 对象中实现各种配置方法,每个方法都调用 set 方法。
    使用 SparkSession.builder() 方法在 end 方法中创建并返回 SparkSession 实例。

SparkFactory配置表如下:
配置表

2.1 Class SparkFactory
  • 作用:SparkFactory类的作用是能够工厂化地创建和配置SparkSession实例,通过一系列的setcheck方法来确保配置项的有效性和正确性,并最终生成一个配置好的SparkSession实例。
  • 注意:我们需要在Spark官网配置页获取所有配置项的标准名称。
  • 代码
class SparkFactory {def build():Builder={new Builder {val conf = new SparkConf()/*** 数据校验* @param title 校验主题* @param value 待校验的值* @param regex 若待校验值为字符串,且有特定的规则,那么提供正则表达式进一步验证格式*/private def check(title:String,value:Any,regex:String=null)={if (null == value) {throw new RuntimeException()(s"value for $title null pointer exception")}if(value.isInstanceOf[String]){if(value.toString.isEmpty){throw new RuntimeException(s"value for $title empty string exception")}if(regex!=null){if(!value.toString.matches(regex)){throw new RuntimeException(s"$title is not match regex $regex")}}}}/*** 先检查配置项名称是否正确* 再检查配置项的值是否正确* @param item 配置项名称* @param value 配置项值* @param regexValue 配置项正则规则*/private def set(item:String,value:String,regexValue:String=null)={check("name_of_config_item",item,"^spark\\..*")check(item,value,regexValue)conf.set(item,value)}// Baseprivate def setBaseAppName(appName:String)={set("spark.app.name",appName,"^\\w+$")}private def setBaseMaster(master:String)={set("spark.master",master,"yarn|spark://([a-z]\\w+|\\d{1,3}(\\.\\d{1,3}){3}):\\d{4,5}|local(\\[\\*|[1-9][0-9]*])")}private def setBaseDeployMode(deployMode:String)={set("spark.submit.deployMode",deployMode,"client|cluster")}private def setBaseEventLogEnabled(eventLogEnabled:Boolean)={set("spark.eventLog.enabled",s"$eventLogEnabled")}override def baseConfig(appName: String, master: String = "local[*]", deployMode: String = "client", eventLogEnabled: Boolean = false): Builder = {setBaseAppName(appName)setBaseMaster(master)setBaseDeployMode(deployMode)setBaseEventLogEnabled(eventLogEnabled)this}// Driverprivate def setDriverMemory(memoryGB:Int)={set("spark.driver.memory",s"${memoryGB}g","[1-9]\\d*")}private def setDriverCoreNum(coreNum: Int) = {set("spark.driver.cores", s"${coreNum}g", "[1-9]\\d*")}private def setDriverMaxResultGB(maxRstGB:Int)={set("spark.driver.maxResultSize",s"${maxRstGB}g","[1-9]\\d*")}private def setDriverHost(driverHost:String)={set("spark.submit.deployMode",driverHost,"localhost|[a-z]\\w+")}override def optimizeDriver(memoryGB: Int = 2, coreNum: Int = 1, maxRstGB: Int = 1, driverHost: String = "localhost"): Builder = {setDriverCoreNum(coreNum)setDriverMemory(memoryGB)/*** 每一个Spark行动算子触发的所有分区序列化结果大小上限*/setDriverMaxResultGB(maxRstGB)/*** Standalone 模式需要设置 DriverHost,便于 executor 与 master 通信*/if (conf.get("spark.master").startsWith("spark://")) {setDriverHost(driverHost)}setDriverHost(driverHost)this}// Executorprivate def setExecutorMemory(memoryGB: Int) = {set("spark.executor.memory", s"${memoryGB}g", "[1-9]\\d*")}private def setExecutorCoreNum(coreNum: Int) = {set("spark.executor.cores", s"$coreNum", "[1-9]\\d*")}override def optimizeExecutor(memoryGB:Int=1,coreNum:Int=1):Builder={setExecutorMemory(memoryGB)/*** Yarn模式下只能由1个核* 其他模式下,核数为所有可用的核*/if(!conf.get("spark.master").equals("yarn")){setExecutorCoreNum(coreNum)}this}// Limitprivate def setLimitMaxCores(maxCores:Int)={set("spark.cores.max",s"$maxCores","[1-9]\\d*")}private def setLimitMaxTaskFailure(maxTaskFailure:Int)={set("spark.task.maxFailures",s"$maxTaskFailure","[1-9]\\d*")}private def setLimitMaxLocalWaitS(maxLocalWaitS:Int)={set("spark.locality.wait",s"${maxLocalWaitS}s","[1-9]\\d*")}override def optimizeLimit(maxCores:Int=4,maxTaskFailure:Int=3,maxLocalWaitS:Int=3):Builder={if (conf.get("spark.master").startsWith("spark://")) {setLimitMaxCores(maxCores)}/*** 单个任务允许失败最大次数,超出会杀死本次任务*/setLimitMaxTaskFailure(maxTaskFailure)/*** 数据本地化读取加载的最大等待时间* 大任务:建议适当增加此值*/setLimitMaxLocalWaitS(maxLocalWaitS)this}// Serializeroverride def optimizeSerializer(serde:String="org.apache.spark.serializer.JavaSerializer",clas:Array[Class[_]]=null):Builder={/*** 设置将需要通过网络发送或快速缓存的对象序列化工具类* 默认为JavaSerializer* 为了提速,推荐设置为KryoSerializer* 若采用 KryoSerializer,需要将所有自定义的实体类(样例类)注册到配置中心*/set("spark.serializer",serde,"([a-z]+\\.)+[A-Z]\\w*")if(serde.equals("org.apache.spark.serializer.KryoSerializer")){conf.registerKryoClasses(clas)}this}// Netprivate def setNetTimeout(netTimeoutS:Int)={set("spark.cores.max",s"${netTimeoutS}s","[1-9]\\d*")}private def setNetSchedulerMode(schedulerMode:String)={set("spark.scheduler.mode",schedulerMode,"FAIR|FIFO")}override def optimizeNetAbout(netTimeOusS:Int=120,schedulerMode:String="FAIR"):Builder={/*** 所有和网络交互相关的超时阈值*/setNetTimeout(netTimeOusS)/*** 多人工作模式下,建议设置为FAIR*/setNetSchedulerMode(schedulerMode)this}// Dynamicprivate def setDynamicEnabled(dynamicEnabled:Boolean)={set("spark.dynamicAllocation.enabled",s"$dynamicEnabled")}private def setDynamicInitialExecutors(initialExecutors:Int)={set("spark.dynamicAllocation.initialExecutors",s"$initialExecutors","[1-9]\\d*")}private def setDynamicMinExecutors(minExecutors:Int)={set("spark.dynamicAllocation.minExecutors",s"$minExecutors","[1-9]\\d*")}private def setDynamicMaxExecutors(maxExecutors:Int)={set("spark.dynamicAllocation.maxExecutors",s"$maxExecutors","[1-9]\\d*")}override def optimizeDynamicAllocation(dynamicEnabled:Boolean=false,initialExecutors:Int=3,minExecutors:Int=0,maxExecutors:Int=6):Builder={/*** 根据应用的工作需求,动态分配executor*/setDynamicEnabled(dynamicEnabled)if(dynamicEnabled){setDynamicInitialExecutors(initialExecutors)setDynamicMinExecutors(minExecutors)setDynamicMaxExecutors(maxExecutors)}this}override def optimizeShuffle(parallelism:Int=3,shuffleCompressEnabled:Boolean=false,maxSizeMB:Int=128,shuffleServiceEnabled:Boolean=true):Builder={null}override def optimizeSpeculation(enabled:Boolean=false,interval:Int=15,quantile:Float=0.75F):Builder={null}override def warehouseDir(hdfs:String):Builder={null}override def end():SparkSession={SparkSession.builder().getOrCreate()}}}
}
2.2 Object SparkFactory
object SparkFactory {trait Builder{// 默认值能给就给/*** 基本配置* @param appName* @param master 默认是本地方式* @param deployMode 默认是集群模式* @param eventLogEnabled 生产环境打开,测试环境关闭* @return*/def baseConfig(appName:String,master:String="local[*]",deployMode:String="client",eventLogEnabled:Boolean=false):Builder/*** 驱动端优化配置* @param memoryGB 驱动程序的内存大小* @param coreNum 驱动程序的核数* @param maxRstGB 驱动程序的最大结果大小* @param driverHost 驱动程序的主机地址:驱动程序会在主机地址上运行,并且集群中的其他节点会通过这个地址与驱动程序通信* @return*/def optimizeDriver(memoryGB:Int=2,coreNum:Int=1,maxRstGB:Int=1,driverHost:String="localhost"):Builderdef optimizeExecutor(memoryGB:Int=1,coreNum:Int=1):Builder/*** 整体限制配置* @param maxCores 整体可用的最大核数* @param maxTaskFailure 单个任务失败的最大次数* @param maxLocalWaitS 容错机制:数据读取阶段允许等待的最长时间,超过时间切换到其他副本。* @return*/def optimizeLimit(maxCores:Int=4,maxTaskFailure:Int,maxLocalWaitS:Int=30):Builder/*** 默认使用:Java序列化* 推荐使用:Kryo序列化 提速或对速度又要i去* 所有的自定义类型都要注册到Spark中,才能完成序列化。* @param serde 全包路径* @param classes 自定义类型,默认认为不需要指定,Class[_]表示类型未知。* @return Builder*/def optimizeSerializer(serde:String="org.apache.spark.serializer.JavaSerializer",clas:Array[Class[_]]=null):Builder/*** 在Spark的官方配置中,netTimeOutS可能被很多超时的数据调用。* @param netTimeOusS 判定网络超时的时间* @param schedulerMode 可能很多任务一起跑,因此公平调度* @return*/def optimizeNetAbout(netTimeOusS:Int=180,schedulerMode:String="FAIR"):Builder/*** 动态分配->按需分配* 类似于配置线程池中的最大闲置线程数,根据需要去做动态分配* @param dynamicEnabled 是否开启动态分配* @param initialExecutors 初始启用的Executors的数量* @param minExecutors 最小启用的Executors的数量* @param maxExecutors 最大启用的Executors的数量* @return*/def optimizeDynamicAllocation(dynamicEnabled:Boolean=false,initialExecutors:Int=3,minExecutors:Int=0,maxExecutors:Int=6):Builder/*** 特指在没有指定分区数时,对分区数的配置。* 并行度和初始启用的Executors的数量一致,避免额外开销。** @param parallelism* @param shuffleCompressEnabled* @param maxSizeMB* @param shuffleServiceEnabled* @return*/def optimizeShuffle(parallelism:Int=3,shuffleCompressEnabled:Boolean=false,maxSizeMB:Int=128,shuffleServiceEnabled:Boolean=true):Builder/*** 推测执行,将运行时间长的任务,放到队列中,等待运行时间短的任务运行完成后,再运行。* @param enabled* @param interval Spark检查任务执行时间的时间间隔,单位是秒。* @param quantile 如果某个任务的执行时间超过指定分位数(如75%的任务执行时间),则认为该任务执行时间过长,需要启动推测执行。*/def optimizeSpeculation(enabled:Boolean=false,interval:Int=15,quantile:Float=0.75F):Builderdef warehouseDir(hdfs:String):Builderdef end():SparkSession}
}

在这里插入图片描述

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

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

相关文章

【人工智能】机器学习 -- 决策树(乳腺肿瘤数)

目录 一、使用Python开发工具&#xff0c;运行对iris数据进行分类的例子程序dtree.py&#xff0c;熟悉sklearn机器实习开源库。 二、登录https://archive-beta.ics.uci.edu/ 三、使用sklearn机器学习开源库&#xff0c;使用决策树对breast-cancer-wisconsin.data进行分类。 …

Linux 注意事项

Linux 与 Windows 是两个相互独立的操作系统&#xff0c;两者有较大差距&#xff1a; 1.1 Linux 严格区分大小写&#xff08;Windows不严格区分大小写&#xff09;&#xff1b; 1.2 Linux 中所有内容&#xff0c;硬件设备都以文件形式保存在 /dev 目录下&#xff08;万物皆文件…

攻防世界 re新手模式

Reversing-x64Elf-100 64位ida打开 看if语句&#xff0c;根据i的不同&#xff0c;选择不同的数组&#xff0c;后面的2*i/3选择数组中的某一个元素&#xff0c;我们输入的是a1 直接逆向得到就行 二维字符数组写法&#xff1a;前一个是代表有几个字符串&#xff0c;后一个是每…

Logback日志异步打印接入指南,输出自定义业务数据

背景 随着应用的请求量上升&#xff0c;日志输出量也会成线性比例的上升&#xff0c;给磁盘IO带来压力与性能瓶颈。应用也遇到了线程池满&#xff0c;是因为大量线程卡在输出日志。为了缓解日志同步打印&#xff0c;会采取异步打印日志。这样会引起日志中的追踪id丢失&#xf…

鸿蒙OS开发工具 DevEco Studio(4.0)安装教程

1.旧版本下载地址【HarmonyOS】HUAWEI DevEco Studio 下载地址汇总_deveco studio历史版本-CSDN博客 2.解压安装包&#xff0c;双击安装程序 3.打开后点击Next 4.点击“Browse...”选择路径&#xff0c;然后点击“Next” 5.勾选&#xff0c;点击“Next” 6.默认&#xff0c;直…

NASA数据集——宝瓶座天体微波发射图辅助数据集 V1.0

Aquarius Celestial Sky Microwave Emission Map Ancillary Dataset V1.0 宝瓶座天体微波发射图辅助数据集 V1.0 简介 本数据集包含三张 L 波段&#xff08;波长 21 厘米&#xff09;天体&#xff08;"银河系"&#xff09;亮度温度图&#xff0c;用于处理美国航天…

maven 私服搭建(tar+docker)

maven私服搭建 一、linux安装nexus1、工具下载 二、 docker 搭建nexus1、镜像下载创建目录2、运行nexus3、访问确认&#xff0c;修改默认密码&#xff0c;禁用匿名用户登录4、创建仓库5、创建hostd仓库6、创建Blob Stores7、创建docker私服1、创建proxy仓库2、创建hotsed本地仓…

netcat 使用

GPT-4o (OpenAI) Netcat (通常缩写为nc) 是一个功能强大的网络工具&#xff0c;可以方便地读写网络连接。它被广泛用于漏洞测试、网络调试和数据传输。Netcat 可以作为客户端&#xff0c;也可以作为服务器使用。 以下是一些常见的 Netcat 用法&#xff1a;基础用法 连接到服务…

ISP代理和双ISP代理:区别和优势

随着互联网技术的不断发展和普及&#xff0c;网络代理服务成为众多用户保护隐私、提高网络性能、增强安全性的重要工具。其中&#xff0c;ISP代理和双ISP代理是两种常见的网络代理服务形式。本文将详细探讨ISP代理和双ISP代理的区别和优势&#xff0c;以便用户更好地了解并选择…

C/C++ json库

文章目录 一、介绍1.1 json 介绍 二、C/C json 库选型2.1 选型范围2.2 jsoncpp2.2.2 jsoncpp 编译和交叉编译 2.3 rapidjson2.4 nlohmann/json2.5 sonic-cpp 五、常见问题5.1 jsoncpp 中关于浮点数的控制和中文显示问题5.2 jsoncpp序列化double类型时精度损失问题的解决办法 一…

算法学习笔记(Hello算法)—— 初识算法

1、相关链接 Hello算法&#xff1a;Hello 算法 (hello-algo.com) 2、算法是什么 2.1 算法定义 算法是一系列明确、有限且有效的步骤或指令的集合&#xff0c;用于解决特定问题或执行特定任务。 算法具有以下基本特征&#xff1a; 输入&#xff1a;算法至少有一个输入&…

【JavaScript 算法】图的遍历:理解图的结构

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、深度优先搜索&#xff08;DFS&#xff09;深度优先搜索的步骤深度优先搜索的JavaScript实现 二、广度优先搜索&#xff08;BFS&#xff09;广度优先搜索的步骤 三、应用场景四、总结 图的遍历是图论中的基本操作之一&am…

院内影像一体化平台PACS源码,C#语言的PACS/RIS系统,二级医院应用案例

全院级PACS系统源码&#xff0c;一体化应用系统整合&#xff0c;满足放射、超声、内窥镜中心、病理、检验等多个科室的工作流程和需求&#xff0c;为不同科室提供专业的解决方案&#xff0c;实现了全院乃至区域内信息互联互通、数据统一存储与管理等功能&#xff0c;做到以病人…

浪漫情怀:红酒中的诗意与情感

在生活的点滴细节中&#xff0c;总有些元素能触动我们内心较柔软的地方&#xff0c;唤起那份深深的浪漫情怀。而红酒&#xff0c;便是这其中的一种神奇媒介。它以其不同的色泽、香气和口感&#xff0c;让人沉醉其中&#xff0c;感受那份诗意与情感的交织。今天&#xff0c;就让…

C语言 | Leetcode C语言题解之第237题删除链表中的节点

题目&#xff1a; 题解&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/void deleteNode(struct ListNode* node) {struct ListNode * p node->next;int temp;temp node->val;node->val…

常见排序算法总结

文章目录 比较排序冒泡排序选择排序插入排序归并排序快速排序堆排序希尔排序 非比较排序&#xff08;桶排序&#xff09;计数排序基数排序 比较排序 冒泡排序 嵌套循环&#xff0c;每次内层循环执行时&#xff0c;数组的每两个元素交换&#xff0c;将一个最大/小的数排到数组…

AIGC代码学习记录

本文主要记录不同的SD模型代码中实现的一些细节&#xff0c;如text2img,img2img,inpaint等等 1. 文生图 第一步&#xff1a;生成随机的latent feature (n,4,64,64)&#xff1b;n为生成的图片个数&#xff1b; 第二步&#xff1a;对于prompt用clip生成特征&#xff0c;正向提示…

PDF-Extract-Kit (PDF内容抽取开源项目)

Github 地址&#xff1a;https://github.com/opendatalab/PDF-Extract-Kit 整体介绍 PDF文档中包含大量知识信息&#xff0c;例如文本、表格、图像、公式等。此外&#xff0c;PDF的文档布局也相当复杂&#xff0c;页眉、页脚、表格标题、图片标题等等&#xff0c;提取高质量的…

python调用chrome浏览器自动化如何选择元素

功能描述&#xff1a;在对话框输入文字&#xff0c;并发送。 注意&#xff1a; # 定位到多行文本输入框并输入内容。在selenium 4版本中&#xff0c;元素定位需要填写父元素和子元素名。 textarea driver.find_element(By.CSS_SELECTOR,textarea.el-textarea__inner) from …

2024.04最新 鹦鹉优化算法 (PO) 开源MATLAB代码

!!!只需要代码的直接跳转到最后一节&#xff0c;有开源代码 1.算法简介2.灵感来源与核心思想2.1 灵感来源2.2 核心思想 3.主要步骤3.1 种群初始化3.2 觅食行为3.3 停留行为3.4 交流行为3.5 对陌生人的恐惧行为 4.伪代码与核心代码4.1流程图与伪代码流程图伪代码 4.2 核心代码(P…