【Spring源码分析】Spring的启动流程源码解析

阅读此需阅读下面这些博客先
【Spring源码分析】Bean的元数据和一些Spring的工具
【Spring源码分析】BeanFactory系列接口解读
【Spring源码分析】执行流程之非懒加载单例Bean的实例化逻辑
【Spring源码分析】从源码角度去熟悉依赖注入(一)
【Spring源码分析】从源码角度去熟悉依赖注入(二)
【Spring源码分析】@Resource注入的源码解析
【Spring源码分析】循环依赖的底层源码剖析

Spring的启动流程源码解析

  • 一、AnnotationConfigApplicationContext()具体流程
    • reader 构造逻辑
    • scanner 构造逻辑
  • 二、refresh 源码分析
    • prepareRefresh()
    • obtainFreshBeanFactory()
    • !prepareBeanFactory(beanFactory) !
    • postProcessBeanFactory(beanFactory);
    • !invokeBeanFactoryPostProcessors(beanFactory);!
    • registerBeanPostProcessors(beanFactory)
    • initMessageSource()
    • initApplicationEventMulticaster()
    • onRefresh() 和 registerListeners()
    • finishBeanFactoryInitialization(beanFactory)
  • 三、总结

阐述这篇要讲述的内容,这篇主要是去阐述Spring在启动过程中会做哪些事情,具体指示的代码是下面这三行(没直接说AnnotationConfigApplicationContext重载的那个构造,是因为SpringBoot就是用的下三行,所以干脆就这样了):

		AnnotationConfigApplicationContextcontext = new AnnotationConfigApplicationContext();context.register(AppConfig.class);context.refresh();

那么这篇就主要就是说这三行代码都做了啥,Spring启动流程就是啥,其中 refresh() 方法是主要的,内容也有点多。

一、AnnotationConfigApplicationContext()具体流程

可以说就三步:

  1. 构造 DefaultListableBeanFactory 实例;
    • 调用构造方法首先是先执行的父类构造,父类构造会实例化一个 DefaultListableBeanFactory。
    • 在这里插入图片描述
  2. 实例化 AnnotatedBeanDefinitionReader 实例;
  3. 实例化 ClassPathBeanDefinitionScanner 扫描器。
	public AnnotationConfigApplicationContext() {// super(); 这里会构造 BeanFactoryStartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");// 额外会创建StandardEnvironmentthis.reader = new AnnotatedBeanDefinitionReader(this);createAnnotatedBeanDefReader.end();// 会填充 Environment 和 ClassLoader 类加载器;// 会将 @Component 注解放入到 includeFilter 集合中,扫描的时候筛选出对应的资源好封装成BeanDefinitionthis.scanner = new ClassPathBeanDefinitionScanner(this);}

有必要看一下 AnnotationConfigApplicationContext 关系图:

在这里插入图片描述那有关 BeanFactory 接口的功能呢其实就是调用 beanFactory#对应方法.

reader 构造逻辑

	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {this(registry, getOrCreateEnvironment(registry));}

会去获取一个 StandardEnvironment 对象,然后调用另一个构造方法
在这里插入图片描述

	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {this.registry = registry;// 用来解析@Conditional注解的this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);// 注册// 这里主要是去设置比较器和是否允许注入的解析器// 而后续的后置处理器是等扫描完还会将这些后置处理器拿到然后去放进去,这里的话没用AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}

流程也是比较简单,就是构建个 @Conditional 注解的解析器,后续扫描BeanDefinition的候选者用到了;然后就是去向BeanFactory中注入 Order 比较器 AnnotationAwareOrderComparator 和 注入候选者解析器 ContextAnnotationAutowireCandidateResolver

后者那个 ContextAnnotationAutowireCandidateResolver 其实就是在我们@Autowired或者说@Resource注解注入的时候,在通过类型去找Bean的时候,会有一段责任链设计模式的筛选——autowireCandidate 参数值应该为 true->泛型判断->@Qualifier 解析,不知道还记不记得,不记得没关系,不影响流程的阅读。

随后会注入一些 BeanPostProcessor 和 BeanFactoryPostProcessor,这里是还未实例化的,是注入到 BeanDefinitionMapBeanDefinitionNames 里的注入。

		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);// 注册ConfigurationClassPostProcessor类型的BeanDefinitionif (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));}// 注册AutowiredAnnotationBeanPostProcessor类型的BeanDefinitionif (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));}// 注册CommonAnnotationBeanPostProcessor类型的BeanDefinition// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));}// 注册EventListenerMethodProcessor类型的BeanDefinition,用来处理@EventListener注解的// 处理 @EventListener 会遍历所有的 beanNames 对应的Class,只要是单例就行,是懒加载也会解析为对应的ApplicationListenerif (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));}// 注册DefaultEventListenerFactory类型的BeanDefinition,用来处理@EventListener注解的if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));}
  • 首先是注入了 ConfigurationClassPostProcessor,它类的结构如下,后面扫描要用:
    • 在这里插入图片描述
  • 然后是注入 AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor,我们的@Value、@Resource、@Autowired、@Qualified、@PreDestroy、@PostConstruct…等注解作用都是靠它俩完成的
  • 随后注入 EventListenerMethodProcessor ,它是用来 @EventListener 注解的,主要解析对象是 beanDefinitionNames 里存在滴;
  • 随后注入 DefaultEventListenerFactory 它是用来将 @EventListener 修饰的方法构建成 ApplicationListener 实例,然后放入到监听集中。

scanner 构造逻辑

	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment, @Nullable ResourceLoader resourceLoader) {this.registry = registry;if (useDefaultFilters) {registerDefaultFilters();}setEnvironment(environment);setResourceLoader(resourceLoader);}

