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

Mr. Cappuccino的第53杯咖啡——Mybatis源码分析

Mybatis源码分析

    • Mybatis源码分析入口
    • 1. 读取配置文件
      • 总结
    • 2. 解析配置文件
      • 核心代码(一)
      • 核心代码(二)
        • 分析parse()方法
        • 分析build()方法
      • 总结
    • 3. 获取SqlSession
      • 总结
    • 4. 获取mapper代理对象
      • 总结
    • 5. 使用mapper代理对象执行Sql语句
      • 二级缓存
      • 一级缓存
      • 总结

Mybatis源码分析入口

本文将根据下面这段代码进行源码分析

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<UserEntity> list = mapper.listUser();
System.out.println(list);
sqlSession.close();

1. 读取配置文件

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

ClassLoaderWrapper.java

在这里插入图片描述


总结

从入口一路点击进去可以发现底层是通过调用java.lang.ClassLoader#getResourceAsStream方法来读取resources目录下的mybatis-config.xml文件,并得到InputStream对象

2. 解析配置文件

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSessionFactoryBuilder.java

在这里插入图片描述

在这里插入图片描述


核心代码(一)

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

XMLConfigBuilder.java

在这里插入图片描述

XPathParser.java

在这里插入图片描述

可以发现底层是将InputStream对象转换成Document对象,并将Document对象保存至当前类(XPathParser)的document属性中

继续回到上一层,点击进入this()方法

XMLConfigBuilder.java

在这里插入图片描述

可以发现this()方法主要是在进行部分属性的初始化,并将XPathParser对象保存至当前类(XMLConfigBuilder)的parser属性中。
关键点:初始化了父类的configuration属性。


核心代码(二)

return build(parser.parse());

分析parse()方法


XMLConfigBuilder.java

在这里插入图片描述

  1. 根据Document对象获取节点为configuration的配置信息,并转换成XNode对象

XPathParser.java

在这里插入图片描述

  1. 将XNode对象解析成Configuration对象

XMLConfigBuilder.java

在这里插入图片描述

XMLConfigBuilder.java

在这里插入图片描述


点击进入addMappers(mapperPackage)方法


Configuration.java

在这里插入图片描述

MapperRegistry.java

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


在这里插入图片描述

从这里可以发现parser.parse()主要是在解析配置文件(mybatis-config.xml),具体过程是根据Document对象获取节点为configuration的配置信息,并转换成XNode对象再解析各个节点,重点部分是mappers节点的解析。
在解析mappers节点的代码中可以发现如果是使用package或class注册mapper可以直接注册mapper接口对象,如果是使用url或者resource注册mapper则需要先解析mapper.xml映射文件后并通过namespace找到所绑定的接口对象再进行注册。
mapper的注册是通过MapperRegistry对象完成的,而MapperRegistry则是Configuration对象里面的一个属性,也就是说所有的配置解析完成后都存放在Configuration对象中。
parser.parse()最终返回Configuration对象。

分析build()方法


SqlSessionFactoryBuilder.java

在这里插入图片描述

DefaultSqlSessionFactory.java

在这里插入图片描述

从这里可以发现SqlSessionFactoryBuilder将得到的Configuration对象建造成DefaultSqlSessionFactory对象,也就是SqlSessionFactory对象。


总结

SqlSessionFactoryBuilder先是通过XMLConfigBuilder解析配置文件并将解析得到的配置装载到Configuration对象中,再将Configuration建造成DefaultSqlSessionFactory对象。

这里采用了建造者设计模式
BaseBuilder:所有解析器的父类,包含配置文件实例,为解析文件提供一些通用的方法;
XMLConfigBuilder:主要负责解析mybatis-config.xml文件;
XMLMapperBuilder:主要负责解析mapper.xml文件;
XMLStatementBuilder:主要负责解析映射文件中的SQL节点;

