MyBatis源码分析の配置文件解析
文章目录
- 前言
- 一、SqlSessionFactoryBuilder
- 1.1、XMLConfigBuilder
- 1.2、parse
- 二、mappers标签的解析
- 2.1、cacheElement
- 2.1.1、缓存策略
- 2.2、buildStatementFromContext
- 2.2.1、sql的解析
前言
本篇主要介绍MyBatis源码中的配置文件解析部分。MyBatis是对于传统JDBC的封装,屏蔽了传统JDBC与数据库进行交互,组装参数,获取查询结果并自己封装成对象的繁琐过程。
原生MyBatis首先需要配置mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><properties resource="jdbc.properties"/><environments default="dev"><environment id="dev"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><mappers><mapper resource="mapper/UserMapper.xml"/></mappers>
</configuration>
并且指定数据源jdbc.properties:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
创建数据库访问层接口:
public interface UserMapper {List<User> selectAll();User selectById(int id);void insert(User user);void update(User user);void delete(int id);
}
以及对应的xml文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mybatis.mapper.UserMapper"><cache/><resultMap id="userResultMap" type="com.example.mybatis.entity.User"><id property="id" column="id"/><result property="name" column="name"/><result property="age" column="age"/></resultMap><select id="selectAll" resultMap="userResultMap">SELECT * FROM users</select><select id="selectById" resultMap="userResultMap" parameterType="int">SELECT * FROM users WHERE id = #{id}</select><insert id="insert" parameterType="com.example.mybatis.entity.User">INSERT INTO users (name, age) VALUES (#{name}, #{age})</insert><update id="update" parameterType="com.example.mybatis.entity.User">UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}</update><delete id="delete" parameterType="int">DELETE FROM users WHERE id = #{id}</delete>
</mapper>
mybatis-config.xml常见的标签:
| 标签 | 作用 |
|---|---|
<settings> | 控制 MyBatis 全局行为(缓存、懒加载、日志等) |
<typeAliases> | 设置类型别名,简化 Mapper XML 中类名书写 |
<typeHandlers> | 自定义类型转换器(Java类型 ↔ JDBC类型) |
<plugins> | 注册插件(如分页插件、SQL打印等) |
<objectFactory> | 自定义对象创建逻辑 |
<environments> | 配置数据库环境及事务管理 |
<mappers> | 注册 Mapper 映射文件或 Mapper 接口 |
原生MyBatis的使用,其中读取配置文件并进行解析,主要体现在SqlSessionFactoryBuilder的build方法中:
public class Main {public static void main(String[] args) throws Exception {//将xml构筑成configuration配置类Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//解析xml,注册成SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);try (SqlSession session = sqlSessionFactory.openSession()) {User user = session.selectOne("com.example.mybatis.mapper.UserMapper.selectById", 1);System.out.println(user);}}
}
一、SqlSessionFactoryBuilder
1.1、XMLConfigBuilder
在调用SqlSessionFactoryBuilder的build方法时,首先会去创建一个XMLConfigBuilder,目的是构建一个XML配置文件解析器对象。
其中的核心代码,这段代码的作用是注册别名,将配置文件中的 “JDBC”、"POOLED"等关键词和实际的类型进行绑定。

| 别名 | 实际类 | 用途 |
|---|---|---|
"JDBC" | JdbcTransactionFactory | JDBC事务管理器(默认事务方式) |
"MANAGED" | ManagedTransactionFactory | 受容器管理的事务(如 Spring) |
"JNDI" | JndiDataSourceFactory | 从 JNDI 获取数据源 |
"POOLED" | PooledDataSourceFactory | 数据库连接池(MyBatis 内置) |
"UNPOOLED" | UnpooledDataSourceFactory | 不使用连接池的数据源 |
"PERPETUAL" | PerpetualCache | 永久缓存 |
"FIFO" | FifoCache | 先进先出缓存 |
"LRU" | LruCache | 最近最少使用缓存 |
"SOFT" | SoftCache | 基于 SoftReference 的缓存 |
"WEAK" | WeakCache | 基于 WeakReference 的缓存 |
"DB_VENDOR" | VendorDatabaseIdProvider | 根据数据库类型自动切换 SQL |
"XML" | XMLLanguageDriver | MyBatis 默认的 XML SQL 语言驱动器 |
"RAW" | RawLanguageDriver | 原生 SQL 写法语言驱动器 |
"SLF4J" | Slf4jImpl | 使用 SLF4J 的日志输出 |
"COMMONS_LOGGING" | JakartaCommonsLoggingImpl | 使用 Commons Logging 日志 |
"LOG4J" | Log4jImpl | 使用 Log4j 日志 |
"LOG4J2" | Log4j2Impl | 使用 Log4j2 日志 |
"JDK_LOGGING" | Jdk14LoggingImpl | 使用 JDK 内建日志 |
"STDOUT_LOGGING" | StdOutImpl | 输出日志到控制台 |
"NO_LOGGING" | NoLoggingImpl | 不输出日志 |
"CGLIB" | CglibProxyFactory | 使用 CGLIB 动态代理 |
"JAVASSIST" | JavassistProxyFactory | 使用 Javassist 动态代理 |
1.2、parse
真正解析配置文件的是利用上一步构造出的XMLConfigBuilder的parse方法,首先会进行判断,如果已经解析过,则抛出异常,不会重复解析:
否则就将标记设置为true。并且执行parseConfiguration方法,从根节点进行解析:
每一行都对应了一个 <mybatis-config.xml> 中的标签,逐步填充 Configuration 对象内容:

/*** 解析 <configuration> 根节点的各个子标签,并将配置信息填充到 Configuration 对象中*/
private void parseConfiguration(XNode root) {try {// 【1】先解析 <properties> 标签(必须最优先解析),以便后续标签中的占位符 ${} 能被正确替换propertiesElement(root.evalNode("properties"));// 【2】解析 <settings> 标签,将其内容转换为 Properties 对象Properties settings = settingsAsProperties(root.evalNode("settings"));// 【3】解析 settings 中的 vfsImpl 属性(如果配置了自定义 VFS 实现类)loadCustomVfs(settings);// 【4】解析 settings 中的 logImpl 属性(设置日志实现类,如 LOG4J、STDOUT_LOGGING 等)loadCustomLogImpl(settings);// 【5】解析 <typeAliases> 标签,注册用户自定义的别名或包扫描别名typeAliasesElement(root.evalNode("typeAliases"));// 【6】解析 <plugins> 标签,注册 MyBatis 插件(如分页插件、SQL 拦截器等)pluginElement(root.evalNode("plugins"));// 【7】解析 <objectFactory> 标签,设置自定义对象工厂(用于实例化结果对象)objectFactoryElement(root.evalNode("objectFactory"));// 【8】解析 <objectWrapperFactory> 标签,自定义对象包装器(封装结果对象属性访问行为)objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));// 【9】解析 <reflectorFactory> 标签,自定义反射器工厂(高级反射行为控制)reflectorFactoryElement(root.evalNode("reflectorFactory"));// 【10】将 <settings> 中的配置项应用到 Configuration 对象中settingsElement(settings);// 【11】解析 <environments> 标签,注册事务管理器和数据源配置(必须在 objectFactory 之后执行)environmentsElement(root.evalNode("environments"));// 【12】解析 <databaseIdProvider> 标签,支持数据库厂商识别(如区分 MySQL、Oracle)databaseIdProviderElement(root.evalNode("databaseIdProvider"));// 【13】解析 <typeHandlers> 标签,注册自定义类型处理器(TypeHandler)typeHandlerElement(root.evalNode("typeHandlers"));// 【14】解析 <mappers> 标签,加载 Mapper 映射器(包括 XML 和接口方式)mapperElement(root.evalNode("mappers"));} catch (Exception e) {// 如果解析过程中发生异常,则封装为 BuilderException 抛出throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}
}
当解析完成后,会得到一个configuration对象,其中就包含了配置文件中的各种值。相当于此时的xml配置文件已经转化为了configuration对象。最后还会将其再次包装成SqlSessionFactory,后续会利用SqlSessionFactory进行sql相关逻辑的执行。
其中最关键的是mappers标签的解析。
二、mappers标签的解析
mapperElement方法,首先会拿到mappers根标签,然后进行解析。
/*** 解析 <mappers> 标签,支持三种加载方式:package、resource/url、class*/
private void mapperElement(XNode parent) throws Exception {if (parent != null) {// 遍历 <mappers> 下的所有子节点(可能是 <package> 或 <mapper>)for (XNode child : parent.getChildren()) {// 情况1:<package name="com.xxx.mapper"/>,批量注册包下所有 Mapper 接口if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");// 自动扫描指定包下的所有接口,并注册到 Configuration 中configuration.addMappers(mapperPackage);} else {// 情况2~4:单个 <mapper> 节点,通过 resource/url/class 指定加载方式String resource = child.getStringAttribute("resource"); // 从 classpath 中加载 Mapper XMLString url = child.getStringAttribute("url"); // 从网络路径加载 Mapper XMLString mapperClass = child.getStringAttribute("class"); // 直接加载 Mapper 接口类// 情况2:只指定 resource,加载 Mapper XML 文件if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource); // 设置错误上下文信息try (InputStream inputStream = Resources.getResourceAsStream(resource)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse(); // 解析 Mapper XML,注册语句映射}// 情况3:只指定 url,加载远程 Mapper XML 文件} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);try (InputStream inputStream = Resources.getUrlAsStream(url)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse(); // 同样调用解析逻辑}// 情况4:只指定 class,注册 Mapper 接口类(无 XML 时适用)} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface); // 注册接口类到 MapperRegistry// 情况5:配置冲突,三种方式只能选一种,否则抛异常} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}
}
案例中对应的是情况2,首先会注册一个mapper解析器,然后调用其parse方法对案例中UserMapper.xml进行解析,在该方法中,首先会进行判断,如果已经进行过解析,则不会重复解析。
解析的核心方法在于configurationElement,同样是对于xml中的各种标签再次分类解析:
这里重点看一下cacheElement以及buildStatementFromContext。
2.1、cacheElement
cacheElement和Mybatis的二级缓存有关。简单的说,Mybatis有两级缓存:
- 一级缓存是SqlSession 级别的,并且默认开启。
- 二级缓存是Mapper 映射级别,默认不开启,如果需要,应该在某个mapper.xml中使用cache标签开启。
cacheElement方法正是解析mapper.xml中的cache标签:
/*** 解析 <cache> 标签,构建二级缓存对象并注册到 Configuration 中。*/
private void cacheElement(XNode context) {// 1. 判断 <cache> 标签是否存在if (context != null) {// 2. 解析缓存类型(默认是 PERPETUAL,即 PerpetualCache)String type = context.getStringAttribute("type", "PERPETUAL");Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);// 3. 解析缓存淘汰策略(默认是 LRU,即最近最少使用)String eviction = context.getStringAttribute("eviction", "LRU");Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);// 4. 缓存刷新间隔(可选):指定自动清空缓存的时间(毫秒)Long flushInterval = context.getLongAttribute("flushInterval");// 5. 缓存大小(可选):最大缓存对象个数Integer size = context.getIntAttribute("size");// 6. 是否为读写缓存(readOnly=false 表示使用序列化;true 表示共享引用)// readWrite = true 表示开启对象副本,确保线程安全boolean readWrite = !context.getBooleanAttribute("readOnly", false);// 7. 是否阻塞:当缓存正在被其他线程刷新时,是否阻塞等待boolean blocking = context.getBooleanAttribute("blocking", false);// 8. 获取 <cache> 中配置的其他 <property> 子节点Properties props = context.getChildrenAsProperties();// 9. 构建缓存并注册到 Configuration,封装为 MapperBuilderAssistant.useNewCache()builderAssistant.useNewCache(typeClass, // 缓存类型类(如 PerpetualCache)evictionClass, // 淘汰策略类(如 LruCache)flushInterval, // 缓存刷新间隔size, // 缓存容量readWrite, // 是否使用读写模式blocking, // 是否阻塞模式props // 自定义属性);}
}
在useNewCache中,最终会调用CacheBuilder的build方法:
build方法中运用到了装饰器模式,所有的Cache都实现了一个共同的父类Cache。
在**cache = newCacheDecoratorInstance(decorator, cache);这一行代码中,传入LruCache和当前的Cache实例(PERPETUAL),将PERPETUAL包装到LRU中:(LruCache的delegate属性,指向的是传入的PerpetualCache实例)

然后继续执行到cache = setStandardDecorators(cache);**这一行代码,会继续进行装饰器的包装:
setStandardDecorators方法,对于Cache实例层层包装,赋值给各自的delegate属性:
包装完成的层次:SynchronizedCache线程同步缓存区->LoggingCache统计命中率以及打印日志->SerializedCache序列化->LruCache最少使用->PerpetualCache默认。

2.1.1、缓存策略
默认的PerpetualCache,使用的是HashMap进行存储。

而LruCache,为了实现最近最少使用的机制,使用了LinkedHashMap的数据结构,并且重写了它的removeEldestEntry方法,关键在于,LinkedHashMap构造时第三个参数为 true 表示按访问顺序排列:

LruCache cache = new LruCache(new PerpetualCache("myCache"));
cache.setSize(3);cache.put("A", 1); // A
cache.put("B", 2); // A B
cache.put("C", 3); // A B C
cache.get("A"); // B C A (A 被访问过,移到尾部)
cache.put("D", 4); // C A D(B 被淘汰)
SynchronizedCache,每个方法上通过加synchronized保证线程安全:
LoggingCache,会记录日志,以及统计缓存命中次数:

2.2、buildStatementFromContext
buildStatementFromContext是用来解析 select、insert、update、delete 标签中sql语句的方法,首先会解析出这些节点,然后进行循环,获取到XMLStatementBuilder后,执行parseStatementNode方法:
在parseStatementNode方法中有几个关键点,这一段代码会判断当前的标签是否为select,如果是select标签,则不会清除一级缓存(增删改会清除),以及判断是否使用二级缓存(默认 select 使用)

2.2.1、sql的解析
真正执行解析sql的是下图中的代码:
同样地会先去构建一个XMLScriptBuilder,然后调用其parseScriptNode方法进行解析:
在parseScriptNode方法中,首先会解析 SQL 标签中的所有子标签,然后去进行判断:
- 包含动态 SQL(即是否包含 if、choose、${} 等动态节点)构建 DynamicSqlSource(运行时动态拼接 SQL)
- 不包含动态 SQL(即是否包含 if、choose、${} 等动态节点)构建 RawSqlSource(直接编译成静态 SQL,提升效率)
MixedSqlNode对象,实现了SqlNode接口,SqlNode是所有动态 SQL节点的统一接口,而MixedSqlNode代表了 一整个 SQL 脚本块,比如select标签中所有内容就会变成一个 MixedSqlNode。
SqlNode 接口
│
├── MixedSqlNode // 组合节点
├── StaticTextSqlNode // 静态文本节点:普通 SQL 字符串
├── TextSqlNode // 动态文本节点:包含 ${}
├── IfSqlNode // if 标签
├── ChooseSqlNode // choose/when/otherwise
├── ForEachSqlNode // foreach
├── WhereSqlNode // where
├── TrimSqlNode // trim
├── SetSqlNode // set
└── BindSqlNode // bind
用一个案例说明,假如我在mapper.xml中定义了如下的sql语句:
<select id="findUser" parameterType="map" resultType="User">SELECT * FROM user<where><if test="name != null">AND name = #{name}</if><if test="age != null">AND age = #{age}</if></where>
</select>
则生成的结构如下:
MixedSqlNode
├── StaticTextSqlNode(“SELECT * FROM user”)
└── WhereSqlNode
└── MixedSqlNode
├── IfSqlNode(test=“name != null”) → TextSqlNode(“AND name = #{name}”)
└── IfSqlNode(test=“age != null”) → TextSqlNode(“AND age = #{age}”)
相关文章:
MyBatis源码分析の配置文件解析
文章目录 前言一、SqlSessionFactoryBuilder1.1、XMLConfigBuilder1.2、parse 二、mappers标签的解析2.1、cacheElement2.1.1、缓存策略 2.2、buildStatementFromContext2.2.1、sql的解析 前言 本篇主要介绍MyBatis源码中的配置文件解析部分。MyBatis是对于传统JDBC的封装&…...
python爬虫笔记(一)
文章目录 html基础标签和下划线无序列表和有序列表表格加边框 html的属性a标签(网站)target属性换行线和水平分割线 图片设置宽高width,height html区块——块元素与行内元素块元素与行内元素块元素举例行内元素举例 表单from标签type属性pla…...
docker后台运行,便于后期用命令行进入它的终端
在 docker compose up --build -d 命令中,**-d(或 --detach)参数的作用是让容器以后台模式(detached mode)**运行。以下是详细解释: **-d 参数的作用** 后台运行容器: 默认情况下&a…...
剑指 Offer II 087. 复原 IP
comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20087.%20%E5%A4%8D%E5%8E%9F%20IP/README.md 剑指 Offer II 087. 复原 IP 题目描述 给定一个只包含数字的字符串 s ,用以表示一个 IP 地址…...
DC-6靶机详解
一、主机发现 arp-scan -l靶机ip为192.168.55.159 二、端口扫描、目录枚举、指纹识别、 2.1端口扫描 nmap 192.168.55.159发现没有开放特殊端口 看来信息收集的重点要放在网页中了 2.2目录枚举 dirb http://192.168.55.1592.3指纹识别 nmap 192.168.55.159 -sV -sC -O …...
Java构造方法详解:从入门到实战
目录 一、什么是构造方法? 二、构造方法的作用 三、构造方法分类与使用 1. 默认构造方法 2. 有参构造方法 3. 构造方法重载 四、注意事项(避坑指南) 五、经典面试题解析 六、实战应用场景 七、总结 一、什么是构造方法? …...
STM32-SPI通信外设
目录 一:SPI外设简介 SPI框图编辑 SPI逻辑 编辑 主模式全双工连续传输 编辑 非连续传输 二:硬件SPI读写W25Q64 1.接线: 2. 代码 SPI外设的初始化 生成时序 一:SPI外设简介 STM32内部集成了硬件SPI收发电路&#…...
远程控制中的云电脑是什么意思?1分钟学会用
很多常用我们ToDesk远程控制的朋友们或许会注意到无论是在PC端还是移动端中都出现有【云电脑】【来云电脑爽玩-新用户免费1小时】这些词句等信息。那么这究竟是代表什么意思呐?云电脑是什么又怎么用呐?为什么要增加云电脑?以下小编就为大家科…...
【go】Go 语言中 errors.Is 和 errors.As 的区别
Go 语言中 errors.Is 和 errors.As 的区别 核心区别概述 errors.Is 和 errors.As 是 Go 1.13 引入的错误处理函数,它们有着不同的用途: errors.Is: 判断错误链中是否包含特定的错误值(错误相等性检查)errors.As: 尝试将错误转换…...
网络爬虫【简介】
我叫补三补四,很高兴见到大家,欢迎一起学习交流和进步 今天来讲一讲视图 一、网络爬虫的定义 网络爬虫(Web Crawler),又称为网络蜘蛛、网络机器人等,是一种按照一定规则自动抓取互联网信息的程序或脚本。它…...
JPA动态查询自定义排序规则
方法1:使用 CASE WHEN 语句排序 // 自定义排序逻辑 // 定义“离线”排在前,“在线”排在后 Expression<Object> caseExpression cb.selectCase().when(cb.equal(root.get("status"), "离线"), 0).when(cb.equal(root.get(…...
卫语句优化多层if else嵌套
一、卫语句的介绍 卫语句是一种编程实践,用于在函数或方法的开头快速处理不符合条件的情况,从而避免深层次的嵌套结构。它的核心思想是尽早返回,减少嵌套,使代码更加清晰易读。 二、卫语句的作用 提高可读性:卫语句将…...
2024华东师范大学计算机复试上机真题
2024华东师范大学计算机复试机试真题 2023华东师范大学计算机复试机试真题 2022华东师范大学计算机复试机试真题 2024华东师范大学计算机复试上机真题 2023华东师范大学计算机复试上机真题 2022华东师范大学计算机复试上机真题 在线评测:传动门:pgcode…...
3.15刷题
P6337 [COCI 2007/2008 #2] CRNE - 洛谷 #include<bits/stdc.h> using namespace std; int main(){int n;cin>>n;//横加竖 最大。n/2,n/21if(n%20){cout<<(n/21)*(n/21);}else cout<<(n/22)*(n/21);return 0; }P6338 [COCI 2007/2008 #2] PRVA - 洛…...
14.使用各种读写包操作 Excel 文件:辅助模块
一 各种读写包 这些是 pandas 在底层使用的各种读写包。无须安装 pandas,直接使用这些读写包就能够读写 Excel 工作簿。可以尽可能地使用 pandas 来解决这类问题,只在 pandas 没有提供你所需要的功能时才用到读写包。 表中没有 xlwings ,因为…...
设计心得——多态
一、设计上的多态 无论是在网上还是书籍上,还是自己的文章里都反复分析过多态的原理、应用和各种常见的情况。本篇重点从设计的角度来阐述一下多态,而不对多态的具体的用法进行说明。在前面的知识学习中可以知道,多态可以分为动多态和靜多态…...
【DeepSeek】本地部署DeepSeek的完整教程(Ollama+Docker+Open WebUI)
本地部署DeepSeek的完整教程 文章目录 本地部署DeepSeek的完整教程写在前面技术需求详细步骤一. 安装Ollama软件二. 安装DeepSeek-R1模型三. 安装Docker软件四. 配置Web UI界面问题解决1. 打开`docker desktop`时,一直显示`Docker Engine stopped`2. 用`Docker`拉取`Open WebU…...
Python数据分析之数据可视化
Python 数据分析重点知识点 本系列不同其他的知识点讲解,力求通过例子让新同学学习用法,帮助老同学快速回忆知识点 可视化系列: Python基础数据分析工具数据处理与分析数据可视化机器学习基础 四、数据可视化 图表类型与选择 根据数据特…...
1、操作系统引论
一、操作系统 会使用linux系统 建议大家先学会linux的基础指令,可以看菜鸟教程网站进行学习。 1、各种定义 操作系统定义 管理计算机的 硬件 和软件资源, 能对各类作业进行调度,方便用户使用计算机的程序集合。操作系统运行在内核态…...
DeepSeek 本地化新篇章:Ollama 兼容 OpenAI API 的深度解析与部署实践
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着大语言模型(LLM)的快速发展,开发者对本地化部署和 API 兼容性的需求日益增加。Ollama 作为一个轻量级开源框架,通过兼容 OpenAI AP…...
【PTA题目解答】7-4 数气球 (20分)
1.题目 天空上有n个气球,第i个气球的颜色为colori(为全由小写字母组成的字符串) 请你数出每种颜色的气球的数量,并按照颜色出现的先后顺序进行排序输出。 输入格式: 测试数据有T组(1≤T≤100)。 对于每组样例,第一…...
Swift 中 associatedtype 的用法详解
目录 前言 1.什么是associatedtype 2.associatedtype 的作用 1.让协议支持泛型 2.让协议支持不同的数据类型 3.结合 where 关键字限制类型 4.什么时候使用 associatedtype 5.总结 前言 在 Swift 语言中,泛型(Generics)是一个非常强大…...
java泛型通配符?及上下界(extends,super)保证安全性、灵活性、可读性
在 Java 中,泛型通配符(?)用于表示未知类型,通常用于增强泛型的灵活性。通配符可以与上下限结合使用,以限制泛型的范围。以下是通配符及上下限的使用示例: 1. 无界通配符 (?) 无界通配符表示可以接受任意…...
HarmonyOS NEXT - 网络请求问题(http)
HTTP(HyperText Transfer Protocal,超文本传输协议)是一种用于传输超媒体文档(如HTML)的应用层协议,它是客户端和服务器之间通信的基础;无论是获取数据、提交表单、上传文件,HTTP都扮…...
告别旧版本,功能全面升级!
小伙伴们,今天来给大家唠唠一款超经典的软件——格式工厂!相信很多人都不陌生吧?它可是早期超多人用的视频格式转换工具呢!但随着软件行业的发展,它慢慢被其他工具代替了,像万兴、小丸、AME这些新宠儿一出现…...
WSL2 Ubuntu安装GCC不同版本
WSL2 Ubuntu安装GCC不同版本 介绍安装gcc 7.1方法 1:通过源码编译安装 GCC 7.1步骤 1:安装编译依赖步骤 2:下载 GCC 7.1 源码步骤 3:配置和编译步骤 4:配置环境变量步骤 5:验证安装 方法 2:通过…...
双目视觉小记
双目视觉及其数学原理 1. 双目视觉概述 双目视觉(Stereo Vision)是一种模仿人眼视觉系统的计算机视觉技术,利用两台摄像机从不同角度拍摄同一场景,通过图像处理和三维重建来获取场景的深度信息。双目视觉广泛应用于机器人、自动…...
Obsidian Copilot:打造你的专属 AI 笔记助手
Obsidian Copilot作为一款非常受欢迎的Obsidian插件,不仅极大地提升了用户的笔记管理和信息检索效率,还通过其多样化的AI功能为用户带来了前所未有的便捷体验。本文将详细介绍Obsidian Copilot的核心特点、使用方法及个人体验分享。 核心特点 Obsidian…...
VPC4-通达oa-docker逃逸-shiro反序列化-hash传递-CrackMapExec喷射-历史ptt攻击-进程注入
由于本人是菜鸡,不会免杀,所有免杀的部分就直接跳过了 (hhh) 靶场地址: 链接: https://pan.baidu.com/s/1Fh1Zg79n1yjCPe6rrQ2apA 提取码: qiag 第一台ubuntu(docker逃逸,shiro反序列化) fscan扫到一…...
稳定运行的以Oracle数据库为数据源和目标的ETL性能变差时提高性能方法和步骤
要提高以 Oracle 为数据源和目标的ETL性能,需要综合考虑数据库性能优化、ETL工具配置、查询优化、并行处理和资源管理等多个方面。优化过程中要根据具体的ETL场景和工具来选择适合的方案,同时建议进行逐步调优,测试不同方案的效果,…...