在这里插入图片描述

也没做啥就是将 @Component 注解放入进 includeFilters 集合中,后续筛选候选者 BeanDefinition 用,然后把环境对象和资源加载器构建一手,就后续扫描资源用的。

看完 AnnotationConfigApplicationContext 的无参构造可以知道:
它无非就是去构建后续 register、refresh和扫描需要的环境

context.register 其实没做啥,就是将我们上面写的 AppConfig 直接通过 reader 将其对应的BeanDefinition和beanName容器中就是了。

二、refresh 源码分析

这个好长,我只能一个方法一个方法解析了,如果不愿看的可以直接跳到总结看流程图。

	@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// Prepare this context for refreshing.// 一些标志位和验证,咱别管prepareRefresh();// Tell the subclass to refresh the internal bean factory.// 这里会判断能否刷新,并且返回一个BeanFactory, 刷新不代表完全情况,主要是先执行Bean的销毁,然后重新生成一个BeanFactory,再在接下来的步骤中重新去扫描等等// 对于Spring来说,对于AnnotationConfigApplicationContext来说,不允许下一次refresh也是在这个阶段进行的ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.// 准备BeanFactory// 1. 设置BeanFactory的类加载器、SpringEL表达式解析器、类型转化注册器// 2. 添加三个BeanPostProcessor,注意是具体的BeanPostProcessor实例对象// 3. 记录ignoreDependencyInterface// 4. 记录ResolvableDependency// 5. 添加三个单例BeanprepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.// 子类来设置一下BeanFactorypostProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");// Invoke factory processors registered as beans in the context.// BeanFactory准备好了之后,执行BeanFactoryPostProcessor,开始对BeanFactory进行处理// 默认情况下:// 此时beanFactory的beanDefinitionMap中有6个BeanDefinition,5个基础BeanDefinition+AppConfig的BeanDefinition// 而这6个中只有一个BeanFactoryPostProcessor:ConfigurationClassPostProcessor// 这里会执行ConfigurationClassPostProcessor进行@Component的扫描,扫描得到BeanDefinition,并注册到beanFactory中// 注意:扫描的过程中可能又会扫描出其他的BeanFactoryPostProcessor,那么这些BeanFactoryPostProcessor也得在这一步执行invokeBeanFactoryPostProcessors(beanFactory);  // scanner.scan()// Register bean processors that intercept bean creation.// 将扫描到的BeanPostProcessors实例化并排序,并添加到BeanFactory的beanPostProcessors属性中去registerBeanPostProcessors(beanFactory);beanPostProcess.end();// Initialize message source for this context.// 设置ApplicationContext的MessageSource,要么是用户设置的,要么是DelegatingMessageSourceinitMessageSource();// Initialize event multicaster for this context.// 设置ApplicationContext的applicationEventMulticaster,要么是用户设置的,要么是SimpleApplicationEventMulticasterinitApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.// 给子类的模板方法// Spring啥也没干,MVC倒是重写了,给其他的模板方法onRefresh();// Check for listener beans and register them.// 把定义的ApplicationListener的Bean对象,设置到ApplicationContext中去,并执行在此之前所发布的事件registerListeners();// Instantiate all remaining (non-lazy-init) singletons.// 实例化所有非懒加载的单例beanfinishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();contextRefresh.end();}}}

prepareRefresh()

一些标志位设置和验证啥的,跟咱开发关系不大,跳过~