Configuration对象核心属性释义:

  1. MapperRegistry:mapper接口动态代理工厂类的注册中心;
  2. ResultMap:用于解析mapper.xml文件中的resultMap节点,使用ResultMapping来封装id、result等子元素;
  3. MappedStatement:用于存储mapper.xml文件中的select、insert、update和delete节点,同时还包含了这些节点的很多重要属性;
  4. SqlSource:用于创建BoundSql,mapper.xml文件中的sql语句会被解析成BoundSql对象,经过解析BoundSql包含的语句最终仅仅包含?占位符,可以直接提交给数据库执行;

3. 获取SqlSession

SqlSession sqlSession = sqlSessionFactory.openSession();

DefaultSqlSessionFactory.java

在这里插入图片描述

Configuration.java

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

从这里可以看到如果没有设置执行器类型,则会默认使用简单执行器类型

ExecutorType.java

在这里插入图片描述

上面枚举类中的三种执行器类型均可通过openSession()传参设置


点击进入openSessionFromDataSource()方法


DefaultSqlSessionFactory.java

在这里插入图片描述

openSessionFromDataSource()方法有三个入参:ExecutorType execType(执行器类型)、TransactionIsolationLevel level(事务隔离级别)、boolean autoCommit(是否自动提交)

  1. 获取TransactionFactory

DefaultSqlSessionFactory.java

在这里插入图片描述

TransactionFactory有两种:JdbcTransactionFactory,ManagedTransactionFactory
通过mybatis-config.xml文件进行配置

在这里插入图片描述

<transactionManager type="JDBC"/>

这里配置的是JdbcTransactionFactory

  1. 获取Transaction

JdbcTransactionFactory.java

在这里插入图片描述

JdbcTransaction.java

在这里插入图片描述

  1. 根据Transaction和执行器类型获取执行器(核心代码)

Configuration.java

在这里插入图片描述

CachingExecutor.java

在这里插入图片描述

Mybatis默认使用的执行器是SimpleExecutor,SimpleExecutor的父类是BaseExecutor,BaseExecutor下一共有三个子类也就是三种执行器:BatchExecutor、SimpleExecutor、ReuseExecutor,这三种执行器均可通过传值设置。

cacheEnabled默认值为true,说明Mybatis默认会使用CachingExecutor。进入CachingExecutor类可以发现,CachingExecutor是在上面三种执行器(BaseExecutor)的基础上做了一层包装(装饰器设计模式),先调用CachingExecutor再调用BaseExecutor,是对BaseExecutor类的增强。

cacheEnabled可以通过mybatis-config.xml文件进行配置

<settings><!-- 是否开启二级缓存 --><setting name="cacheEnabled" value="false"/>
</settings>

BaseExecutor是一级缓存(默认开启),默认使用SimpleExecutor,CachingExecutor是二级缓存(默认开启,但还需要做一些额外的配置才能生效)

  1. 生成DefaultSqlSession

在这里插入图片描述

将Configuration、Executor、autoCommit等信息包装成DefaultSqlSession对象,并且返回该对象


总结

openSession()是SqlSessionFactory接口中的一个重载方法,可以配置执行器类型、事务隔离级别、是否自动提交等参数,Configuration负责判断当前使用的执行器(Executor),DefaultSqlSessionFactory最后将Configuration、Executor、autoCommit等信息包装成DefaultSqlSession对象并返回。

这里采用了装饰器设计模式

BaseExecutor是一级缓存(默认开启),BaseExecutor是BatchExecutor、SimpleExecutor、ReuseExecutor三种执行器的父类。

  1. SimpleExecutor:默认的Executor,每个SQL执行的时候都会创建新的Statement;
  2. ReuseExecutor:相同的SQL会重复使用Statement;
  3. BatchExecutor:用于批处理的Executor;

CachingExecutor是二级缓存(默认开启,但还需要做一些额外的配置才能生效)
CachingExecutor:可缓存数据的Executor,用装饰器模式包装了其它的执行器(如BaseExecutor下的三种执行器)

4. 获取mapper代理对象

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

DefaultSqlSession.java

在这里插入图片描述

Configuration.java

在这里插入图片描述

MapperRegistry.java

在这里插入图片描述

之前已经对mapper接口进行了注册,这里通过mapper接口类型获取对应的动态代理工厂类(MapperProxyFactory),动态代理工厂类使用JDK动态代理技术生成mapper代理对象并返回该对象。

