Mybatis原理分析

news2024/11/26 22:48:40

一、总结

MyBatis的基本工作原理就是:先封装SQL,接着调用JDBC操作数据库,最后把数据库返回的表结果封装成Java类。

1. JDBC有四个核心对象:

(1)DriverManager,用于注册数据库连接。
(2)Connection,与数据库连接对象。
(3)Statement/PrepareStatement,操作数据库SQL语句的对象。
(4)ResultSet,结果集或一张虚拟表。

2. MyBatis也有四大核心对象:

(1)SqlSession对象,该对象中包含了执行SQL语句的所有方法。类似于JDBC里面的Connection。
(2)Executor接口,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。类似于JDBC里面的Statement/PrepareStatement。
(3)MappedStatement对象,该对象是对映射SQL的封装,用于存储要映射的SQL语句的id、参数等信息。
(4)ResultHandler对象,用于对返回的结果进行处理,最终得到自己想要的数据格式或类型。可以自定义返回类型。
在这里插入图片描述
上面中流程就是MyBatis内部核心流程,每一步流程的详细说明如下文所述:
(1)读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。
(2)加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
(3)构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。
(4)创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
(5)Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
(6)MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
(7)输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
(8)输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。

二、MyBatis工作流程简述

传统工作模式:

private static void mybatis1() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        //创建SqlSessionFactoryBuilder对象,
        //调用build(inputstream)方法读取并解析配置文件,返回SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        //由SqlSessionFactory创建SqlSession 对象
        SqlSession sqlSession = factory.openSession();
        String id = "0014d1fb909e4b588ab87aa8d0532c3d";
        //调用SqlSession中的api,传入Statement Id和参数,内部进行复杂的处理,最后调用jdbc执行SQL语句,封装结果返回
        UserDao userDao = sqlSession.selectOne("com.locallife.mapper.UserMapper.queryById", id);
        System.out.println(userDao);
    }

使用Mapper接口:

private static void mybatis2() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        //创建SqlSessionFactoryBuilder对象,
        //调用build(inputstream)方法读取并解析配置文件,返回SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        //由SqlSessionFactory创建SqlSession 对象
        SqlSession sqlSession = factory.openSession();
        String id = "0014d1fb909e4b588ab87aa8d0532c3d";
        //调用SqlSession中的api,传入Statement Id和参数,内部进行复杂的处理,最后调用jdbc执行SQL语句,封装结果返回
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        UserDao userDao = mapper.queryById(id);
        System.out.println(userDao);
    }

1. 原生MyBatis原理分析

1.1初始化工作,解析配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactoryBuilder对象,
//调用build(inputstream)方法读取并解析配置文件,返回SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

进入源码分析:

 public SqlSessionFactory build(InputStream inputStream, Properties properties) {
 		// 调用重载方法
        return this.build((InputStream)inputStream, (String)null, properties);
    }

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
         	// XMLConfigBuilder是专门解析mybatis的配置文件的类
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            //这里又调用了一个重载方法。parser.parse()的返回值是Configuration对象
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } //省略部分代码
        return var5;
    }

初始化配置文件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部的属性中。

//在创建XMLConfigBuilder时,它的构造方法中解析器XPathParser已经读取了配置文件
//3. 进入XMLConfigBuilder 中的 parse()方法。
 public Configuration parse() {
  if (this.parsed) {
         throw new BuilderException("Each XMLConfigBuilder can only be used once.");
     } else {
         this.parsed = true;
         //parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签
         this.parseConfiguration(this.parser.evalNode("/configuration"));
         //最后返回的是Configuration 对象
         return this.configuration;
     }
 }
 
//4. 进入parseConfiguration方法
//此方法中读取了各个标签内容并封装到Configuration中的属性中。
private void parseConfiguration(XNode root) {
      try {
          this.propertiesElement(root.evalNode("properties"));
          Properties settings = this.settingsAsProperties(root.evalNode("settings"));
          this.loadCustomVfs(settings);
          this.loadCustomLogImpl(settings);
          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"));
          this.settingsElement(settings);
          this.environmentsElement(root.evalNode("environments"));
          this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          this.typeHandlerElement(root.evalNode("typeHandlers"));
          this.mapperElement(root.evalNode("mappers"));
      } catch (Exception var3) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
      }
    }
1.2 执行SQL
1.2.1 SqlSession API方式

继续分析,初始化完毕后,我们就要执行SQL了

