当前位置: 首页 > news >正文

Tomcat源码解析——Tomcat的启动流程

一、启动脚本

        当我们在服务启动Tomcat时,都是通过执行startup.sh脚本启动。

        

        在Tomcat的启动脚本startup.sh中,最终会去执行catalina.sh脚本,传递的参数是start。

        

        在catalina.sh脚本中,前面是环境判断和初始化参数,最终根据传递的start来执行上图的代码,最终会调用Tomcat的Bootstrap启动类的main方法,传递的参数是start。

二、源码解析

        为了更容易的理解源码,整个系列中Tomcat的运行模式采用BIO的方式。

    public static void main(String args[]) {if (daemon == null) {Bootstrap bootstrap = new Bootstrap();try {//初始化bootstrap.init();} catch (Throwable t) {handleThrowable(t);t.printStackTrace();return;}daemon = bootstrap;} else {Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);}try {String command = "start";if (args.length > 0) {command = args[args.length - 1];}//脚本传递的是startif (command.equals("startd")) {args[args.length - 1] = "start";daemon.load(args);daemon.start();} else if (command.equals("stopd")) {args[args.length - 1] = "stop";daemon.stop();} else if (command.equals("start")) {//加载和启动整个Tomcatdaemon.setAwait(true);daemon.load(args);daemon.start();} else if (command.equals("stop")) {daemon.stopServer(args);} else if (command.equals("configtest")) {daemon.load(args);if (null==daemon.getServer()) {System.exit(1);}System.exit(0);} else {log.warn("Bootstrap: command \"" + command + "\" does not exist.");}} catch (Throwable t) {if (t instanceof InvocationTargetException &&t.getCause() != null) {t = t.getCause();}handleThrowable(t);t.printStackTrace();System.exit(1);}}

        在启动类的main方法中,创建了一个启动类的实例,然后进行初始化,最后再根据启动命令传递进来的参数,也就是上文中的 start 来执行对应的逻辑,即加载和启动整个Tomcat。

    public void init() throws Exception {//设置容器的路径和脚本中的基本信息setCatalinaHome();setCatalinaBase();//初始化Tomcat的三大类加载器initClassLoaders();//该类加载器用于加载这个源码的类Thread.currentThread().setContextClassLoader(catalinaLoader);SecurityClassLoad.securityClassLoad(catalinaLoader);//创建一个容器的启动实例,待会用于加载server.xmlClass<?> startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");Object startupInstance = startupClass.newInstance();String methodName = "setParentClassLoader";Class<?> paramTypes[] = new Class[1];paramTypes[0] = Class.forName("java.lang.ClassLoader");Object paramValues[] = new Object[1];paramValues[0] = sharedLoader;Method method =startupInstance.getClass().getMethod(methodName, paramTypes);method.invoke(startupInstance, paramValues);catalinaDaemon = startupInstance;}

        在初始化启动器是,主要是创建三大类加载器(commonLoader、catalinaLoader、sharedLoader)并设置好父子关系,最后再创建一个启动的实例,主要是用来解析server.xml文件。

        在Tomcat中一共有四大类加载器,如下图:

类加载器作用父加载器

commonLoader(共同类加载器)

加载$CATALINA_HOME/lib下的类加载器应用类加载器

catalinaLoader(容器类加载器)

加载Tomcat应用服务器的类加载器,可以理解为加载Tomcat源码中的类共同类加载器

sharedLoader(共享类加载器)

加载应用类加载器的共享的类加载器,例如相同版本的mysql驱动等共同类加载器
webappLoader(应用类加载)加载web应用下的类类加载,每个web应用之间是相互隔离的共享类加载器

        commonLoader、catalinaLoader、sharedLoader可以在tomcat下的conf/catalina.properties文件中修改。

        初始化完成之后,会根据启动参数 start,然后执行daemon.load(args),即加载。

    private void load(String[] arguments)throws Exception {String methodName = "load";Object param[];Class<?> paramTypes[];if (arguments==null || arguments.length==0) {paramTypes = null;param = null;} else {paramTypes = new Class[1];paramTypes[0] = arguments.getClass();param = new Object[1];param[0] = arguments;}Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes);method.invoke(catalinaDaemon, param);}

        加载就是通过反射去调用Catalina的load方法。

    public void load() {//找到脚本中设置的路径initDirs();initNaming();//创建一个解析器Digester digester = createStartDigester();InputSource inputSource = null;InputStream inputStream = null;File file = null;try {try {//读取server.xml文件file = configFile();inputStream = new FileInputStream(file);inputSource = new InputSource(file.toURI().toURL().toString());} catch (Exception e) {if (log.isDebugEnabled()) {log.debug(sm.getString("catalina.configFail", file), e);}}if (inputStream == null) {try {inputStream = getClass().getClassLoader().getResourceAsStream(getConfigFile());inputSource = new InputSource(getClass().getClassLoader().getResource(getConfigFile()).toString());} catch (Exception e) {if (log.isDebugEnabled()) {log.debug(sm.getString("catalina.configFail",getConfigFile()), e);}}}if (inputStream == null) {try {inputStream = getClass().getClassLoader().getResourceAsStream("server-embed.xml");inputSource = new InputSource(getClass().getClassLoader().getResource("server-embed.xml").toString());} catch (Exception e) {if (log.isDebugEnabled()) {log.debug(sm.getString("catalina.configFail","server-embed.xml"), e);}}}if (inputStream == null || inputSource == null) {if (file == null) {log.warn(sm.getString("catalina.configFail",getConfigFile() + "] or [server-embed.xml]"));} else {log.warn(sm.getString("catalina.configFail",file.getAbsolutePath()));if (file.exists() && !file.canRead()) {log.warn("Permissions incorrect, read permission is not allowed on the file.");}}return;}try {//解析server.xml文件,把server.xml中的每一个标签都转换成对应的实例对象inputSource.setByteStream(inputStream);digester.push(this);digester.parse(inputSource);} catch (SAXParseException spe) {log.warn("Catalina.start using " + getConfigFile() + ": " +spe.getMessage());return;} catch (Exception e) {log.warn("Catalina.start using " + getConfigFile() + ": ", e);return;}} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {// Ignore}}}getServer().setCatalina(this);initStreams();try {//初始化最顶层的组件,会导致Service组件和Connector组件也跟着初始化getServer().init();} catch (LifecycleException e) {if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {throw new java.lang.Error(e);} else {log.error("Catalina.start", e);}}}

        在Catalina的load方法中,会找到server.xml文件,然后解析标签并创建出对应的实例对象,最终在调用最顶层的Server组件的init方法,会调用Service组件的初始化,而Service组件的会调用Connector组件的初始化(容器的初始化是懒加载的,即有请求达到时才开始初始化)。

        在上面3个组件的初始化中最值得关注的是Connector组件的初始化,因为它会绑定一个端口并且添加对应的协议处理器,从而等待请求。

Connector:protected void initInternal() throws LifecycleException {super.initInternal();//创建一个适配器adapter = new CoyoteAdapter(this);//协议处理器在连接器创建时也跟着创建了protocolHandler.setAdapter(adapter);if( null == parseBodyMethodsSet ) {setParseBodyMethods(getParseBodyMethods());}//...省略try {//协议处理器初始化protocolHandler.init();} catch (Exception e) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);}mapperListener.init();}Http11Protocol:public void init() throws Exception {//...省略try {endpoint.init();} catch (Exception ex) {getLog().error(sm.getString("abstractProtocolHandler.initError",getName()), ex);throw ex;}}JIoEndpoint:public final void init() throws Exception {testServerCipherSuitesOrderSupport();if (bindOnInit) {bind();bindState = BindState.BOUND_ON_INIT;}}public void bind() throws Exception {if (acceptorThreadCount == 0) {acceptorThreadCount = 1;}if (getMaxConnections() == 0) {setMaxConnections(getMaxThreadsExecutor(true));}if (serverSocketFactory == null) {if (isSSLEnabled()) {serverSocketFactory =handler.getSslImplementation().getServerSocketFactory(this);} else {serverSocketFactory = new DefaultServerSocketFactory(this);}}//创建ServerSocket绑定监听端口if (serverSocket == null) {try {if (getAddress() == null) {serverSocket = serverSocketFactory.createSocket(getPort(),getBacklog());} else {serverSocket = serverSocketFactory.createSocket(getPort(),getBacklog(), getAddress());}} catch (BindException orig) {String msg;if (getAddress() == null)msg = orig.getMessage() + " <null>:" + getPort();elsemsg = orig.getMessage() + " " +getAddress().toString() + ":" + getPort();BindException be = new BindException(msg);be.initCause(orig);throw be;}}}

        在连接器的初始化方法中,会调用协议处理器的初始化方法,协议处理器会调用Endpoint的初始化方法,最终在Endpoint中完成了ServerSocket的创建,并且绑定了端口(此时还不能接受处理HTTP请求)

        当Catalina的load方法调用完成后,除了懒加载的容器组件还未创建,其它组件都已经创建出来了,下一步就是启动这些组件(上面只是初始化,组件还没开始工作)。

    public void start()throws Exception {if( catalinaDaemon==null ) init();Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);method.invoke(catalinaDaemon, (Object [])null);}

        跟调用load方法一样,也是通过反射去调用Catalina的start方法。

    public void start() {//...省略try {//调用Server的启动方法getServer().start();} catch (LifecycleException e) {log.fatal(sm.getString("catalina.serverStartFail"), e);try {getServer().destroy();} catch (LifecycleException e1) {log.debug("destroy() failed for failed Server ", e1);}return;}//...省略}

        通过调用最顶层Server组件的start方法,会导致Service组件的start被调用,而Service组件的会调用Connector组件的start方法。

        此处值得关注的还是Connector的start方法,在上文中,ServerSocket已经被创建并且绑定了端口,但是还没有去执行接受连接的方法。  

Connector:protected void startInternal() throws LifecycleException {//...省略try {//调用协议处理器的startprotocolHandler.start();} catch (Exception e) {//...省略}mapperListener.start();}Http11Protocolpublic void start() throws Exception {try {endpoint.start();} catch (Exception ex) {}}JIoEndpointpublic final void start() throws Exception {if (bindState == BindState.UNBOUND) {bind();bindState = BindState.BOUND_ON_START;}startInternal();}public void startInternal() throws Exception {if (!running) {running = true;paused = false;//创建一个线程池if (getExecutor() == null) {createExecutor();}initializeConnectionLatch();//创建连接接收器并放入线程池中接受连接startAcceptorThreads();//创建一个超时处理线程Thread timeoutThread = new Thread(new AsyncTimeout(),getName() + "-AsyncTimeout");timeoutThread.setPriority(threadPriority);timeoutThread.setDaemon(true);timeoutThread.start();}}protected final void startAcceptorThreads() {int count = getAcceptorThreadCount();//创建连接接收器acceptors = new Acceptor[count];for (int i = 0; i < count; i++) {acceptors[i] = createAcceptor();String threadName = getName() + "-Acceptor-" + i;acceptors[i].setThreadName(threadName);//一个连接接收器对应一个线程Thread t = new Thread(acceptors[i], threadName);t.setPriority(getAcceptorThreadPriority());t.setDaemon(getDaemon());t.start();}}Acceptor:        public void run() {//...省略try {countUpOrAwaitConnection();Socket socket = null;try {//此处调用ServerSocket的阻塞接受连接方法socket = serverSocketFactory.acceptSocket(serverSocket);} catch (IOException ioe) {countDownConnection();// Introduce delay if necessaryerrorDelay = handleExceptionWithDelay(errorDelay);// re-throwthrow ioe;}//...省略}

          连接器start中调用协议处理器的start,协议处理器最终调用Endpoint的start,最终在Endpoint中创建了Acceptor(接收器),并且将Acceptor放入一个线程中异步处理(因为BIO的socket会阻塞),此刻整个tomcat的启动流程大致完成,Tomcat启动完成之后,就是在Acceptor中接受请求并处理了。

        Tomcat启动时序图:

        

相关文章:

Tomcat源码解析——Tomcat的启动流程

一、启动脚本 当我们在服务启动Tomcat时&#xff0c;都是通过执行startup.sh脚本启动。 在Tomcat的启动脚本startup.sh中&#xff0c;最终会去执行catalina.sh脚本&#xff0c;传递的参数是start。 在catalina.sh脚本中&#xff0c;前面是环境判断和初始化参数&#xff0c;最终…...

蓝桥杯真题演练:2023B组c/c++

日期统计 小蓝现在有一个长度为 100 的数组&#xff0c;数组中的每个元素的值都在 0 到 9 的范围之内。 数组中的元素从左至右如下所示&#xff1a; 5 6 8 6 9 1 6 1 2 4 9 1 9 8 2 3 6 4 7 7 5 9 5 0 3 8 7 5 8 1 5 8 6 1 8 3 0 3 7 9 2 7 0 5 8 8 5 7 0 9 9 1 9 4 4 6 8 6 3 …...

微信小程序实现预约生成二维码

业务需求&#xff1a;点击预约按钮即可生成二维码凭码入校参观~ 一.创建页面 如下是博主自己写的wxml&#xff1a; <swiper indicator-dots indicator-color"white" indicator-active-color"blue" autoplay interval"2000" circular > &…...

专业140+总分410+北京理工大学826信号处理导论考研经验北理工电子信息通信工程,真题,参考书,大纲。

今年考研专业课826信号处理导论&#xff08;信号系统和数字信号处理&#xff09;140&#xff0c;总分410&#xff0c;顺利上岸&#xff01;回看去年将近一年的复习&#xff0c;还是记忆犹新&#xff0c;有不少经历想和大家分享&#xff0c;有得有失&#xff0c;希望可以对大家复…...

做一个后台项目的架构

后台架构的11个维度 架构1&#xff1a;团队协助基础工具链的选型和培训架构2&#xff1a;搭建微服务开发基础设施架构3&#xff1a;选择合适的RPC框架架构4&#xff1a;选择和搭建高可用的注册中心架构5&#xff1a;选择和搭建高可用的配置中心架构6&#xff1a;选择和搭建高性…...

嵌入式单片机 TTL电平、232电平、485电平的区别和联系

一、简介 TTL、232和485是常见的串口通信标准&#xff0c;它们在电平和通信方式上有所不同&#xff0c; ①一般情况下TTL电平应用于单片机外设&#xff0c;属于MCU/CPU等片外外设&#xff1b; ②232/485电平应用于产品整体对外的接口&#xff0c;一般是片外TTL串口转232/485…...

2024年大唐杯备考

努力更新中…… 第一章 网络架构和组网部署 1.1 5G的网络整体架构 5G网络中的中传、回传、前传&#xff08;这里属于承载网的概念&#xff09; CU和DU之间是中传 BBU和5GC之间是回传 BBU和AAU之间是前传&#xff08;这个好记&#xff09; 这里竟然还藏了MEC&#xff08;…...

Spring Boot(06):Spring Boot与MySQL搭配,打造极简高效的数据管理系统

1. 前言 Spring Boot 是一个基于Spring框架的快速开发框架&#xff0c;可以使开发者快速搭建一个可靠的Java Web应用程序。而MySQL是最广泛使用的关系型数据库系统之一&#xff0c;也是Spring Boot整合数据库的首选。本文将介绍Spring Boot如何整合MySQL数据库。 2. 摘要 本文…...

Vue3 + Vite 构建组件库发布到 npm

你有构建完组件库后&#xff0c;因为不知道如何发布到 npm 的烦恼吗&#xff1f;本教程手把手教你用 Vite 构建组件库发布到 npm 搭建项目 这里我们使用 Vite 初始化项目&#xff0c;执行命令&#xff1a; pnpm create vite my-vue-app --template vue这里以我的项目 vue3-xm…...

Vite多环境配置与打包:灵活高效的Vue开发工作流

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…...

从零实现诗词GPT大模型:数据集介绍和预处理

专栏规划: https://qibin.blog.csdn.net/article/details/137728228 本章将介绍该系列文章中使用的数据集&#xff0c;并且编写预处理代码&#xff0c;处理成咱们需要的格式。 一、数据集介绍 咱们使用的数据集名称是chinese-poetry&#xff0c;是一个在github上开源的中文诗…...

45.HarmonyOS鸿蒙系统 App(ArkUI)创建列表(List)

列表是一种复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;内容超过屏幕大小时&#xff0c;可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集&#xff0c;例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求&#xff08;如通讯录、音乐列…...

推荐算法之协同过滤

算法原理 透过百科&#xff0c;我们了解到协同过滤推荐&#xff08;Collaborative Filtering recommendation&#xff09;是在信息过滤和信息系统中正迅速成为一项很受欢迎的技术。与传统的基于内容过滤直接分析内容进行推荐不同&#xff0c;协同过滤算法结合用户行为分析用户…...

Kotlin 面试题

lifecycleScope.launchWhenResumed launchWhenResumed是一个扩展函数,它是LifecycleCoroutineScope的一部分,并且它是在Android的Lifecycle库中引入的。 这个函数的主要目的是在Lifecycle的对应组件(通常是Activity或Fragment)处于“resumed”状态时启动协程。 public fun …...

TCM(Tightly Coupled Memory)紧密耦合存储器简介

在ARM Cortex处理器中&#xff0c;TCM通常指的是紧密耦合存储器&#xff08;Tightly Coupled Memory&#xff09;。TCM是一种位于处理器核心旁边的高速存储器&#xff0c;它的设计目的是为了提供低延迟和高带宽的内存访问性能。 TCM的特点是它与处理器内核紧密耦合&#xff0c;…...

《自动机理论、语言和计算导论》阅读笔记:p172-p224

《自动机理论、语言和计算导论》学习第 8 天&#xff0c;p172-p224总结&#xff0c;总计 53 页。 一、技术总结 1.Context-Free Grammar(CFG) 2.parse tree (1)定义 p183&#xff0c;But perhaps more importantly, the tree, known as a “parse tree”, when used in a …...

typescript playwright 笔记

录制调式 命令 npx playwright codegen url npx playwright codegen https://www.baidu.com/typescript 中 format 和 split 的使用 import * as util from util;const str1 hellow %s; const format util.format; const str2 format(str1, word);// 提取taskId const str3…...

从零实现诗词GPT大模型:了解Transformer架构

专栏规划: https://qibin.blog.csdn.net/article/details/137728228 这篇文档我们开始对GPT的核心组件Transformer进行一个详细的讲解, 加急编写中…...

温故知新之-TCP Keepalive机制及长短连接

[学习记录] 前言 TCP连接一旦建立&#xff0c;只要连接双方不主动 close &#xff0c;连接就会一直保持。但建立连接的双方并不是一直都存在数据交互&#xff0c;所以在实际使用中会存在两种情况&#xff1a;一种是每次使用完&#xff0c;主动close&#xff0c;即短连接&…...

架构师系列-搜索引擎ElasticSearch(七)- 集群管理之分片

集群健康检查 Elasticsearch 的集群监控信息中包含了许多的统计数据&#xff0c;其中最为重要的一项就是集群健康&#xff0c;它在 status字段中展示为 green&#xff08;所有主分片和副本分片都正常&#xff09;、yellow&#xff08;所有数据可用&#xff0c;有些副本分片尚未…...

基于Spring Boot实现的图书个性化推荐系统

基于Spring Boot实现的图书个性化推荐系统 开发语言&#xff1a;Java语言 数据库&#xff1a;MySQL工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统实现 前台首页功能模块 学生注册 登录 图书信息 个人信息 管理员功能模块 学生管理界面图 图书分类管理界面图 图书信息管…...

安全加速SCDN带的态势感知能为网站安全带来哪些帮助

随着安全加速SCDN被越来越多的用户使用&#xff0c;很多用户都不知道安全加速SCDN的态势感知是用于做什么的&#xff0c;德迅云安全今天就带大家来了解下什么是态势感知&#xff0c;态势感知顾名思义就是对未发生的事件进行预知&#xff0c;并提前进行防范措施的布置&#xff0…...

java面向对象.day21(继承02--super)

说明 super父 this当前 使用super时&#xff0c;首先要继承父类&#xff0c;其次是在子类里面才能使用super。 继承父类后&#xff0c;运行子类时会同时调用父类的构造方法&#xff0c;如果要显性调用父类的构造方法必须在子类的第一行调用。 单使用super()表示调用父类构造…...

【数据结构】4.List的介绍

目录 1.什么是List 2.常见接口介绍 3.List的使用 1.什么是List 在集合框架中&#xff0c;List是一个接口&#xff0c;继承自Collection。 Collection也是一个接口&#xff0c;该接口中规范了后序容器中常用的一些方法&#xff0c;具体如下&#xff1a; Iterable也是一个接口…...

acwing算法提高之图论--最近公共祖先

目录 1 介绍2 训练 1 介绍 本博客用来记录"对于有根图中&#xff0c;求最近公共祖先"的题目。 求解方法&#xff1a; 向上标记法。每次求两个结点的最近公共祖先的时间复杂度是O(N)。由于时间复杂度较高&#xff0c;通常不用。倍增法。 倍增法重要思路&#xff1…...

C语言 函数——断言与防御式编程

目录 如何确定假设的真假&#xff1f; 断言 防御式编程&#xff08;Defensive programming&#xff09; 如何确定假设的真假&#xff1f; 程序中的假设 *某个特定点的某个表达式的值一定为真 *某个特定点的某个表达式的值一定位于某个区间等 问题&#xff1a;如何确定这些…...

【opencv】示例-travelsalesman.cpp 使用模拟退火算法求解旅行商问题

// 载入 OpenCV 的核心头文件 #include <opencv2/core.hpp> // 载入 OpenCV 的图像处理头文件 #include <opencv2/imgproc.hpp> // 载入 OpenCV 的高层GUI(图形用户界面)头文件 #include <opencv2/highgui.hpp> // 载入 OpenCV 的机器学习模块头文件 #includ…...

【linux深入剖析】深入理解软硬链接 | 动静态库的制作以及使用

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1.理解软硬链接1.1 操作观…...

xss常用标签和触发事件

无过滤情况 <script> <scirpt>alert("xss");</script> <img> 图片加载错误时触发 <img src"x" οnerrοralert(1)> <img src"1" οnerrοreval("alert(xss)")> 鼠标指针移动到元素时触发 <im…...

WPF中Binding的原理和应用

WPF中Binding的原理和应用 在WPF中&#xff0c;Binding机制是实现数据与界面的连接和同步的重要工具。了解Binding的原理和应用&#xff0c;对于开发人员来说是非常重要的。本文将详细介绍WPF中Binding的原理和应用&#xff0c;帮助读者更好地理解和运用这一强大的机制。 Bin…...

北京礼品网站建设/网络营销的六大功能

入门三问&#xff1a; 组的概念是什么&#xff1f;为什么引入它&#xff1f;有什么用&#xff1f; 答&#xff1a;通过组可以更加方便的管理用户&#xff0c;组的概念应用于各行行业&#xff0c;例如企业会使用部门、职能或地理区域的分类方式来管理成员&#xff0c;映射在Li…...

快站怎么做淘客网站/百度网盘破解版

zookeeper集群 可靠的zookpeer服务 只要集群的大多数准备好了&#xff0c;就可以使用这项 容错集群至少要三台以上机器&#xff0c;建议奇数以上 建议独立运行在每个服务器上 集群参数配置 initLimit 集群中的follower服务器(F)与leader服务器(L)之间完成初始化同 步连接时…...

淘宝网站建设哪个类目/营销成功的案例

文章目录简单选择排序基本思想&#xff1a;以炒股为喻&#xff0c;冒泡像炒短线&#xff0c;选择则是炒长线代码&#xff1a;交换次数非常少时间复杂度:O(n2)O(n^2)O(n2)堆排序&#xff1a;充分利用了完全二叉树的深度和序号信息堆 heap堆排序的原理堆排序的例子堆排序的代码&a…...

英文网站建/软文广告图片

今天在刷题的时候用到了正则&#xff0c;用的过程中就感觉有点不太熟练了&#xff0c;很久没有用正则都有点忘了。所以现在呢&#xff0c;我们就一起来review一下python中正则模块re的用法吧。 今天是review&#xff0c;所以一些基础的概念就不做介绍了&#xff0c;先来看正则中…...

更改wordpress登陆/长沙百度网站优化

/* 结构体与类之间的区别在于&#xff1a;结构体中所有成员&#xff08;包括数据成员和成员函数&#xff09;缺省情况下都是public&#xff0c; 而类中所有成员缺省情况下都是private。除些之外&#xff0c;两者可以通用。 */ #include <iostream> #include <string…...

做封面的网站/太原百度推广开户

Flask 初步学习 Flask程序的基本构造 from flask import Flask,render_templateapp Flask(__name__)app.route(/) def index():return render_template(demo.html)if __name__ __main__:app.run()路由器请求方法限定 在route的参数中设置method的值&#xff0c;默认是GET方…...