MapperProxyFactory.java

在这里插入图片描述

MapperProxy.java

在这里插入图片描述

JDK动态代理技术主要用于拦截和修改方法的调用,在使用mapper代理对象调用mapper接口中的方法时MapperProxy中的invoke方法也会被执行。

总结

根据mapper接口类型从MapperRegistry中获取对应的动态代理工厂类(MapperProxyFactory),动态代理工厂类使用JDK动态代理技术生成mapper代理对象并返回该对象。在使用mapper代理对象调用方法时底层会走MapperProxy中的invoke方法。

这里采用了JDK动态代理设计模式

MapperRegistry:mapper接口动态代理工厂类的注册中心;
MapperProxyFactory:用于生成动态代理的实例对象;
MapperProxy:动态代理回调类;

5. 使用mapper代理对象执行Sql语句

List<UserEntity> list = mapper.listUser();

在这里插入图片描述

在这里插入图片描述


MapperProxy.java

在这里插入图片描述

在这里插入图片描述

核心代码

mapperMethod.execute(sqlSession, args);

MapperMethod.java

在这里插入图片描述

因为执行的SQL为select,返回值类型为List集合,所以会走executeForMany()方法

在这里插入图片描述

DefaultSqlSession.java

在这里插入图片描述

这个方法是不是很熟悉,没错,这就是在基于XML方式-原生方式开发用到的方法

List<UserEntity> list = sqlSession.selectList("com.mybatis.mapper.UserMapper.listUser", UserEntity.class);

DefaultSqlSession.java

在这里插入图片描述

在这里插入图片描述

二级缓存

如果开启了二级缓存则会使用CachingExecutor

CachingExecutor.java

在这里插入图片描述

  1. 获取SQL语句

在这里插入图片描述

  1. 创建缓存key

在这里插入图片描述

  1. 执行查询逻辑

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

SimpleExecutor中没有query方法,默认走父类(BaseExecutor)


一级缓存

BaseExecutor.java

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

SimpleExecutor.java

在这里插入图片描述

  1. 初始化RoutingStatementHandler对象

Configuration.java

在这里插入图片描述

RoutingStatementHandler.java

在这里插入图片描述

  1. 生成Statement对象

SimpleExecutor.java

在这里插入图片描述

2.1. 获取Connection

BaseExecutor.java

在这里插入图片描述

JdbcTransaction.java

在这里插入图片描述

2.2. 根据不同的StatementHandler创建Statement对象

RoutingStatementHandler.java

在这里插入图片描述

BaseStatementHandler.java

在这里插入图片描述

Mybatis默认采用PreparedStatementHandler处理器

PreparedStatementHandler.java

在这里插入图片描述

2.3. 使用ParameterHandler处理占位符参数

RoutingStatementHandler.java

在这里插入图片描述

PreparedStatementHandler.java

在这里插入图片描述

DefaultParameterHandler.java

在这里插入图片描述

  1. 执行查询逻辑

RoutingStatementHandler.java

在这里插入图片描述

PreparedStatementHandler.java

在这里插入图片描述

DefaultResultSetHandler.java

在这里插入图片描述


总结

在使用代理对象调用方法时,底层会走MapperProxy中的invoke方法,在执行查询语句时,默认会先从二级缓存(CachingExecutor)中读取数据,如果存在则直接返回,不存在则继续查询一级缓存,如果一级缓存(BaseExecutor)中存在则直接返回,不存在则继续查询数据库,在查询数据库时,总体上使用StatementHandler对象和JDBC进行交互,整个查询流程先是使用ParameterHandler对SQL语句的入参进行处理,待SQL语句被执行完后得到结果集,再使用ResultSetHandler对结果集进行处理并返回。

四大核心接口对象

  1. Executor(执行器):负责整个SQL执行过程的总体控制;
  2. StatementHandler(语句处理器):负责和JDBC层的具体交互;
  3. ParameterHandler(参数处理器):负责PreparedStatement入参的具体设置;
  4. ResultSetHandler(结果集处理器):负责将JDBC查询的结果映射为Java对象;