obtainFreshBeanFactory()

这个方法是去得到 BeanFactory,我们之前阐述了,在 AnnotationConfigApplicationContext 的父类的无参构造中会构建一个 DefaultListableBeanFactory,这里就是将这个返回。

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

但这里还需要注意的是 refresh 方法不能重复调用,就是在这里进行判断的。

在这里插入图片描述
内部封装了一个 refreshed 的标志为,类型是 AtomicBoolean 类型的,这里会通过 CAS 判断设置,如果已经是true了会抛出异常,表示不允许重复进行 refresh。
在这里插入图片描述

!prepareBeanFactory(beanFactory) !

这个方法做了挺多事的,主要是对 BeanFactory 的初始化,但是在我们之前都有遇到,它何时注入的呢?下面看看:

  1. 注入类加载器、Spel 解析器、类型转化器(在咱Bean生命周期createBean的第一步就是去尝试使用类加载器对类进行加载;在注入解析@Value的时候针对#{}就是Spel解析器在发挥作用;类型转化器在找到注入实例不就开始用类型转化器进行转化了)。
    在这里插入图片描述
  2. 添加一个 ApplicationContextAwareProcecssor 实例到 BeanFactory 容器中(它就是针对那些Aware注入嘛,在生命周期之初始化前会遍历BeanPostProcessor那里),可以发现它是第一个填充到 BeanFactory 容器中的 BeanPostProcessor

在这里插入图片描述

  1. 向 ignoreDependecyInterfaces 中添加点东西,这个无所谓的,这个是Spring提供的那种ByName/ByType的注入方式,前面参数 populateBean 一 的时候有说过,开发又不用不阐述了,就是说这里面的setter的话就不会进行注入了。
    在这里插入图片描述
  2. 注入一些Bean,表示已经解析过了。这个在我们去根据类型找Bean的时候,就是会拿注入的类型和这些去比对,如果比对成功就直接注入这里的 Bean(如果想不起来了,去看属性注入二,我阐述过了)。

在这里插入图片描述

  1. ApplicationListenerDetector 这个事件监听器的检测BeanPostProcessor。它主要是在Bean初始化后那个时候去判断一下是否是单例且是否实现了ApplicationListener接口,如果是就放入到applicationListeners集合中。可以说这是Spring启动阶段放入到BeanFactory中的第二个BeanPostProcessor,用于检测容器中的ApplicationListener。
    在这里插入图片描述在这里插入图片描述
  2. 将环境对象放到单例池里(这属于手动添加单例池)
    在这里插入图片描述

postProcessBeanFactory(beanFactory);

Spring 这个方法没有实现,这是一个模板方法,用于其他接入Spring的,用来填充BeanFactory的。

!invokeBeanFactoryPostProcessors(beanFactory);!

代码太多,这里就阐述个流程,其中如何解析的配置也是在这个方法中,其中在解析@ComponentScan的时候就会进行扫描,我下篇博客阐述。
首先里面是用的 BeanFactoryPostProcessor ,准确点说是它的子类 BeanDefinitionRegistryPostProcessor。前面我们阐述过一些 BeanPostProcessor,那是用来处理 Bean 的后置处理器,那这个 BeanFactoryBeanProcessor 就是 BeanFactory 的后置处理器。(阐述这个是为了你们若是根据这个博客去看源码的话不会懵,还有就是前面在构造reader的时候注入了 ConfigurationClassPostProcessor,在此之前就注入了这么个BeanFactoryPostProcessor)

下面直接阐述流程:

  • 解析配置类
    • 解析 @ComponentScan,扫描得到BeanDefinition并注册;
    • 解析 @Bean、@Import等注解得到BeanDefinition并注册;
    • 详细下篇博客阐述;
  • 执行 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry()
  • 执行 BeanDefinitionRegistryPostProcessor#postProcessBeanFactory()

说白了就是解析注解后注册BeanDefinition。

registerBeanPostProcessors(beanFactory)

这里就是注册BeanPostProcessor到单例池中,然后添加到 beanPostProcessors 集合中,说是排了序,但感觉排不排一个样。就是 @PriorityOrdered > @Ordered > 啥都没有的。说是这样说,但我感觉Spring中的BeanPostProcessor没啥顺序可言,以下是Spring的顺序,大伙看看就行:

在这里插入图片描述其中那个AsyncAnnotationBeanPostProcessor那个是因为我开始加了@EnableAsync注解忘记去掉了。

initMessageSource()

就是设置 ApplicationContext 的 MessageSource,没啥好说的。

initApplicationEventMulticaster()

初始化事件转播器,默认是 SimpleApplicationEventMulticaster,如果自己构建了并且放入了容器,那就用咱的,但谁没事写这玩意啊。

在这里插入图片描述

onRefresh() 和 registerListeners()

onRefresh 是模版方法,Spring啥也没干;
registerListeners 的作用是把定义好的 ApplicationListener 的Bean对象注入到ApplicationContext中,但是Spring本质啥也没干,因为没有。

finishBeanFactoryInitialization(beanFactory)

这就核心来了,实例化所有的非懒加载的单例Bean。
前面博客讲述了这个的主要流程。

三、总结

可以看流程图:
https://www.processon.com/view/link/5f60a7d71e08531edf26a919

但是感觉看流程图挺累的,还是简单阐述一下吧(大概流程哈,不可能详细阐述):

  • 构建个DefaultBeanFactory注入进去;
  • 构建环境:reader、scanner、一些BeanPostProcessor这些;
  • 通过 ConfigurationClassPostProcessor 去扫描配置类;
  • 然后去实例化容器里的 BeanPostProcessor 到 beanPostProcessors 集合中;
  • 构建事件传播器;
  • 实例化非懒加载单例Bean到单例池中。

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

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

相关文章

应用层 HTTP协议(1)

回顾 前面我们说到了数据链路层,网络层IP协议,传输层的TCP/UDP协议一些知识点,现在让我们谈谈 应用层的HTTP协议的知识点. 这篇我们先从大局入手,仍然是对总体报文进行全局分析,再对细节报文进行拆解分析 版本 首先我们谈谈HTTP协议的版本 HTTP 0.9 (1991) HTTP 1.0 (1992 - 1…

二、OpenAI开发者快速入门

启动并运行OpenAI API OpenAI API 为开发者提供一个简单的接口&#xff0c;使其能够在他们的应用中创建一个智能层&#xff0c;由OpenAI最先进的模型提供支持。聊天补全端点为ChatGPT提示支持&#xff0c;一种简单的方法是&#xff1a;输入文本&#xff0c;使用GPT-4模型输出。…

Spring Boot3统一结果封装

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途。 目录 前置条件 封装目的 常用格式 定义返回结果枚举类 定义返回结果封装类 对返回结果封装 测试封装 前置条件 已…

【Linux】Linux下的基本指令

Linux下的基本指令 Linux 的操作特点&#xff1a;纯命令行ls 指令文件 pwd命令Linux的目录结构绝对路径 / 相对路径&#xff0c;我该怎么选择&#xff1f; cd指令touch指令mkdir指令&#xff08;重要&#xff09;rmdir指令rm 指令&#xff08;重要&#xff09;man指令&#xff…

C语言分钟计算

一.题目描述 给你同一天的两个时间(24小时制),求这两个时间内有多少分钟,保证第一个时间在第二个时间之前. 二.输入描述 输入两行,每行包括两个整数表示小时和分钟. 三.输出描述 输出分钟数. 四.示例 输入 10 10 11 05 输出 55 五.代码

烟火可禁却难禁,灵境难及终将及

现实痛点 2024年1月30日&#xff0c;贵阳市发生了一件令人痛心的事&#xff0c;有人在小区内放烟花导致失火&#xff0c;一男子具备足够的消防安全知识&#xff0c;知道如何使用消防栓却因设施不合格接不上消防栓&#xff0c;接上了又没水&#xff0c;消防员来也束手无策&…

【Django】Django中间件

Django中间件 1 中间件的定义 中间件是Django请求/响应处理的钩子框架。它是一个轻量级的、低级的“插件”系统&#xff0c;用于全局改变Django的输入或输出。 中间件以类的形式体现。 每个中间件组件负责做一些特定的功能。例如&#xff0c;Django包含一个中间件组件Authen…

Redis -- 渐进式遍历

家&#xff0c;是心的方向。不论走多远&#xff0c;总有一盏灯为你留着。桌上的碗筷多了几双&#xff0c;笑声也多了几分温暖。家人团聚&#xff0c;是最美的风景线。时间&#xff1a;2024年 2月 8日 12:51:20 目录 前言 语法 示例 前言 试想一个场景,那就是在key非常多的…

前端vue学习

1.创建vue项目 2.绑定html属性值 3.v-bind &#xff1a; 4.v-if 5.v-for 6.更改html内容 7.e.preventDefault()阻止默认事件 8.事件修饰符号&#xff0c;使用.prevent可以达到和上面一样的效果 9.阻止事件冒泡 10.计算属性缓存与方法&#xff0c;计算属性存在缓存&#xff0c;…

Bean 的作用域

Bean 的作用域种类 在 Spring 中⽀持 6 种作⽤域&#xff0c;后 4 种在 Spring MVC 环境才⽣效 1. singleton&#xff1a;单例作⽤域 2. prototype&#xff1a;原型作⽤域&#xff08;多例作⽤域&#xff09; 3. request&#xff1a;请求作⽤域 4. session&#xff1a;会话作⽤…

Android:内存泄漏检查内存优化

3.17Android优化 手机移动设备的内存是有限的,需要避免内存泄漏,优化内存使用。 1.java中四种引用类型 强引用、软引用、弱引用、虚引用。 强引用:使用类构造方法,创建对象,当内存超出了,也不会释放对象所占内存空间; String str = new String(‘1223’); 切断引用str=…

Python pandas中read_csv函数的io参数

前言 在数据分析和处理中&#xff0c;经常需要读取外部数据源&#xff0c;例如CSV文件。Python的pandas库提供了一个强大的 read_csv() 函数&#xff0c;用于读取CSV文件并将其转换成DataFrame对象&#xff0c;方便进一步分析和处理数据。在本文中&#xff0c;将深入探讨 read…

Java与MySQL的精准结合:打造高效审批流程

1流程思路分析 审批流程&#x1f431;‍&#x1f4bb; 1.串行流程 当前节点审批完成后&#xff0c;下一次节点才能进行操作&#xff0c;例如经理通过之后&#xff0c;总监才能审批&#xff1b; 2.并行流程 一个审批节点需要多人联审。一般有两种方式&#xff1a;会签、或签…

尚硅谷 Vue3+TypeScript 学习笔记(中)

目录 三、路由 3.1. 【对路由的理解】 3.2. 【基本切换效果】 3.3. 【两个注意点】 3.4.【路由器工作模式】 3.5. 【to的两种写法】 3.6. 【命名路由】 3.7. 【嵌套路由】 3.8. 【路由传参】 query参数 params参数 3.9. 【路由的props配置】 3.10. 【 replace属性…

C++,stl,list容器详解

目录 1.list基本概念 2.list构造函数 3.list的赋值和交换 4.list大小操作 5.list的插入的删除 6.list数据存取 7.list反转和排序 排序案例 1.list基本概念 2.list构造函数 #include<bits/stdc.h> using namespace std;void print(const list<int> &lk) …

.NET命令行(CLI)常用命令

本文用于记录了.NET软件开发全生命周期各阶段常用的一些CLI命令&#xff0c;用于开发速查。 .NET命令行&#xff08;CLI&#xff09;常用命令 项目创建&#xff08;1&#xff09;查看本机SDK&#xff08;2&#xff09;查看本机可以使用的.NET版本&#xff08;3&#xff09;生成…

【开源】SpringBoot框架开发桃花峪滑雪场租赁系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 游客服务2.2 雪场管理 三、数据库设计3.1 教练表3.2 教练聘请表3.3 押金规则表3.4 器材表3.5 滑雪场表3.7 售票表3.8 器材损坏表 四、系统展示五、核心代码5.1 查询教练5.2 教练聘请5.3 查询滑雪场5.4 滑雪场预定5.5 新…

基于完全二叉树实现线段树-- [爆竹声中一岁除,线段树下苦踌躇]

文章目录 一.完全二叉树完全二叉树的父子结点引索关系 二.线段树三.基于完全二叉树实现线段树关于线段树的结点数量问题的证明递归建树递归查询区间和递归单点修改线段树模板题 一.完全二叉树 完全二叉树的物理结构是线性表,逻辑结构是二叉树 完全二叉树的父子结点引索关系 …

【数据结构】双向链表(链表实现+测试+原码)

前言 在双向链表之前&#xff0c;如果需要查看单链表来复习一下&#xff0c;链接在这里&#xff1a; http://t.csdnimg.cn/Ib5qS 1.双向链表 1.1 链表的分类 实际中链表的结构非常多样&#xff0c;以下情况组合起来就有8种链表结构&#xff1a; 1.1.1 单向或者双向 1.1.2 …

专业135+总400+中国科学院大学859国科大信号与系统考研经验电子信息与通信,真题,大纲,参考书

今年考研专业课859信号与系统135&#xff0c;总分400上岸国科大&#xff0c;总结一下自己这一年的复习经验&#xff0c;希望对后面报考中科院大学的同学有所帮助。 专业课&#xff1a; 国科大不同研究所都是统一命题&#xff0c;859信号与系统的参考书目是郑君里的《信号与系…