MyBatis 源码分析--SqlSessionFactory

前言:

前文我们简单的回顾了 MyBatis 的基本概念,有聊到核心组件,工作流程等,本篇我们开始深入剖析 MyBatis 的核心源码,欢迎大家持续关注。

Mybatis 知识传送门

初识 MyBatis 【MyBatis 核心概念】

MyBatis 源码解析

为了方便我们分析 MyBatis 源码,我们先写一段简单的小程序,来辅助我们分析 MyBatis 源码,如下:

public class MyBatisTest {@Testpublic void test() throws IOException {//读取配置文件InputStream is = Resources.getResourceAsStream("mybatis-config.xml");//创建 SqlSessionFactoryBuilder 对象SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();//通过 SqlSessionBuilder 对象 解析 mybatis-config.xml 文件 构建一个SqlSessionFactory SqlSessionFactory sqlSessionFactory = builder.build(is);//通过SqlSessionFactory构建一个SqlSessionSqlSession session = sqlSessionFactory.openSession();//通过SqlSession 获取 Mapper 实例UserMapper userMapper = session.getMapper(UserMapper.class);//获取数据List<User> users = userMapper.findAll();//打印输出for (User user : users) {System.out.println(user);}//关闭资源session.close();is.close();}
}

在分析创建 SqlSessionFactory 源码之前,先分享一个代码流程简图,帮助梳理源码脉络。

在这里插入图片描述

获取 SqlSessionFactory 源码分析

SqlSessionFactory 工厂是通过 SqlSessionFactoryBuilder#build 方法获取的,build 方法会先创建一个 XMLConfigBuilder ,XMLConfigBuilder 会去解析 XML 文件,生成一个 SqlSessionFactory 方返回,我们重点关注 var5 = this.build(parser.parse()) 这行代码。

//org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.Reader, java.lang.String, java.util.Properties)
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {//SqlSessionFactorySqlSessionFactory var5;try {//根据入参创建 xml 配置生成器   XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);//parser.parse() :解析 xml 文件  重点关注//创建 SqlSessionFactory 并返回var5 = this.build(parser.parse());} catch (Exception var14) {throw ExceptionFactory.wrapException("Error building SqlSession.", var14);} finally {ErrorContext.instance().reset();try {reader.close();} catch (IOException var13) {}}return var5;
}

XMLConfigBuilder#parse 方法源码分析

XMLConfigBuilder#parse 方法会先判断当前 XML 文件是否解析过,如果解析过就抛出异常,没有解析过就正常开始解析文件 。

//org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
public Configuration parse() {//判断是否被解析过if (this.parsed) {//解析过再次被解析就要抛出异常了throw new BuilderException("Each XMLConfigBuilder can only be used once.");} else {//标记为已经解析this.parsed = true;//解析配置 xml 文件  重点关注this.parseConfiguration(this.parser.evalNode("/configuration"));//返回 Configurationreturn this.configuration;}
}

XMLConfigBuilder#parseConfiguration 方法源码分析

XMLConfigBuilder#parseConfiguration 方法会解析 XML 文件的各种属性,最后会去加载 Mapper 文件,也就是我们写的 SQL 文件。

//org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {try {//解析 properties 节点this.propertiesElement(root.evalNode("properties"));//解析 Setting 节点Properties settings = this.settingsAsProperties(root.evalNode("settings"));//加载自定义的 VFS  VFS 主要用来加载容器内的各种资源 例如 jar 或者 class文件this.loadCustomVfs(settings);//加载自定义的日志实现this.loadCustomLogImpl(settings);//解析 typeAliases this.typeAliasesElement(root.evalNode("typeAliases"));//加载 插件 this.pluginElement(root.evalNode("plugins"));//加载对象工厂this.objectFactoryElement(root.evalNode("objectFactory"));//加载对象包装工厂this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));//加载反射工厂this.reflectorFactoryElement(root.evalNode("reflectorFactory"));//把 settings 赋值给 confiurationthis.settingsElement(settings);//加载环境配置this.environmentsElement(root.evalNode("environments"));//加载数据库标识this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));//加载 typeHandlerthis.typeHandlerElement(root.evalNode("typeHandlers"));//加载 mapper 文件 也就是我们写的 sql 文件  重点关注this.mapperElement(root.evalNode("mappers"));} catch (Exception var3) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);}
}