SqlSession sqlSession = factory.openSession();
String id = "0014d1fb909e4b588ab87aa8d0532c3d";
//调用SqlSession中的api,传入Statement Id和参数,内部进行复杂的处理,最后调用jdbc执行SQL语句,封装结果返回
UserDao userDao = sqlSession.selectOne("com.locallife.mapper.UserMapper.queryById", id);
1.2.2 获得sqlSession
//6. 进入openSession方法。
  public SqlSession openSession() {
  	//getDefaultExecutorType()传递的是SimpleExecutor
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

//7. 进入openSessionFromDataSource。
//ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
//openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //根据参数创建指定类型的Executor
      final Executor executor = configuration.newExecutor(tx, execType);
      //返回的是DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

1.2.3 执行sqlsession中的api
//8.进入selectList方法,多个重载方法。
public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
}
public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //根据传入的全限定名+方法名从映射的Map中取出MappedStatement对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      //调用Executor中的方法处理
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
 }

介绍一下MappedStatement :
作用 MappedStatement与Mapper配置文件中的一个select/update/insert/delete节点相对应。mapper中配置的标签都被封装到了此对象中,主要用途是描述一条SQL语句。
初始化过程 回顾刚开始介绍的加载配置文件的过程中,会对mybatis-config.xml中的各个标签都进行解析,其中有 mappers标签用来引入mapper.xml文件或者配置mapper接口的目录。

  <select id="getUser" resultType="user" >
    select * from user where id=#{id}
  </select>

这样的一个select标签会在初始化配置文件时被解析封装成一个MappedStatement对象,然后存储在Configuration对象的mappedStatements属性中,mappedStatements 是一个HashMap,存储时key = 全限定类名 + 方法名,value = 对应的MappedStatement对象。
在configuration中对应的属性为

Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")

在XMLConfigBuilder中的处理:

  private void parseConfiguration(XNode root) {
    try {
      // 省略其他标签的处理
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

1.2.4 继续源码中的步骤,进入 executor.query()
//此方法在SimpleExecutor的父类BaseExecutor中实现
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	//根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
    BoundSql boundSql = ms.getBoundSql(parameter);
    //为本次查询创建缓存的Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }
 
//进入query的重载方法中
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
      	// 如果缓存中没有本次查找的值,那么从数据库中查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

//从数据库查询
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 查询的方法
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    // 将查询结果放入缓存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

// SimpleExecutor中实现父类的doQuery抽象方法
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 传入参数创建StatementHanlder对象来执行查询
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 创建jdbc中的statement对象
      stmt = prepareStatement(handler, ms.getStatementLog());
      // StatementHandler进行处理
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

// 创建Statement的方法
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //条代码中的getConnection方法经过重重调用最后会调用openConnection方法,从连接池中获得连接。
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
//从连接池获得连接的方法
protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    //从连接池获得连接
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommit);
  }

//进入StatementHandler进行处理的query,StatementHandler中默认的是PreparedStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    //原生jdbc的执行
    ps.execute();
    //处理结果返回。
    return resultSetHandler.handleResultSets(ps);
  }

2 接口方式

回顾一下写法:

public static void main(String[] args) {
		//前三步都相同
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
		SqlSession sqlSession = factory.openSession();
		
		//这里不再调用SqlSession 的api,而是获得了接口对象,调用接口中的方法。
		UserMapper mapper = sqlSession.getMapper(UserMapper.class);
		List<User> list = mapper.getUserByName("tom");
}

开始之前介绍一下MyBatis初始化时对接口的处理:MapperRegistry是Configuration中的一个属性,它内部维护一个HashMap用于存放mapper接口的工厂类,每个接口对应一个工厂类。mappers中可以配置接口的包路径,或者某个具体的接口类。

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <mapper class="com.demo.mapper.UserMapper"/>
  <package name="com.demo.mapper"/>
</mappers>

当解析mappers标签时,它会判断解析到的是mapper配置文件时,会再将对应配置文件中的增删改查标签一 一封装成MappedStatement对象,存入mappedStatements中。(上文介绍了)
当判断解析到接口时,会创建此接口对应的MapperProxyFactory对象,存入HashMap中,key = 接口的字节码对象,value = 此接口对应的MapperProxyFactory对象。
正文:
进入sqlSession.getMapper(UserMapper.class)中

在动态代理返回了示例后,我们就可以直接调用mapper类中的方法了,说明在MapperProxy中的invoke方法中已经为我们实现了方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //判断调用是是不是Object中定义的方法,toString,hashCode这类非。是的话直接放行。
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    } 
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 重点在这:MapperMethod最终调用了执行的方法
    return mapperMethod.execute(sqlSession, args);
  }