StatementHandler

  1. RoutingStatementHandler:根据StatementType路由到不同的StatementHandler对象;
  2. SimpleStatementHandler:管理Statement对象并向数据库中推送不需要预编译的SQL语句;
  3. PreparedStatementHandler:管理Statement对象并向数据库中推送需要预编译的SQL语句;
  4. CallableStatementHandler:管理Statement对象并调用数据库中的存储过程;

相关文章:

Mr. Cappuccino的第53杯咖啡——Mybatis源码分析

Mybatis源码分析 Mybatis源码分析入口1. 读取配置文件总结 2. 解析配置文件核心代码&#xff08;一&#xff09;核心代码&#xff08;二&#xff09;分析parse()方法分析build()方法 总结 3. 获取SqlSession总结 4. 获取mapper代理对象总结 5. 使用mapper代理对象执行Sql语句二…...

修改文件格式(查看文件拓展名)

很多时候我们直接把txt文件重命名为xxx.c或者别的文件格式&#xff0c;文件类型依然会是txt&#xff0c;文件名并不会变成我们想要的xxx.c&#xff0c;而是xxx.c.txt&#xff0c;也就是下面这个样子 给大家介绍2种方法去解决这个问题 目录 1.另存为新格式 2.显示文件拓展名 1…...

利用鸿鹄可观测性监控Istio Ingress网关

一、需求描述 在上一篇《利用Vector和鸿鹄搭建微服务应用的可观测性平台》中&#xff0c;阐述了微服务的基本概念、优点及如何利用鸿鹄来处理分布式应用的日志。本文将进一步讨论微服务架构面临的问题、服务网格及鸿鹄处理Istio Gateway的独特优势。 1.1 微服务架构面临的挑战 …...

vscode 前端开发插件 2023

自己记录 安装vscode后必装插件 chinesegit 必装没啥可说 随时更新 1.CSS Navigation CTRL点击类名可跳转到对应样式位置。 如果是scss less的话。css peak插件无法生效 2.GitLens — Git supercharged 可以看到每一行的git提交记录。 3.Auto Rename Tag 可以同步更新…...

使用docker部署Wordpress

文章目录 1.创建网络2.创建volume存储3.拉取镜像4.创建mysql容器mysql修改密码 5.创建wordpress容器6.访问localhost:80就可以直接使用啦 1.创建网络 docker network create --subnet172.18.0.0/24 pro-net2.创建volume存储 # mysql 存储 docker volume create volume_mysql…...

7.31黄金最新行情走势分析及多空交易策略

近期有哪些消息面影响黄金走势&#xff1f;黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;上周有重磅数据美联储加息的消息&#xff0c;黄金受其影响波动比较频繁&#xff0c;总体空间40美金。但这个过程跌宕起伏。收线来看黄金在连续上涨三周后迎来一根小阴十…...

Spring框架——AOP注解方式

目录 Spring框架的AOP技术&#xff08;注解方式&#xff09; 通知类型 Spring框架的AOP技术&#xff08;注解方式&#xff09; 1. 步骤一&#xff1a;创建JavaWEB项目&#xff0c;引入具体的开发的jar包* 先引入Spring框架开发的基本开发包com.springsource.org.apache.commo…...

Java 日志(Logging)如何创建和捕获日志消息和文件

Java允许我们通过日志记录过程来创建和捕获日志消息和文件。 在Java中&#xff0c;日志记录需要框架和API。Java在java.util.logging程序包中具有内置的日志记录框架。 Java 日志组件 下图显示了Java Logging API&#xff08;java.util.logging&#xff09;的核心组件和指定…...

em3288 linux_4.19 lvds+tp调试