XMLConfigBuilder#mapperElement 方法源码分析

XMLConfigBuilder#mapperElement 方法会解析 SQL 文件的配置方式,在 Mappers 下 Mapper 文件有四种配置方式,分别是 package、resource、url、class,mapperElement 方法对其进行了区分处理,我们重点关注 mapperParser.parse() 方法。

//org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {if (parent != null) {//获取 mappers 的子节点 也就是 mapper 节点 迭代遍历Iterator var2 = parent.getChildren().iterator();while(true) {while(var2.hasNext()) {//Mappers 的子节点  只能是 package 节点 和 mapper 节点XNode child = (XNode)var2.next();String resource;//是否是 packge 节点if ("package".equals(child.getName())) {//获取需要扫描的包路径 resource = child.getStringAttribute("name");//加入到 configuration 中this.configuration.addMappers(resource);} else {//获取 resource、url、class这三个属性  只能有一个生效//获取 resource resource = child.getStringAttribute("resource");//获取 url  String url = child.getStringAttribute("url");//获取 mapperClassString mapperClass = child.getStringAttribute("class");//xml 解析器XMLMapperBuilder mapperParser;InputStream inputStream;if (resource != null && url == null && mapperClass == null) {//解析 resource 类路径ErrorContext.instance().resource(resource);inputStream = Resources.getResourceAsStream(resource);mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {//解析 url 绝对url路径ErrorContext.instance().resource(url);inputStream = Resources.getUrlAsStream(url);mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());mapperParser.parse();} else {if (resource != null || url != null || mapperClass == null) {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}//解析 mapperclass  类名Class<?> mapperInterface = Resources.classForName(mapperClass);//加入到 configuration 中this.configuration.addMapper(mapperInterface);}}}return;}}
}

Configuration#addMappers 方法源码分析

addMappers 方法会把包下面的所有 Mapper 解析出来,加入到一个 knownMappers 的 HashMap中。

//org.apache.ibatis.session.Configuration#addMappers
public void addMappers(String packageName) {this.mapperRegistry.addMappers(packageName);
}//org.apache.ibatis.binding.MapperRegistry#addMappers(java.lang.String)
public void addMappers(String packageName) {this.addMappers(packageName, Object.class);
}//org.apache.ibatis.binding.MapperRegistry#addMappers
public void addMappers(String packageName, Class<?> superType) {//处理 package 下的 所有 mapper ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();resolverUtil.find(new IsA(superType), packageName);Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();Iterator var5 = mapperSet.iterator();//循环遍历每个 Mapperwhile(var5.hasNext()) {Class<?> mapperClass = (Class)var5.next();//加入到 knownMappers Map 中this.addMapper(mapperClass);}}//org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {if (type.isInterface()) {//判断 Mapper 是否存在if (this.hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {//将 Mapper 的 Class 做为 key value 为 代理对象工厂this.knownMappers.put(type, new MapperProxyFactory(type));//解析注解方式的 MapperMapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {//失败 从 Mapper 中移出this.knownMappers.remove(type);}}}}

XMLMapperBuilder#parse 方法源码分析

XMLMapperBuilder#parse 方法主要作用就是解析 Mapper 文件,加载 Resouce,创建 MapperProxyFactory,我们重点分析解析 Mapper 标签部分。

//org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {//是否加载过if (!this.configuration.isResourceLoaded(this.resource)) {//解析 mapper 标签this.configurationElement(this.parser.evalNode("/mapper"));//加载 resourcethis.configuration.addLoadedResource(this.resource);//为名称空间绑定映射器 其实就是创建 MapperProxyFactorythis.bindMapperForNamespace();}//结果集this.parsePendingResultMaps();//缓存this.parsePendingCacheRefs();//待处理的sqlthis.parsePendingStatements();
}

XMLMapperBuilder#configurationElement 方法源码分析

XMLMapperBuilder#configurationElement 方法已经开始解析 Mapper 文件了,会对 Mapper 文件中的一些标签进行解析,并去构建 SQL 语句,我们重点关注 this.buildStatementFromContext(context.evalNodes(“select|insert|update|delete”))。