public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //判断mapper中的方法类型,最终调用的还是SqlSession中的方法
    switch (command.getType()) {
      case INSERT: {
    	Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

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

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

相关文章

基于单片机的智能楼道灯光控制系统设计

摘要&#xff1a;基于单片机的智能楼道灯光控制系统设计由人体感应模块、光照强度检测模块、灯光控制模块、声音传感器模块、声光报警模块等组成。以单片机为核心&#xff0c;通过检测光照强度和红外人体感应相结合&#xff0c;实现了对楼道内灯光的控制&#xff0c;从而达到节…

vue-cli 中 配置 productionSourceMap 为 false 失效?

背景 最近 发现 vuecli 构建的 项目中配置的 productionSourceMap 为 false 后 &#xff0c;生产代码 还是能够看到 sourceMap 文件 。 原因 生效前提条件 得设置 NODE_ENV 为 production 才会生效&#xff01; 解决 直接修改生产环境的配置 NODE_ENV 为 production 直接覆…

二.PhotoKit - 相册权限(彻底读懂权限管理)

引言 用户的照片和视频算是用户最私密的数据之一&#xff0c;由于内置的隐私保护功能&#xff0c;APP只有在用户明确授权的前提下才能访问用户的照片库。从iOS14 开始&#xff0c;PhotoKit进一步增强了用户的隐私控制&#xff0c;用户可以选择指定的照片或者视频资源的访问权限…

阿里淘天landing,是结束也是新的开始(附校/社招内推码)

阿里3个多月landing成功&#xff0c;是结束也是新开始&#xff0c;望我们往后一切顺利~ 因为个人发展规划&#xff0c;今年开始找工作&#xff0c;但负责的业务实在繁忙&#xff0c;所以一边面试一边整理资料&#xff0c;每天都在挤海绵。 今年的就业形势着实不乐观&#xff0c…

NPN传感器与汉姆485总线驱动器限位功能使用

传感器输出的引脚电平是由传感器是常闭还是常开决定的&#xff1b;light on和 dark on决定的是触发信号&#xff1b; PNP-NO常开&#xff1a;在没有信号触发时&#xff0c;输出线是悬空的&#xff08;即VCC电源线和OUT线断开&#xff09;&#xff0c;输出为低电平或不确定状态…

【推荐】免费一年期的SSL证书现在哪里可以申请到

免费一年期的SSL证书虽然不像以前那样普遍易得&#xff0c;但仍有部分途径可以申请到。以下是一些可能的申请渠道&#xff1a; JoySSL 特点&#xff1a;JoySSL是自主品牌SSL证书&#xff0c;安全性和兼容性都有保障。它提供教育版和政务版域名的一年期免费DV单证书。申请流程&…

java中RSA分段加解密及Data must not be longer than异常处理

谈到RSA非对称加密&#xff0c;作为开发的我们第一想到的是安全&#xff0c;几乎不会被破解&#xff0c;以及公钥加密&#xff0c;私钥解密这些。在Java代码中&#xff0c;我们常使用一些现成的工具类如hutool中提供的工具类、网上在线的或者博客上的RSAUtils工具类来实现公钥私…

解决BkwinProject无法编译运行问题

#ifdef _UNICODE // 如果定义了 _UNICODE&#xff0c;这意味着程序正在使用 Unicode 字符集进行编译。 // Unicode 字符集是现代 Windows 应用程序普遍使用的字符编码方式&#xff0c;支持多语言字符。#if defined _M_IX86 // 如果定义了 _M_IX86&#xff0c;这意味着程序正…

【源码交付】数字化产科管理平台:一个集孕期产检、健康宣教、随访、住院、产后42天管理的专科管理系统

项目介绍&#xff1a; 数字化产科管理平台是一个集孕期产检、健康宣教、随访、住院、产后42天管理的专科管理系统&#xff0c;由门诊、住院、数据统计三大功能模块组成&#xff0c;与院内系统HIS/LIS/PACS数据对接&#xff0c;实现以孕妇为中心的全面、高效、多元的全周期服务…

用的到linux-系统性能监控(内存、CPU、硬盘、IO)-Day6

前言&#xff1a; 在Linux系统中&#xff0c;实时监控系统的资源使用情况&#xff08;如内存、硬盘、CPU、网络和IO等&#xff09;是非常重要的&#xff0c;它可以帮助你了解系统的健康状况&#xff0c;及时发现潜在的性能瓶颈或问题。下面将介绍一些常用的命令和工具&#xff…

私服(Nexus)相关笔记

目录 Nexus服务器安装与启动 仓库分类与手动上传组件 私服资源获取 仓库分类 IDEA环境中资源上传与下载 Nexus服务器安装与启动 私服 Nexus是Sonatype公司的一款maven私服产品下载地址: https://help.sonatype.com/repomanager3/download 解压到自己放置资源的地方 输入…

YOLOV8多类别训练时遇到的大坑

本篇文章帮大家避坑。。。 如果要训练六个类别的数据集&#xff0c;按照我以下做数据集文件夹 有test、train、valid、还有一个data.yaml的配置文件。 每个下面都有images和labels 我们拿train的imges来说&#xff0c;它里面存放着你六个类别的图片 labels也要跟上面的图片名…

AH8681锂电升压3.7升5V升12V 2A可支持QC2.0 3.0

135.3806.7573在探讨AH8681这款专为3.7V升压5V至12V&#xff0c;并具备2A输出能力&#xff0c;同时兼容QC2.0与QC3.0快充协议的升压芯片时&#xff0c;我们不得不深入其技术细节、应用场景、设计优势以及市场定位等多个维度&#xff0c;以全面理解其在现代电子设备中的重要作用…

色板游戏 (珂朵莉树+优化)

色板游戏 - 洛谷 核心思路 用珂朵莉树处理区间赋值、计算颜色。 加一个记录答案&#xff0c;就可以过掉全部数据。 AC代码 #include<bits/stdc.h> #define ll long long using namespace std; struct ran{int l, r;mutable int v;bool operator <(const ran &…

CDP问卷填报流程-百胜企业管理咨询

填写CDP问卷需要以下几个步骤&#xff1a; 了解问卷内容&#xff1a;首先&#xff0c;仔细阅读问卷的说明和背景信息&#xff0c;了解问卷的目的和问题。这样可以更好地理解问题&#xff0c;并提供准确的答案。 分析问题&#xff1a;在填写问卷之前&#xff0c;花时间仔细阅读…

C语言 ——— 在杨氏矩阵中查找具体的某个数

目录 何为杨氏矩阵 题目要求 代码实现 何为杨氏矩阵 可以把杨氏矩阵理解为一个二维数组&#xff0c;这个二维数组中的每一行从左到右是递增的&#xff0c;每一列从上到下是递增的 题目要求 在杨氏矩阵中查找具体的某个数 要求&#xff1a;时间复杂度小于O(N) 代码实现…

机器学习--混淆矩阵(Confusion Matrix)

一、混淆矩阵 True Negative (TN)&#xff1a; 真负类&#xff0c;样本的真实类别是负类&#xff0c;并且模型将其识别为负类&#xff0c;cm[0][0]。False Positive (FP)&#xff1a; 假正类&#xff0c;样本的真实类别是负类&#xff0c;但是模型将其识别为正类&#xff0c;cm…

Mutual_Voting_for_Ranking_3D_Correspondences

因为目前主要在看表示学习&#xff0c;所以配准了解较少&#xff0c;这篇文章就主要记录了一下相关工作和作者提出的非dl的模型&#xff0c;实验部分很简略&#xff0c;大家可以做参考。 Abstract 在摘要部分&#xff0c;作者介绍了一种新颖的相互投票方法&#xff0c;用于对…

孙宇晨所到之处,投资人闻风丧胆?WBTC被坑惨了?MakerDAO带头抗议!BitGo退场!

为实现WBTC跨多个司法管辖区的托管业务和冷储存业务多元化&#xff0c;加密货币托管公司BitGo于8月10日宣布&#xff0c;将与BiT Global创建合资企业对Wrapped Bitcoin&#xff08;WBTC&#xff09;业务进行多司法管辖区托管&#xff0c;且BitGo将成为新合资企业的少数股东。此…

满天飞的浮毛怎么去掉?最最高效解决浮毛的办法宠物空气净化器

作为一名呼吸科医生&#xff0c;我们的工作有着明显的“淡旺季”之分。旺季&#xff1a;一是秋冬季节雾霾加剧&#xff0c;引发呼吸道不适&#xff1b;二是宠物换毛期&#xff0c;导致家庭环境中浮毛增多&#xff0c;进而引发呼吸道过敏、炎症等问题。秋冬的雾霾是自然界的不可…