一、显示配置\rk3288_linux4.19\kernel\arch\arm\boot\dts\rk3288-evb-act8846.dtspanel {compatible "simple-panel";backlight <&backlight>;bus-format <MEDIA_BUS_FMT_RGB666_1X18>;enable-gpios <&gpio1 24 GPIO_ACTIVE_HIGH>;ena…...

Linux 之 systemctl

systemctl 可以控制软件&#xff08;一般指服务&#xff09;的启动、关闭、开机自启动 能被systemctl 管理的软件&#xff0c;一般也称 服务 系统内置服务均可被 systemctl 控制第三方软件&#xff0c;如果 自动注册了 可被systemctl 控制第三方软件&#xff0c;如果没有自动…...

【技巧】通过 CMD 走代理下载 Vue

通过 CMD 走代理下载 Vue 在学习或者工作中&#xff0c;有时上网走的是代理模式&#xff0c;就是在浏览器里面配置代理服务的那种。后来在下载 Vue 组件的时候显示请求超时。此时才发先&#xff0c;浏览器代理只能在浏览器里生效&#xff0c;cmd 中不生效&#xff0c;那该怎么办…...

VSCode C/C++多文件编译配置

多文件编译备忘&#xff0c;带注释的地方都需要注意&#xff01;&#xff01;&#xff01; launch.json文件 {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: https://go.microsoft.com/fwlink/?linkid830387&quo…...

Autosar通信入门系列05-聊聊一帧Can/CanFD报文发送时间?

本文框架 1. 概述2. 一帧CAN报文发送时间计算3. 一帧CanFD报文的传输时间计算3.1 标准CAN与CANFD两者间的区别3.2 CANFD报文传输时间计算 1. 概述 本篇我们一起看下一帧Can报文发送需要多长时间&#xff0c;下述文章里我们会首先计算下Can分别对应的字节数&#xff0c;再根据传…...

【phaser微信抖音小游戏开发002】hello world!

执行效果&#xff1a; 将以下代码文本内容&#xff0c;放入到game.js中即可。目录结构如下图 import ./js/libs/weapp-adapter import ./js/libs/symbolGameGlobal.window.scrollTo () > { };//防止真机出错 import Phaser from ./js/phaser//引入Phaservar {windowWidth, …...

2023.07.29 驱动开发DAY6

通过epoll实现一个并发服务器 服务器 #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h…...

网工必须掌握的5种组网技术,你会了吗?

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 作者会持续更新网络知识和python基础知识&#xff0c;期待你的关注 目录 一、VLAN技术 1、VLAN是什么&#xff1f; 2、VLAN的作用 ①提高网络安全性 ②提高了网络的灵活性性 ③增强了网络的健壮性 二、D…...

webpack中文文档

基本安装 首先我们创建一个目录&#xff0c;初始化 npm&#xff0c;然后 在本地安装 webpack&#xff0c;接着安装 webpack-cli&#xff08;此工具用于在命令行中运行 webpack&#xff09;&#xff1a; mkdir webpack-demo cd webpack-demo npm init -y npm install webpack …...

【Linux指令篇】--- Linux常用指令汇总(克服指令繁杂问题)

文章目录 前言&#x1f31f;一、Linux基本指令&#x1f31f;二、ls指令&#x1f30f;2.1.语法&#xff1a;&#x1f30f;2.2.功能&#xff1a;&#x1f30f;2.3.常用选项&#xff1a; &#x1f31f;三、pwd指令&#x1f30f;3.1.语法&#xff1a;&#x1f30f;3.2.功能&#xf…...

硬盘的分类

目前常见的硬盘种类主要有以下2种&#xff1a; 机械硬盘&#xff08;HDD&#xff09; 机械硬盘&#xff08;HDD&#xff09;是一种利用旋转磁盘和读写头来存储和访问数据的存储设备。它由磁盘、读写头、电机和控制电路等组成&#xff0c;磁盘通常是一种铝合金或玻璃材质的圆盘&…...

el-upload批量手动上传,并用form表单校验上传文件

手动上传设置:auto-upload"false" <el-formref"formData"class"formWidth":model"formData"label-width"120px":rules"rules"><el-form-itemlabel"数据"class"uploadClass"required…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

关于uniapp展示PDF的解决方案

在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项&#xff1a; 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库&#xff1a; npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...

Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解

文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一&#xff1a;HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二&#xff1a;Floyd 快慢指针法&#xff08;…...