//org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {try {//获取 namespaceString namespace = context.getStringAttribute("namespace");if (namespace != null && !namespace.isEmpty()) {//设置 namespacethis.builderAssistant.setCurrentNamespace(namespace);//解析 cache-ref 标签this.cacheRefElement(context.evalNode("cache-ref"));//解析 cache 标签this.cacheElement(context.evalNode("cache"));//解析映射参数 parameterMap this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));//解析结果集 resultMapthis.resultMapElements(context.evalNodes("/mapper/resultMap"));//解析 sqlthis.sqlElement(context.evalNodes("/mapper/sql"));//构建 crud 语句 重点关注this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} else {throw new BuilderException("Mapper's namespace cannot be empty");}} catch (Exception var3) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);}
}

XMLMapperBuilder#buildStatementFromContext 方法源码分析

XMLMapperBuilder#buildStatementFromContext 方法包装调用了 XMLMapperBuilder#buildStatementFromContext 方法,主要做的事情就是迭代解析构建 SQL 语句。

//org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)
private void buildStatementFromContext(List<XNode> list) {if (this.configuration.getDatabaseId() != null) {//database 不为空 构建 sql 语句this.buildStatementFromContext(list, this.configuration.getDatabaseId());}//构建 sql 语句this.buildStatementFromContext(list, (String)null);
}//org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>, java.lang.String)
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {//迭代遍历 sql 语句Iterator var3 = list.iterator();while(var3.hasNext()) {XNode context = (XNode)var3.next();//创建 XMLStatementBuilder 解析器XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);try {//解析 sql  重点关注statementParser.parseStatementNode();} catch (IncompleteElementException var7) {this.configuration.addIncompleteStatement(statementParser);}}}

XMLStatementBuilder#parseStatementNode 方法源码分析

XMLStatementBuilder#parseStatementNode 方法创建了 SqlSource ,并获取一系列的属性,最后封装成了 MappedStatement 对象。

//org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {//获取 id 属性String id = this.context.getStringAttribute("id");//获取 database idString databaseId = this.context.getStringAttribute("databaseId");//启用的数据库和sql节点配置的是否匹配if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {//匹配//获取 nodeNameString nodeName = this.context.getNode().getNodeName();//获取 sql 命令类型SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));//是否是 select 类型boolean isSelect = sqlCommandType == SqlCommandType.SELECT;//是否需要刷新本地缓存 和 二级缓存boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);//是否需要使用二级缓存boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);//这个设置仅针对嵌套结果 select 语句 如果为 true 将会假设包含了嵌套结果集或是分组 当返回一个主结果行时 就不会产生对前面结果集的引用 这就使得在获取嵌套结果集的时候不至于内存不够用 默认值:false。boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);//xml 转换其XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);//适用到 nodeincludeParser.applyIncludes(this.context.getNode());//获取参数类型String parameterType = this.context.getStringAttribute("parameterType");//解析参数类型的 classClass<?> parameterTypeClass = this.resolveClass(parameterType);//获取 long 属性String lang = this.context.getStringAttribute("lang");//动态sql 语言驱动器LanguageDriver langDriver = this.getLanguageDriver(lang);//处理  selectKey节点  当数据表中主键设计为自增 可能会存在业务需要在插入后获取到主键 这时候就需要使用 selectKey 节点 解析完毕后删除 selectKey 节点this.processSelectKeyNodes(id, parameterTypeClass, langDriver);//拼接idString keyStatementId = id + "!selectKey";//名称空间+.+keyStatementIdkeyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);//获取 keyGeneratorObject keyGenerator;if (this.configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = this.configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}//创建  SqlSource 这里面有是否动态 sql 的判断SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);//获取 StatementType  STATEMENT:普通语句    PREPARED:预处理     CALLABLE:存储过程StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));//获取 fetchSizeInteger fetchSize = this.context.getIntAttribute("fetchSize");//获取 timeoutInteger timeout = this.context.getIntAttribute("timeout");//获取入参 parameterMapString parameterMap = this.context.getStringAttribute("parameterMap");//获取 resultTypeString resultType = this.context.getStringAttribute("resultType");//获取 resultType 的类型Class<?> resultTypeClass = this.resolveClass(resultType);//获取 resultMapString resultMap = this.context.getStringAttribute("resultMap");//获取 resultSetTypeString resultSetType = this.context.getStringAttribute("resultSetType");//获取 ResultSetTypeResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {//获取默认的 ResultSetTyperesultSetTypeEnum = this.configuration.getDefaultResultSetType();}//获取 keyPropertyString keyProperty = this.context.getStringAttribute("keyProperty");//获取 keyColumnString keyColumn = this.context.getStringAttribute("keyColumn");//获取 resultSetsString resultSets = this.context.getStringAttribute("resultSets");//创建 SQL 对应的 MappedStatement 对象this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}
}

XMLLanguageDriver#createSqlSource 方法源码分析

XMLLanguageDriver#createSqlSource 方法中有对动态 SQL 的处理 ,对于动态 SQL 和普通 SQL 会有不同的处理。

  • RawSqlSource : 存储的是只有 #{} 或者没有标签的纯文本SQL信息。
  • DynamicSqlSource : 存储的是写有 ${} 或者具有动态SQL标签的SQL信息。
  • StaticSqlSource : 是DynamicSqlSource和RawSqlSource解析为BoundSql的一个中间态对象类型。
  • BoundSql:用于生成我们最终执行的SQL语句,属性包括参数值、映射关系、以及SQL(带问号的)。
//org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {//创建 XMLScriptBuilder 解析 SQLXMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode();
}//org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
public SqlSource parseScriptNode() {//解析动态标签 MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);Object sqlSource;//是否是动态 sqlif (this.isDynamic) {//创建动态 SQL DynamicSqlSource sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);} else {//普通 SQL RawSqlSourcesqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);}return (SqlSource)sqlSource;
}//org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags
protected MixedSqlNode parseDynamicTags(XNode node) {List<SqlNode> contents = new ArrayList();NodeList children = node.getNode().getChildNodes();for(int i = 0; i < children.getLength(); ++i) {XNode child = node.newXNode(children.item(i));String nodeName;if (child.getNode().getNodeType() != 4 && child.getNode().getNodeType() != 3) {if (child.getNode().getNodeType() == 1) {nodeName = child.getNode().getNodeName();XMLScriptBuilder.NodeHandler handler = (XMLScriptBuilder.NodeHandler)this.nodeHandlerMap.get(nodeName);if (handler == null) {throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");}handler.handleNode(child, contents);this.isDynamic = true;}} else {nodeName = child.getStringBody("");//重点关注这里 将节点封装成 TextSqlNodeTextSqlNode textSqlNode = new TextSqlNode(nodeName);// textSqlNode.isDynamic() 判断是否是动态的if (textSqlNode.isDynamic()) {contents.add(textSqlNode);//设置为 是this.isDynamic = true;} else {contents.add(new StaticTextSqlNode(nodeName));}}}return new MixedSqlNode(contents);
}//org.apache.ibatis.scripting.xmltags.TextSqlNode#isDynamic
public boolean isDynamic() {//创建一个 DynamicCheckerTokenParserTextSqlNode.DynamicCheckerTokenParser checker = new TextSqlNode.DynamicCheckerTokenParser();//创建一个以 ${ 为开始和以 } 为结尾的解析器 DynamicCheckerTokenParser GenericTokenParser parser = this.createParser(checker);//解析parser.parse(this.text);//解析成功 返回 return checker.isDynamic();
}//org.apache.ibatis.scripting.xmltags.TextSqlNode#createParser
private GenericTokenParser createParser(TokenHandler handler) {return new GenericTokenParser("${", "}", handler);
}

createSqlSource 源码分析完毕,我们继续分析创建 SQL 对应的 MappedStatement 对象的源码。

MapperBuilderAssistant#addMappedStatement 方法源码分析

MapperBuilderAssistant#addMappedStatement 方法的主要作用就是将 MappedStatement 加入 mappedStatements 集合中。

//org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {if (this.unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");} else {//获取 名称空间id = this.applyCurrentNamespace(id, false);//是否是 select 类型boolean isSelect = sqlCommandType == SqlCommandType.SELECT;//创建 statementBuilderorg.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);if (statementParameterMap != null) {//给 mappedStatement 赋值 parameterMapstatementBuilder.parameterMap(statementParameterMap);}MappedStatement statement = statementBuilder.build();//将 MappedStatement 加入 mappedStatements 集合中this.configuration.addMappedStatement(statement);return statement;}
}//org.apache.ibatis.session.Configuration#addMappedStatement
public void addMappedStatement(MappedStatement ms) {//将 MappedStatement 加入 mappedStatements 集合中this.mappedStatements.put(ms.getId(), ms);
}

this.build(parser.parse()) 源码分析

经过以上步骤,Configuration 已经加载完毕,调用SqlSessionFactoryBuilder#build 方法创建 SqlSessionFactory 。

//org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}

至此 SqlSessionFactory 创建的源码分析完毕,希望可以帮助到有需要的小伙伴。

欢迎提出建议及对错误的地方指出纠正。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

钒能新材料综合回收利用,钒溶液净化富集工艺之离子交换法

钒电池储能产业作为典型的绿色低碳优势产业&#xff0c;是新型储能领域重要发展方向。钒电池储能具备大规模、长周期等优势&#xff0c;是储能领域的重要组成部分&#xff0c;将成为拓展电能利用、应对可再生能源随机波动、支撑可再生能源高占比电力系统的最佳技术途径之一。 …

openEuler 24.03 LTS - 华为欧拉开源版(华为 RHEL 兼容发行版)

openEuler 24.03 LTS - 华为欧拉开源版&#xff08;华为 RHEL 兼容发行版&#xff09; 华为红帽企业 Linux 兼容发行版 请访问原文链接&#xff1a;https://sysin.org/blog/openeuler/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sy…

【C++题解】1026 - 求商数和余数

问题&#xff1a;1026 - 求商数和余数 类型&#xff1a;基础问题 题目描述&#xff1a; 输入 a,b 两个整数&#xff0c;编程求出 a 除以 b 得到的商和余数。 输入&#xff1a; 输入一行&#xff0c;只有两个整数(中间有空格)。 输出&#xff1a; 输出只有一行&#xff0c…

优思学院|质量管理中如何应用5W1H或5W2H方法?

5W1H或者5W2H其实是一种"工作方式"&#xff0c;它不是"思考方法"&#xff0c;这种工作方式是通过回答“什么&#xff08;What&#xff09;&#xff1f;为什么&#xff08;Why&#xff09;&#xff1f;谁&#xff08;Who&#xff09;&#xff1f;何时&#…

求解FAT表大小

540M/1K 540k 540 * 1024 b 512k对应2的19次方<540k<1024k对应2的20次方 取比540k大的1024k 八位一个字节 20对应2.5个字节 所以FAT占用 2.5*5401350k 1.2G/1k1.2M 1M1024k对应2的20次方<1.2M<2M对应2的21次方 取2M 21/8是循环小数不可以直接用 所以四舍五…

什么电脑录屏软件好用?这里有3款超实用推荐!

在当今数字化时代&#xff0c;电脑录屏软件已经成为了人们日常学习和工作中不可或缺的工具。无论是录制游戏实况、制作教程视频&#xff0c;还是保存线上会议的内容&#xff0c;一款好用的电脑录屏软件都显得尤为重要。可是什么电脑录屏软件好用呢&#xff1f;本文将为大家介绍…

Redis-数据类型-Set(不允许重复)

文章目录 1、查看redis是否启动2、通过客户端连接redis3、切换到2数据库4、给key指定的set集合中存入数据&#xff0c;set会自动去重5、返回可以指定的set集合中所有的元素6、返回集合中元素的数量(set cardinality)7、检查当前指定member是否是集合中的元素8、从集合中删除元素…

文件扫描工具哪个好?便捷的文件扫描工具推荐

对于初入职场的大学毕业生&#xff0c;申请就业补贴是一项不可忽视的福利。 它不仅能够为新生活带来经济上的缓解&#xff0c;也有助于职业生涯的顺利起步。面对申请过程中需提交的文件&#xff0c;如纸质劳动合同&#xff0c;不必烦恼。市面上众多文件扫描软件能助你一臂之力…

【Web APIs】DOM 文档对象模型 ④ ( querySelector 函数 | querySelectorAll 函数 | NodeList 对象 )

文章目录 一、querySelector 函数1、querySelector 函数简介2、完整代码示例 二、querySelectorAll 函数1、querySelectorAll 函数简介2、完整代码示例 三、NodeList 对象1、NodeList 对象简介2、完整代码示例 本博客相关参考文档 : WebAPIs 参考文档 : https://developer.moz…

【Hadoop大数据技术】——期末复习(冲刺篇)

&#x1f4d6; 前言&#xff1a;快考试了&#xff0c;做篇期末总结&#xff0c;都是重点与必考点。 题型&#xff1a;简答题、编程题&#xff08;Java与Shell操作&#xff09;、看图分析题。题目大概率会从课后习题、实验里出。 课本&#xff1a; 目录 &#x1f552; 1. HDF…

FreeRTOS学习 -- 时间管理

在使用 FreeRTOS 的过程中通常会在一个任务函数中使用延时函数对这个任务延时&#xff0c;当执行延时函数的时候会进行任务切换&#xff0c;并且此任务就会进入阻塞态&#xff0c;直到延时完成&#xff0c;任务重新进入就绪态。 FreeRTOS 延时函数 1、函数 vTaskDelay() 在F…

用Nuitka打包 Python,效果竟如此惊人!

目录 为什么选择Nuitka&#xff1f; Nuitka的工作原理 Nuitka的工作流程大致如下&#xff1a; 安装Nuitka 实战案例 示例代码 打包程序 运行可执行文件 进阶技巧 优化选项 多文件项目 打包第三方库 使用Python开发一个程序后&#xff0c;将Python脚本打包成独立可执…

深度神经网络——决策树的实现与剪枝

概述 决策树 是一种有用的机器学习算法&#xff0c;用于回归和分类任务。 “决策树”这个名字来源于这样一个事实&#xff1a;算法不断地将数据集划分为越来越小的部分&#xff0c;直到数据被划分为单个实例&#xff0c;然后对实例进行分类。如果您要可视化算法的结果&#xf…

Web开发的未来:深入Symfony框架的全方位指南

Symfony是一款强大的PHP框架&#xff0c;用于开发高性能的Web应用。它提供了一套完整的工具和API&#xff0c;帮助开发者构建从简单的博客到复杂的企业级应用。本文将全面介绍Symfony框架的基本概念、使用方法、主要作用以及注意事项。 一、Symfony框架简介 1. Symfony的起源 …

数据结构5---矩阵和广义表

一、矩阵的压缩存储 特殊矩阵:矩阵中很多值相同的元素并且它们的分布有一定的规律。 稀疏矩阵:矩阵中有很多零元素。压缩存储的基本思想是: (1)为多个值相同的元素只分配一个存储空间; (2)对零元素不分配存储空间。 1、特殊矩阵的压缩存储 &#xff08;1&#xff09;对称矩…

【机器学习】必会降维算法之:随机投影(Random Projection)

随机投影&#xff08;Random Projection&#xff09; 1、引言2、随机投影&#xff08;Random Projection&#xff09;2.1 定义2.2 核心原理2.3 应用场景2.4 实现方式2.5 算法公式2.6 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c;降维算法还没讲完呢。 小鱼&a…

浙江保融科技2025实习生校招校招笔试分享

笔试算法题一共是有4道&#xff0c;第一道是手搓模拟实现一个ArrayList&#xff0c;第二道是判断字符串是否回文&#xff0c;第三道是用代码实现1到2种设计模式。 目录 一.模拟实现ArrayList 二.判断字符串是否回文 ▐ 解法一 ▐ 解法二 ▐ 解法三 三.代码实现设计模式 一…

网络协议安全:TCP/IP协议栈的安全问题和解决方案

「作者简介」:北京冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础对安全知识体系进行总结与归纳,著作适用于快速入门的 《网络安全自学教程》,内容涵盖Web安全、系统安全等12个知识域的一百多个知识点,持续更新。 这一章节我们需要知道TCP/IP每…

大模型扫盲系列——大模型实用技术介绍_大模型底层技术是哪些

Gemma模型架构和参数计算 近期&#xff0c;大模型相关的技术和应用层出不穷&#xff0c;各个方向的论文百花齐放&#xff0c;底层的核心技术是大家公认的精华部分。本文从技术的角度聚焦大模型的实战经验&#xff0c;总结大模型从业者关注的具体方向以及相关发展&#xff0c;帮…

干货 | 如何进行群体DNA甲基化分析

目前&#xff0c;针对群体的研究基本上还是以重测序为主&#xff0c;基于对遗传多样性丰富的自然群体中的个体进行全基因组重测序&#xff0c;研究物种遗传进化多样性&#xff0c;结合准确的目标性状的表型数据及统计方法进行全基因组关联分析&#xff0c;可对动植物复杂农艺性…