4.二级缓存解析

news2024/11/27 4:38:08

文章目录

  • 1. 二级缓存配置
  • 2. 二级缓存结构
  • 3. 二级缓存命中条件
  • 4. 缓存空间的理解
  • 5. 二级缓存执行流程

二级缓存也称作是应用级缓存,与一级缓存不同的,是它的作用范围是整个应用,而且可以跨线程使用。所以二级缓存有更高的命中率,适合缓存一些修改较少的数据。在流程上是先访问二级缓存,在访问一级缓存。

1. 二级缓存配置

mybatis要使二级缓存生效可采用下面两种方式:配置文件方式和注解方式。
第一种配置文件方式,首先在mybatis的全局配置文件添加如下配置,负责总开启二级缓存的开关,当然如不设置,也是默认开启的。

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

总开关打开后,若要使哪个mapper文件的二级缓存生效,还需要在各自的mapper中添加<cache></cache>标签,如下所示

<?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">

<!--namespace为UserDao接口的全类名-->
<mapper namespace="com.lzj.dao.UserDao">
    <cache></cache>
    <!--selectOne要与UserDao接口中的接口方法名一致-->
    <!--parameterType和resultType指定的类型除了基本类型外,自定义的类要用全类名-->
    <select id="selectOne" parameterType="int" resultType="com.lzj.bean.User">
        select * from user where id=#{id}
    </select>

    <select id="selectById" parameterType="int" resultType="com.lzj.bean.User">
        select * from user where id=#{id}
    </select>

    <update id="updAge" parameterType="com.lzj.bean.User">
        update user set age=#{age} where id=#{id}
    </update>

    <insert id="insert" parameterType="com.lzj.bean.User">
        insert into user values (#{id},#{name},#{age})
    </insert>
</mapper>

第二种方式是注解方式,需要在对应的Mapper接口类上添加@CacheNamespace 注解。

2. 二级缓存结构

二级缓存包括存储、溢出淘汰、过期清理、线程安全、命中率统计、序列化等功能。

其中二级缓存最基本的存储功能K-V形式实现的。

二级缓存的溢出淘汰功能用到的溢出算法包括下述4种:

  • FIFO:先进先出;
  • LRU:最近最少使用;
  • WeakReference: 弱引用,将缓存对象进行弱引用包装,当Java进行gc的时候,不论当前的内存空间是否足够,这个对象都会被回收;
  • SoftReference:软件引用,基机与弱引用类似,不同在于只有当空间不足时GC才才回收软引用对象。

过期清理功能指清理存放数据过久的数据。

线程安全可以保证缓存可以被多个线程同时使用。

mybatis实现这些功能是通过责任链设计模式+装饰器模式实现的。

下面看下mybatis是如何通过责任链设计模式+装饰器模式实现二级缓存各个功能的。如下图所示
在这里插入图片描述

首先Myatis定义了Cache接口,只定义了Cache基本功能,每一个实现类只执行一种功能,遵守责任单一原则。从上图最左边看,BlockingCache装饰了SynchronizedCache,SynchronizedCache又装饰了LoggingCache,每一个缓存类一次向右进行装饰,当代码执行时先从最右边的被装饰类开始执行,然后依次向左执行装饰类,就像在一个链条上依次执行各个功能一样。

具体的可以通过断点方式查看mybatis二级缓存依次执行的缓存类

public void test1(){
    SqlSessionFactory factory = mybatisUtil.getFactory();
    SqlSession sqlSession = factory.openSession(false);  //true表示自动提交
    List<Object> list = sqlSession.selectList("com.lzj.dao.UserDao.selectOne", 2);
    sqlSession.commit();
    list = sqlSession.selectList("com.lzj.dao.UserDao.selectOne", 2);
    Configuration configuration = mybatisUtil.getConfiguration();
    Cache cache = configuration.getCache("com.lzj.dao.UserDao");
    System.out.println(list);
}

断点后可以查看Cache的默认缓存执行顺序如下所示,先执行delegate被装饰的缓存类,当然还可以设置其他缓存类到责任链上。
在这里插入图片描述

3. 二级缓存命中条件

命中二级缓存还需要满足下面4个条件:

  • 必须会话提交后才生效;
  • SQL的语句和参数必须相同;
  • StatementID必须相同;
  • RowBounds分页条件必须相同;

3.1 必须会话提交后才生效
首先看下面的测试案例

    public void test1(){
        SqlSessionFactory factory = mybatisUtil.getFactory();
        SqlSession sqlSession = factory.openSession(false);  //true表示自动提交
        List<Object> list = sqlSession.selectList("com.lzj.dao.UserDao.selectOne", 2);
        list = sqlSession.selectList("com.lzj.dao.UserDao.selectOne", 2);
        System.out.println(list);
    }

执行该案例,输出结果如下所示,两次SQL日志显示Cache Hit Ratio [com.lzj.dao.UserDao]: 0.0,说明两次查询都未命中缓存。因为二级缓存是跨线程级别的,假设一个线程A正在修改缓存空间中的一条SQL,此时有另外一个线程B需要读这个缓存空间的SQL,如果A线程修改未提交就生效的话,B线程就立即读到缓存中修改的数据,而如果线程B后续操作失败了需要回滚,那么B线程就是读到的脏数据了,所以会话必须提交后缓存才生效。

2023-01-11 22:39:16.991  INFO 2764 --- [nio-8004-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-01-11 22:39:16.991  INFO 2764 --- [nio-8004-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-01-11 22:39:17.024  INFO 2764 --- [nio-8004-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 33 ms
2023-01-11 22:39:17.099 DEBUG 2764 --- [nio-8004-exec-1] com.lzj.dao.UserDao                      : Cache Hit Ratio [com.lzj.dao.UserDao]: 0.0
2023-01-11 22:39:17.190 DEBUG 2764 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectOne            : ==>  Preparing: select * from user where id=? 
2023-01-11 22:39:17.291 DEBUG 2764 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectOne            : ==> Parameters: 2(Integer)
2023-01-11 22:39:17.419 DEBUG 2764 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectOne            : <==      Total: 1
2023-01-11 22:39:17.426 DEBUG 2764 --- [nio-8004-exec-1] com.lzj.dao.UserDao                      : Cache Hit Ratio [com.lzj.dao.UserDao]: 0.0
[User{id=2, name='xiaoli', age=26}]

3.2. SQL的语句和参数必须相同

public void test1(){
    SqlSessionFactory factory = mybatisUtil.getFactory();
    SqlSession sqlSession = factory.openSession(false);  //true表示自动提交
    List<Object> list = sqlSession.selectList("com.lzj.dao.UserDao.selectOne", 2);
    sqlSession.commit();
    list = sqlSession.selectList("com.lzj.dao.UserDao.selectOne", 2);
    System.out.println(list);
}

运行上面测试案例,输出结果如下所示第一次执行selectOne时,参数为2,日志显示Cache Hit Ratio为0.0,未命中二级缓存。当commit后再次查询selectOne,参数还是2时,Cache Hit Ratio显示为0.5,表示命中了二级缓存。如果第二次查询的SQL不是selectOne或者参数不是2时,也不会命中二级缓存。

2023-01-11 22:50:53.498  INFO 14000 --- [nio-8004-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-01-11 22:50:53.506  INFO 14000 --- [nio-8004-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-01-11 22:50:53.539  INFO 14000 --- [nio-8004-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 33 ms
2023-01-11 22:50:53.645 DEBUG 14000 --- [nio-8004-exec-1] com.lzj.dao.UserDao                      : Cache Hit Ratio [com.lzj.dao.UserDao]: 0.0
2023-01-11 22:50:53.721 DEBUG 14000 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectOne            : ==>  Preparing: select * from user where id=? 
2023-01-11 22:50:53.797 DEBUG 14000 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectOne            : ==> Parameters: 2(Integer)
2023-01-11 22:50:53.865 DEBUG 14000 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectOne            : <==      Total: 1
2023-01-11 22:50:53.897 DEBUG 14000 --- [nio-8004-exec-1] com.lzj.dao.UserDao                      : Cache Hit Ratio [com.lzj.dao.UserDao]: 0.5
[User{id=2, name='xiaoli', age=26}]

3.3. StatementID必须相同;
首先看下面测试案例

public void test2(){
    SqlSessionFactory factory = mybatisUtil.getFactory();
    SqlSession sqlSession = factory.openSession(false);  //true表示自动提交
    List<Object> list = sqlSession.selectList("com.lzj.dao.UserDao.selectOne", 2);
    sqlSession.commit();
    list = sqlSession.selectList("com.lzj.dao.UserDao.selectById", 2);
    System.out.println(list); 
}

两次查询对应的SQL如下所示

<select id="selectOne" parameterType="int" resultType="com.lzj.bean.User">
    select * from user where id=#{id}
</select>

<select id="selectById" parameterType="int" resultType="com.lzj.bean.User">
    select * from user where id=#{id}
</select>

发现两次查询额SQL是一样的,只是对应的StatementID不同,分别诶selectOne和selectById,测试结果如下所示,发现两次查询日志都显示Cache Hit Ration为0.0,表示未命中缓存。因此哪怕SQL相同,如果StatementID不同也无法命中二级缓存。

2023-01-11 23:05:48.278  INFO 3488 --- [nio-8004-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-01-11 23:05:48.297  INFO 3488 --- [nio-8004-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 19 ms
2023-01-11 23:05:48.373 DEBUG 3488 --- [nio-8004-exec-1] com.lzj.dao.UserDao                      : Cache Hit Ratio [com.lzj.dao.UserDao]: 0.0
2023-01-11 23:05:48.455 DEBUG 3488 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectOne            : ==>  Preparing: select * from user where id=? 
2023-01-11 23:05:48.546 DEBUG 3488 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectOne            : ==> Parameters: 2(Integer)
2023-01-11 23:05:48.614 DEBUG 3488 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectOne            : <==      Total: 1
2023-01-11 23:05:48.638 DEBUG 3488 --- [nio-8004-exec-1] com.lzj.dao.UserDao                      : Cache Hit Ratio [com.lzj.dao.UserDao]: 0.0
2023-01-11 23:05:48.638 DEBUG 3488 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectById           : ==>  Preparing: select * from user where id=? 
2023-01-11 23:05:48.639 DEBUG 3488 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectById           : ==> Parameters: 2(Integer)
2023-01-11 23:05:48.641 DEBUG 3488 --- [nio-8004-exec-1] com.lzj.dao.UserDao.selectById           : <==      Total: 1
[User{id=2, name='xiaoli', age=26}]

3.4 RowBounds分页条件必须相同
如果分页条件都不一致,查询的页数都不同,也必定不会命中缓存了。

4. 缓存空间的理解

在mybatis中并不是查询完SQL就就立即缓存到缓存空间中的,而是先缓存到暂存区中,会话提交后才会把暂存区的SQL缓存到缓存空间中,这样操作的好处就是当会话回滚的话,直接删除事务缓存管理器,当然暂存区也一并删除了,无须再回滚缓存空间了。
在执行中,每个会话都会维护一个事务缓存管理器,每个事务缓存管理器又会维护一个或者多个暂存区,这要取决于本次会话中共操作了几个缓存空间,也即共操作了几个Mapper文件,比如本次会话中即要用到UserMapper又要用到CustomerMapper,那么事务缓存管理器就要维护2个暂存区。
在这里插入图片描述
通过如下断点方式可以看出,二级缓存执行器CachingExecutor中维护了一个TransactionalCacheManager事务管理器,事务管理器内部维护了一个HashMap<Cache, TransactionalCache>类型的transactionalCaches变量,transactionalCaches就可以理解为暂存区,下面测试案例中由于只用到了UserMapp.xml,所以暂存区中缓存的数量为1。
在这里插入图片描述
暂存区的key为Cache,表示二级缓存的key,也可以理解为二级缓存责任链中的第一个Cache实现类的对象,此处案例默认的是SynchronizedCache的对象。暂存区的value为TrasactionalCache的对象,TrasactionalCache也是实现了Cache,并且内部也是维护了一个delegate变量,而delegate表示二级缓存空间,即上面介绍的二级缓存责任链方式,此处运用了装饰器模式+责任链模式维护了暂存区。由于暂存区与缓冲区结构一致,当会话提交时就可以比较方便的把暂存区内容同步到二级缓冲区中了。

public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

  private final Cache delegate;
  private boolean clearOnCommit;
  private final Map<Object, Object> entriesToAddOnCommit;
  private final Set<Object> entriesMissedInCache;

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<>();
    this.entriesMissedInCache = new HashSet<>();
  }
  ……
  ……
  ……

5. 二级缓存执行流程

二级缓存执行流程的核心方法是CachingExecutor下面的query方法,如下所示。

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

下面逐步进行分析,
1.首先执行Cache cache = ms.getCache();,表示通过MapStatement获取缓存,只有在Mapper文件中配置了<cache/>,<cache-ref/>或在Dao方法上添加了注解@CacheNamespace,@CacheNamespaceRef的,获取的cache才不为null。

2.如果配置的不开启二级缓存,执行return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);,就会通过代理的执行器,即如未特别指定,默认为SimpleExecutor,去数据库中去查找。

3.如果配置了二级缓存的话,执行flushCacheIfRequired(ms);,会根据sql上配置的<insert>,<select>,<update>,<delete>的flushCache属性来确定是否清空缓存。

  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    /*ms.isFlushCacheRequired()解析flushCache属性,insert/update/delete默认配置的清空缓存,select默认不清空,当然也可以配置清空,则查询也会清空二级缓存,就没什么意义了*/
    if (cache != null && ms.isFlushCacheRequired()) {
      tcm.clear(cache);
    }
  }

其中清空缓存的clear方法如下所示,是清空暂存区TransactionCache中的entriesToAddOnCommit,而entriesToAddOnCommit是HashMap类型的,key为缓存key,value则为缓存的数据,下面会看如何把数据缓存进去的。也就是说当清空缓存时会首先清空暂存区中的缓存。

  @Override
  public void clear() {
    clearOnCommit = true;
    entriesToAddOnCommit.clear();
  }

4.下面继续判断Mapper是否配置二级缓存if (ms.isUseCache() && resultHandler == null),就是有没有在Mapper文件中配置<cache/>,<cache-ref/>或在Dao方法上添加注解@CacheNamespace,@CacheNamespaceRef。一般resultHandler 默认都是null。

5.下面继续校验输入的参数是否合法,<insert>,<select>,<update>,<delete>中不能配置Out类型的参数,mybatis不支持存储过程的缓存,所以如果是存储过程,这里就会报错。

private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
    if (ms.getStatementType() == StatementType.CALLABLE) {
      for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
        if (parameterMapping.getMode() != ParameterMode.IN) {
          throw new ExecutorException("Caching stored procedures with OUT params is not supported.  Please configure useCache=false in " + ms.getId() + " statement.");
        }
      }
    }
  }

6.下面最重要的一点就是从暂存区中获取暂存的数据 List<E> list = (List) tcm.getObject(cache, key);,如果暂存区没有就从数据库中查出来缓存到暂存区。
在从暂存区获取数据时首先获取暂存区,如果还未有暂存区就创建一个维护到缓存事务管理器中。

  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }

获取到暂存区TrasactionalCache后,就可以调用getObject开始从缓存空间中查询数据了,注意下面当标签上属性flushCache配置成true清空缓存或者<insert>,<update>,<delete>标签时,clearOnCommit都会被赋值为true,清空本地暂存区,但还没有清空二级缓存,如果在此地就清空二级缓存,其它会话还是会继续从二级缓存中继续读数据的,只有当本会话commit后才会真正的清空二级缓存,防止事务回滚,误清空二级缓存。

  @Override
  public Object getObject(Object key) {
    // issue #116
    /*直接从缓存空间中获取数据,注意不是从暂存区中获取数据*/
    Object object = delegate.getObject(key);
    if (object == null) {
      /*如果缓存空间中没有,就把key缓存到entriesMissedInCache中*/
      entriesMissedInCache.add(key);
    }
    // issue #146
    /*判断是否被清空缓存,有标签上配置了flushCache属性,或者是增删改标签就会把缓存暂存区清空,此时即使二级缓存有数据,那么此时也是直接返回null,等commit后才会把二级缓存中的数据真正的清空*/
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

7.如果从缓存空间中获取数据了就直接返回了,如果未从缓存空间中获取到数据,还是要查询数据库,并把数据先缓存到暂存空间的

if (list == null) {
  list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  tcm.putObject(cache, key, list); // issue #578 and #116
}

CachingExecutor中,如未指定,默认delegate代理的是SimpleExecutor执行器,会直接先查一级缓存然后查数据库的,查到的数据再执行tcm.putObject(cache, key, list),把数据先放到暂存区中,缓存到暂存区其实就是先存放到TransactionalCache暂存区中的HashMap类型的entriesToAddOnCommit变量中,等commit后再把暂存区中数据库同步到二级缓存中,防止事务回滚。

  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

8.执行完上述query方法后,会话最后会调用CachingExecutor.commit方法进行事务提交,其实就是缓存事务管理器的提交。执行commit时会首先执行delegate.commit进行数据库的事务的提交,然后执行tcm.commi进行缓存事务管理器TransactionalCacheManager的提交。

  /*CachingExecutor*/
  @Override
  public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    tcm.commit();
  }

在执行TransactionalCacheManager缓存事务管理器提交时,会对缓存事务管理器维护的暂存区分别进行提交,比如本案例只用了UserMapper一个Mapper文件,则只有一个暂存区。

  /*TransactionalCacheManager*/
  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }

在执行暂存区commit时,如果前面已经把清空标志设置成了true,则此时会清空二级缓存,增删改默认都会设置该标志位true的。前面分析过,暂存区中delegate是代理的二级缓存。

  /*TransactionalCache */
  public void commit() {
    /*如果清空标志位true,则清空二级缓存*/
    if (clearOnCommit) {
      delegate.clear();
    }
    /*如果清空标志位false,则把暂存区中数据同步到二级缓存中*/
    flushPendingEntries();
    reset();
  }

在暂存区中数据同步到二级缓存时entriesToAddOnCommit中的数据直接添加到二级缓存中,entriesMissedInCache中的key如果不是在entriesToAddOnCommit中的就会缓存一个null到二级缓存中,防止缓存穿透。

  private void flushPendingEntries() {
    /*entriesToAddOnCommit表示暂存在暂存区中的key-value数据*/
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    /*entriesMissedInCache表示查询数据库也会获取到数据,此时把null缓存到二级缓存中,防止缓存穿透*/
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

执行完commit后,以上二级缓存流程执行完毕。

参考文献:源码阅读网

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

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

相关文章

从南丁格尔图到医学发展史

可视化中&#xff0c;前端用于表现不同类目的数据在总和中的占比的场景&#xff0c;往往会采用饼图。 针对数据大小相近&#xff0c;南丁格尔图的呈现会更加美观。 南丁格尔图&#xff0c;又称玫瑰图&#xff0c;是由弗罗伦斯南丁格尔发明。 弗洛伦斯南丁格尔 开创了护理事业…

二、django中的路由系统

django中的路由系统 django中路由的作用和路由器类似&#xff0c;当一个用户请求Django站点的一个页面时&#xff0c;是路由系统通过对url的路径部分进行匹配&#xff0c;一旦匹配成功就导入并执行对应的视图来返回响应。 django如何处理请求 当一个请求来到时&#xff0c;d…

SpringSecurityOauth2架构Demo笔记

总体分为SpringSecurityOauth2授权码模式演示和密码模式演示 一直下一步,依赖手动导入,SpringBoot版本改成2.2.5.RELEASE,JDK版本1.8 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xml…

Open3D 点云投影至指定球面(Python版本)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 假设球体的相关参数:中心为 C ( x c , y c , z c ) C(x_c,y_c,z_c)

【数据结构和算法】栈—模拟实现Stack和栈相关算法题

文章目录栈的定义Stack模拟实现相关算法题1.栈的压入弹出序列2.逆波兰表达式(后缀表达式)⭐1.什么是逆波兰表达式?如何转换成逆波兰表达式逆波兰表达式如何计算3.有效的括号总结栈的定义 栈作为一种数据结构&#xff0c;是一种只能在一端进行插入和删除操作的特殊线性表。它按…

华为MPLS跨域C2方案实验配置

MPLS隧道——跨域解决方案C1、C2讲解_静下心来敲木鱼的博客-CSDN博客_route-policy rr permit node 10 if-match mpls-labelhttps://blog.csdn.net/m0_49864110/article/details/127634890?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId…

深度学习——长短期记忆网络LSTM(笔记)

长短期记忆网络LSTM&#xff1a; ①隐变量模型存在长期信息保存和短期输入缺失问题&#xff0c;解决方法是LSTM ②发明于90年代 ③使用效果和GRU差别不大&#xff0c;但是实现起来复杂 1.长短期记忆网络 ①忘记门Ft&#xff1a;将值朝0减少 ②输入门It&#xff1a;是否忽…

最容易理解的并查集详解

并查集 并查集&#xff0c;在一些有N个元素的集合应用问题中&#xff0c;我们通常是在开始时让每个元素构成一个单元素的集合&#xff0c;然后按一定顺序将属于同一组的元素所在的集合合并&#xff0c;其间要查找一个元素在哪个集合中。 比如下面这幅图&#xff0c;总共有 10 …

MySQL之存储过程

MySQL存储过程1、基本介绍1.1、介绍存储过程&#xff1a;1.2、特点1.3、基本语法1.3.1、delimiter1.3.1、创建存储过程1.3.2、调用存储过程1.3.3、查看存储过程1.3.4、删除存储过程2、变量2.1、系统变量2.1.1、查询(会话、全局、模糊、精确)2.1.2、设置系统变量2.2、用户定义变…

IB学生必须具备的三大特质

以往的专栏亦提及过&#xff0c;修读IB课程要面对几大挑战。而要应对这些挑战&#xff0c;IB学生须具备以下三大条件&#xff1a; 时间管理能力 IBDP 首先&#xff0c;要对时间分配掌握得很好。两年的IB预科课程非常紧凑&#xff0c;不但每科都有其内部评核&#xff08;Interna…

VMware17虚拟机安装Ubuntu最新版本(Ubuntu22.04LTS)详细步骤

目录 一、概述 二、下载Ubuntu 22.04.1 LTS 三、在VMware虚拟机下安装Ubuntu22.04 四、配置网络 一、概述 Ubuntu是基于Linux内核开发的&#xff0c;免费下载&#xff0c;使用和分享的开源系统。如果需要在Linux下开发程序&#xff0c;这是一个很好的选择。本文介绍了Ubuntu最…

【问题解决】Tomcat启动服务时提示Filter初始化或销毁出现java.lang.AbstractMethodError错误

问题背景 最近在开发项目接口&#xff0c;基于SpringBoot 2.6.8&#xff0c;最终部署到外置Tomcat 8.5.85 下&#xff0c;开发过程中写了一个CookieFilter&#xff0c;实现javax.servlet.Filter接口&#xff0c;代码编译期正常。部署到外置Tomcat 8.5.85 下&#xff0c;在控制…

【Java寒假打卡】Java基础-类加载器

【Java寒假打卡】Java基础-类加载器概述类加载时机类加载的过程-加载类加载的过程-链接类加载的过程-初始化类加载器的分类类加载器-双亲委派模型类加载器-常用方法概述 负责将字节码文件加载到内存中 类加载时机 创建类的实例对象调用类的类方法访问类或者接口的类变量&am…

SymPy符号运算库与latex数学公式

SymPy符号运算库与latex数学公式sympylatexsympy SymPy是一个用于以符号运算为主的符号数学的Python库。它的目标是成为一个全功能的计算机代数系统(CAS)&#xff0c;同时保持代码尽可能的简单&#xff0c;以便易于理解和易于扩展。SymPy完全是用Python编写的。 官网地址:http…

【linux kernel】Linux设备驱动模型 | bus

文章目录一、导读二、与总线相关的数据结构&#xff08;2-1&#xff09;struct bus_type&#xff08;2-2&#xff09;struct subsys_private三、总线的初始化四、总线的操作接口&#xff08;4-1&#xff09;总线的注册&#xff08;4-2&#xff09;总线的注销&#xff08;4-3&am…

Linux的基本使用在Linux上部署程序

linux概述 Linux严格意义来说只是一个"操作系统内核"&#xff0c;一个完整的操作系统 操作系统内核 配套的应用程序 由于 Linux 是一个完全开源免费的内核&#xff0c;因此有些公司/开源组织又基于 Linux 内核&#xff0c;提供了不同的配套程序&#xff0c;这就构…

GAN“家族”又添新成员——EditGAN,不但能自己修图,还修得比你我都好

导语&#xff1a;从风格迁移到特征解耦、语言概念解耦&#xff0c;研究人员正通过数学和语言逐步改善GAN的功能。作者 | 莓酊编辑 | 青暮首先想让大家猜一猜&#xff0c;这四张图中你觉得哪张是P过的&#xff1f;小编先留个悬念不公布答案&#xff0c;请继续往下看。生成对抗网…

【蓝桥杯】历届真题 时间显示(省赛)Java

【问题描述】 小蓝要和朋友合作开发一个时间显示的网站。在服务器上&#xff0c;朋友已经获取了当前的时间&#xff0c;用一个整数表示&#xff0c;值为从1970年1月1日O0:00:00到当前时刻经过的毫秒数。 现在&#xff0c;小蓝要在客户端显示出这个时间。小蓝不用显示出年月日&a…

Allegro如何灌铜操作指导

Allegro如何灌铜操作指导 在做PCB设计平面层的铜皮时候,会需要用到灌铜的操作,如下图 灌铜可以让铜皮自动沿着Antietch画指定网络的铜皮 具体操作如下 点击Add Line命令选择Anti Etch的层面,比如Anti Etch画在L2层,线宽设置为40mil

TCP通信的三次握手和四次挥手详解

TCP通信的三次握手和四次挥手详解 计算机网络参考模型: 应用层:例如Modbus、Http、FTP 传输层:TCP、UDP 网络层:IP 数据链路层:MAC 物理层:RS485、RS232、以太网 TCP的包头: TCP包头为至少20字节 TCP包头解释  源端口号、目的端口号,用于建立连接时,确认源端口(本机…