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

为啥一个 main 方法就能启动项目

在 Spring Boot 出现之前,我们要运行一个 Java Web 应用,首先需要有一个 Web 容器(例如 Tomcat 或 Jetty),然后将我们的 Web 应用打包后放到容器的相应目录下,最后再启动容器。

在 IDE 中也需要对 Web 容器进行一些配置,才能够运行或者 Debug。而使用 Spring Boot 我们只需要像运行普通 JavaSE 程序一样,run 一下 main() 方法就可以启动一个 Web 应用了。这是怎么做到的呢?今天我们就一探究竟,分析一下 Spring Boot 的启动流程。

概览

回看我们写的第一个 Spring Boot 示例,我们发现,只需要下面几行代码我们就可以跑起一个 Web 服务器:

@SpringBootApplication
public class HelloApplication {public static void main(String[] args) {SpringApplication.run(HelloApplication.class, args);}
}

去掉类的声明和方法定义这些样板代码,核心代码就只有一个 @SpringBootApplication 注解和 SpringApplication.run(HelloApplication.class, args) 了。而我们知道注解相当于是一种配置,那么这个 run() 方法必然就是 Spring Boot 的启动入口了。

接下来,我们沿着 run() 方法来顺藤摸瓜。进入 SpringApplication 类,来看看 run() 方法的具体实现:

public class SpringApplication {......public ConfigurableApplicationContext run(String... args) {// 1 应用启动计时开始StopWatch stopWatch = new StopWatch();stopWatch.start();// 2 声明上下文DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;// 3 设置 java.awt.headless 属性configureHeadlessProperty();// 4 启动监听器SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {// 5 初始化默认应用参数ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 6 准备应用环境ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);configureIgnoreBeanInfo(environment);// 7 打印 Banner(Spring Boot 的 LOGO)Banner printedBanner = printBanner(environment);// 8 创建上下文实例context = createApplicationContext();context.setApplicationStartup(this.applicationStartup);// 9 构建上下文prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 10 刷新上下文refreshContext(context);// 11 刷新上下文后处理afterRefresh(context, applicationArguments);// 12 应用启动计时结束stopWatch.stop();if (this.logStartupInfo) {// 13 打印启动时间日志new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}// 14 发布上下文启动完成事件listeners.started(context);// 15 调用 runnerscallRunners(context, applicationArguments);}catch (Throwable ex) {// 16 应用启动发生异常后的处理handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {// 17 发布上下文就绪事件listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;}......
}

Spring Boot 启动时做的所有操作都这这个方法里面,当然在调用上面这个 run() 方法之前,还创建了一个 SpringApplication 的实例对象。因为上面这个 run() 方法并不是一个静态方法,所以需要一个对象实例才能被调用。

可以看到,方法的返回值类型为 ConfigurableApplicationContext,这是一个接口,我们真正得到的是 AnnotationConfigServletWebServerApplicationContext 的实例。通过类名我们可以知道,这是一个基于注解的 Servlet Web 应用上下文(我们知道上下文(context)是 Spring 中的核心概念)。

上面对于 run() 方法中的每一个步骤都做了简单的注释,接下来我们选择几个比较有代表性的来详细分析。

应用启动计时

在 Spring Boot 应用启动完成时,我们经常会看到类似下面内容的一条日志:

Started AopApplication in 2.732 seconds (JVM running for 3.734)

应用启动后,会将本次启动所花费的时间打印出来,让我们对于启动的速度有一个大致的了解,也方便我们对其进行优化。记录启动时间的工作是 run() 方法做的第一件事,在编号 1 的位置由 stopWatch.start() 开启时间统计,具体代码如下:

public void start(String taskName) throws IllegalStateException {if (this.currentTaskName != null) {throw new IllegalStateException("Can't start StopWatch: it's already running");}// 记录启动时间this.currentTaskName = taskName;this.startTimeNanos = System.nanoTime();
}

然后到了 run() 方法的基本任务完成的时候,由 stopWatch.stop()(编号 12 的位置)对启动时间做了一个计算,源码也很简单:

public void stop() throws IllegalStateException {if (this.currentTaskName == null) {throw new IllegalStateException("Can't stop StopWatch: it's not running");}// 计算启动时间long lastTime = System.nanoTime() - this.startTimeNanos;this.totalTimeNanos += lastTime;......
}

最后,在 run() 中的编号 13 的位置将启动时间打印出来:

if (this.logStartupInfo) {// 打印启动时间new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}

打印 Banner

Spring Boot 每次启动是还会打印一个自己的 LOGO,如图 8-6:

在这里插入图片描述

图 8-6 Spring Boot Logo

这种做法很常见,像 Redis、Docker 等都会在启动的时候将自己的 LOGO 打印出来。Spring Boot 默认情况下会打印那个标志性的“树叶”和 “Spring” 的字样,下面带着当前的版本。

在 run() 中编号 7 的位置调用打印 Banner 的逻辑,最终由 SpringBootBanner 类的 printBanner() 完成。这个图案定义在一个常量数组中,代码如下:

class SpringBootBanner implements Banner {private static final String[] BANNER = {"", "  .   ____          _            __ _ _"," /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\"," \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )", "  '  |____| .__|_| |_|_| |_\\__, | / / / /"," =========|_|==============|___/=/_/_/_/" };......public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {for (String line : BANNER) {printStream.println(line);}......}}

手工格式化了一下 BANNER 的字符串,轮廓已经清晰可见了。真正打印的逻辑就是 printBanner() 方法里面的那个 for 循环。

记录启动时间和打印 Banner 代码都非常的简单,而且都有很明显的视觉反馈,可以清晰的看到结果。拿出来咱们做个热身,配合断点去 Debug 会有更加直观的感受,尤其是打印 Banner 的时候,可以看到整个内容被一行一行打印出来,让我想起了早些年用那些配置极低的电脑(还是 CRT 显示器)运行着 Win98,经常会看到屏幕内容一行一行加载显示。

创建上下文实例

下面我们来到 run() 方法中编号 8 的位置,这里调用了一个 createApplicationContext() 方法,该方法最终会调用 ApplicationContextFactory 接口的代码:

ApplicationContextFactory DEFAULT = (webApplicationType) -> {try {switch (webApplicationType) {case SERVLET:return new AnnotationConfigServletWebServerApplicationContext();case REACTIVE:return new AnnotationConfigReactiveWebServerApplicationContext();default:return new AnnotationConfigApplicationContext();}}catch (Exception ex) {throw new IllegalStateException("Unable create a default ApplicationContext instance, "+ "you may need a custom ApplicationContextFactory", ex);}
};

这个方法就是根据 SpringBootApplication 的 webApplicationType 属性的值,利用反射来创建不同类型的应用上下文(context)。而属性 webApplicationType 的值是在前面执行构造方法的时候由 WebApplicationType.deduceFromClasspath() 获得的。通过方法名很容易看出来,就是根据 classpath 中的类来推断当前的应用类型。

我们这里是一个普通的 Web 应用,所以最终返回的类型为 SERVLET。所以会返回一个 AnnotationConfigServletWebServerApplicationContext 实例。

构建容器上下文

接着我们来到 run() 方法编号 9 的 prepareContext() 方法。通过方法名,我们也能猜到它是为 context 做上台前的准备工作的。

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {......// 加载资源load(context, sources.toArray(new Object[0]));listeners.contextLoaded(context);
}

在这个方法中,会做一些准备工作,包括初始化容器上下文、设置环境、加载资源等。

加载资源

上面的代码中,又调用了一个很关键的方法——load()。这个 load() 方法真正的作用是去调用 BeanDefinitionLoader 类的 load() 方法。源码如下:

class BeanDefinitionLoader {......void load() {for (Object source : this.sources) {load(source);}}private void load(Object source) {Assert.notNull(source, "Source must not be null");if (source instanceof Class<?>) {load((Class<?>) source);return;}if (source instanceof Resource) {load((Resource) source);return;}if (source instanceof Package) {load((Package) source);return;}if (source instanceof CharSequence) {load((CharSequence) source);return;}throw new IllegalArgumentException("Invalid source type " + source.getClass());}......
}

可以看到,load() 方法在加载 Spring 中各种资源。其中我们最熟悉的就是 load((Class<?>) source) 和 load((Package) source) 了。一个用来加载类,一个用来加载扫描的包。

load((Class<?>) source) 中会通过调用 isComponent() 方法来判断资源是否为 Spring 容器管理的组件。 isComponent() 方法通过资源是否包含 @Component 注解(@Controller、@Service、@Repository 等都包含在内)来区分是否为 Spring 容器管理的组件。

而 load((Package) source) 方法则是用来加载 @ComponentScan 注解定义的包路径。

刷新上下文

run() 方法编号10 的 refreshContext() 方法是整个启动过程比较核心的地方。像我们熟悉的 BeanFactory 就是在这个阶段构建的,所有非懒加载的 Spring Bean(@Controller、@Service 等)也是在这个阶段被创建的,还有 Spring Boot 内嵌的 Web 容器要是在这个时候启动的。

跟踪源码你会发现内部调用的是 ConfigurableApplicationContext.refresh(),ConfigurableApplicationContext 是一个接口,真正实现这个方法的有三个类:AbstractApplicationContext、ReactiveWebServerApplicationContext 和 ServletWebServerApplicationContext。

AbstractApplicationContext 为后面两个的父类,两个子类的实现比较简单,主要是调用父类实现,比如 ServletWebServerApplicationContext 中的实现是这样的:

public final void refresh() throws BeansException, IllegalStateException {try {super.refresh();}catch (RuntimeException ex) {WebServer webServer = this.webServer;if (webServer != null) {webServer.stop();}throw ex;}
}

主要的逻辑都在 AbstractApplicationContext 中:

@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// 1 准备将要刷新的上下文prepareRefresh();// 2 (告诉子类,如:ServletWebServerApplicationContext)刷新内部 bean 工厂ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 3 为上下文准备 bean 工厂prepareBeanFactory(beanFactory);try {// 4 允许在子类中对 bean 工厂进行后处理postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");// 5 调用注册为 bean 的工厂处理器invokeBeanFactoryPostProcessors(beanFactory);// 6 注册拦截器创建的 bean 处理器registerBeanPostProcessors(beanFactory);beanPostProcess.end();// 7 初始化国际化相关资源initMessageSource();// 8 初始化事件广播器initApplicationEventMulticaster();// 9 为具体的上下文子类初始化特定的 beanonRefresh();// 10 注册监听器registerListeners();// 11 实例化所有非懒加载的单例 beanfinishBeanFactoryInitialization(beanFactory);// 12 完成刷新发布相应的事件(Tomcat 就是在这里启动的)finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// 遇到异常销毁已经创建的单例 beandestroyBeans();// 充值 active 标识cancelRefresh(ex);// 将异常向上抛出throw ex;} finally {// 重置公共缓存,结束刷新resetCommonCaches();contextRefresh.end();}}
}

简单说一下编号 9 处的 onRefresh() 方法,该方法父类未给出具体实现,需要子类自己实现,ServletWebServerApplicationContext 中的实现如下:

protected void onRefresh() {super.onRefresh();try {createWebServer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start web server", ex);}
}private void createWebServer() {......if (webServer == null && servletContext == null) {......// 根据配置获取一个 web server(Tomcat、Jetty 或 Undertow)ServletWebServerFactory factory = getWebServerFactory();this.webServer = factory.getWebServer(getSelfInitializer());......}......
}

factory.getWebServer(getSelfInitializer()) 会根据项目配置得到一个 Web Server 实例,这里跟下一篇将要谈到的自动配置有点关系。

更多独家精彩内容尽在我的新书《Spring Boot趣味实战课》中。

相关文章:

为啥一个 main 方法就能启动项目

在 Spring Boot 出现之前&#xff0c;我们要运行一个 Java Web 应用&#xff0c;首先需要有一个 Web 容器&#xff08;例如 Tomcat 或 Jetty&#xff09;&#xff0c;然后将我们的 Web 应用打包后放到容器的相应目录下&#xff0c;最后再启动容器。 在 IDE 中也需要对 Web 容器…...

洛谷:P1554 梦中的统计 JAVA

思路&#xff1a;定义一个长度为10的数组&#xff0c;数组下标代表数组元素的数字&#xff0c;比如arr[0]代表数字0.用一个for循环&#xff0c;对每个数先取余再取整&#xff0c;知道取整得到的数为0&#xff0c;说明该数字已经被拆解完了。今天又学了一个输入&#xff0c;原来…...

C++初学笔记整理

目录 1. C关键字 2. 命名空间 1&#xff09;命名空间的引入和概述 2&#xff09;命名空间的定义 3&#xff09;std与命名空间的使用 4).相关特性 3. C输入&输出 4. 缺省参数 1 &#xff09;缺省参数概念 2&#xff09;使用及分类 a.全缺省 b.部分缺省 5. 函数…...

记录--在Vue3这样子写页面更快更高效

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 在开发管理后台过程中&#xff0c;一定会遇到不少了增删改查页面&#xff0c;而这些页面的逻辑大多都是相同的&#xff0c;如获取列表数据&#xff0c;分页&#xff0c;筛选功能这些基本功能。而…...

【程序设计与算法(三)】测验和作业题部分答案汇总(面向对象篇)

题目来源&#xff1a;程序设计与算法&#xff08;三&#xff09;测验和作业题汇总 文章目录001:简单的swap002:难一点的swap003:好怪异的返回值004:神秘的数组初始化005:编程填空&#xff1a;学生信息处理程序006:奇怪的类复制007:返回什么才好呢008:超简单的复数类009:哪来的输…...

LeetCode 349. 两个数组的交集和 692. 前K个高频单词

两个数组的交集 难度 简单 题目链接 这道题的难度不大&#xff0c;我们可以把数组里的数据存到set里面。这样就完成了排序和去重&#xff0c;然后我们再把一个set里面的数据和另外一个set数据进行比较。如果相同就插入到数组里。 代码如下&#xff1a; 但是这个算法的时间复…...

SpringCloud的五大组件功能

SpringCloud的五大组件 EurekaRibbonHystrixZuulConfig 一、Eureka 作用是实现服务治理&#xff0c;即服务注册与发现。 Eureka服务器相当于一个中介&#xff0c;负责管理、记录服务提供者的信息。服务调用者不需要自己寻找服务 &#xff0c;而是把需求告诉Eureka &#x…...

剑指 Offer II 016. 不含重复字符的最长子字符串

题目链接 剑指 Offer II 016. 不含重复字符的最长子字符串 mid 题目描述 给定一个字符串 s&#xff0c;请你找出其中不含有重复字符的 最长连续子字符串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子字符串是 “abc”&#xff0c;所以其长度…...

HBase读取流程详解

读流程从头到尾可以分为如下4个步骤&#xff1a;Client-Server读取交互逻辑&#xff0c;Server端Scan框架体系&#xff0c;过滤淘汰不符合查询条件的HFile&#xff0c;从HFile中读取待查找Key。其中Client-Server交互逻辑主要介绍HBase客户端在整个scan请求的过程中是如何与服务…...

Redis学习(一):NoSQL概述

为什么要使用Nosql 现在是大数据时代&#xff0c;过大的数据一般的数据库无法进行分析处理了。 单机MySQL的年代 90年代&#xff0c;一个基本的网站访问量一般不会太大&#xff0c;单个数据库完全足够&#xff01; 那个时候&#xff0c;更多的去使用静态网站&#xff0c;服务器…...

ESP32设备驱动-MCP23017并行IO扩展驱动

MCP23017并行IO扩展驱动 1、MCP23017介绍 MCP23017是一个用于 I2C 总线应用的 16 位通用并行 I/O 端口扩展器。 16 位 I/O 端口在功能上由两个 8 位端口(PORTA 和 PORTB)组成。 MCP23017 可配置为在 8 位或 16 位模式下工作。 其引脚排列如下: MCP23017 在 3.3v 下工作正常…...

RabbitMQ简介

0. 学习目标 能够说出什么是消息中间件能够安装RabbitMQ能够编写RabbitMQ的入门程序能够说出RabbitMQ的5种模式特征能够使用Spring整合RabbitMQ 1. 消息中间件概述 1.1. 什么是消息中间件 MQ全称为Message Queue&#xff0c;消息队列是应用程序和应用程序之间的通信方法。是…...

【项目设计】高并发内存池(五)[释放内存流程及调通]

&#x1f387;C学习历程&#xff1a;入门 博客主页&#xff1a;一起去看日落吗持续分享博主的C学习历程博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 也许你现在做的事情&#xff0c;暂时看不到成果&#xff0c;但不要忘记&…...

Git标签与版本发布

1. 什么是git标签 标签&#xff0c;就类似我们阅读时的书签&#xff0c;可以很轻易找到自己阅读到了哪里。 对于git来说&#xff0c;在使用git对项目进行版本管理的时候&#xff0c;当我们的项目开发到一定的阶段&#xff0c;需要发布一个版本。这时&#xff0c;我们就可以对…...

Python面向对象编程

文章目录1 作用域1.1 局部作用域2 类成员权限3 是否继承新式类4 多重继承5 虚拟子类6 内省【在运行时确定对象类型的能力】7 函数参数8 生成器1 作用域 1.1 局部作用域 1&#xff0c;当局部变量遮盖全局变量&#xff0c;使用globals()[变量名]来使用全局变量&#xff1b;2&am…...

【什么情况会导致 MySQL 索引失效?】

MySQL索引失效可能有多种原因&#xff0c;下面列举一些常见的情况&#xff1a; 数据库表数据量太小&#xff1a; 如果表的数据量非常小&#xff0c;则MySQL可能不会使用索引&#xff0c;因为它认为全表扫描的代价更小。 索引列上进行了函数操作&#xff1a; 如果在索引列上…...

Java核心知识点整理之小碎片--每天一点点(坚持呀)--自问自答系列版本

1.int和Integer Integer是int的包装类&#xff1b;int是基本数据类型。 Integer变量必须实例化后才能使用&#xff1b;int变量不需要。 Integer实际是对象的引用&#xff0c;当new一个Integer时&#xff0c;实际上是生成一个指针指向此对象&#xff1b;而int则是直接存储数据值…...

js中new Map()的使用方法

1.map的方法及属性Map对象存有键值对&#xff0c;其中的键可以是任何数据类型。Map对象记得键的原始插入顺序。Map对象具有表示映射大小的属性。1.1 基本的Map() 方法MethodDescriptionnew Map()创建新的 Map 对象。set()为 Map 对象中的键设置值。get()获取 Map 对象中键的值。…...

synchronized从入门到踹门

synchronized是什么synchronized是Java关键字&#xff0c;为了维护高并发是出现的原子性问题。技术是把双刃剑&#xff0c;多线程并发给我带来了前所未有的速率&#xff0c;然而在享受快速编程的过程&#xff0c;也给我们带来了原子性问题。如下&#xff1a;public class Main …...

ubuntu-8-安装nfs服务共享目录

Ubuntu最新版本(Ubuntu22.04LTS)安装nfs服务器及使用教程 ubuntu16.04挂载_如何在Ubuntu 20.04上设置NFS挂载 Ubuntu 20.04 设置时区、配置NTP同步 timesyncd 代替 ntpd 服务器 10.0.2.11 客户端 10.0.2.121 NFS简介 (1)什么是NFS NFS就是Network File System的缩写&#xf…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...