主頁 > 軟體設計 > 精通Mybatis之快取體系

精通Mybatis之快取體系

2021-04-19 13:01:00 軟體設計

一文搞定mybatis的快取體系

  • 前言
  • 一級快取
    • 命中場景
    • 原始碼分析
    • spring集成時一級快取失效問題
  • 二級快取
    • 快取的完整方案
    • Mybatis二級快取結構以及實作
    • 二級快取命中場景
    • 為什么需要提交后才能命中快取?
    • 二級快取執行流程
      • 查詢 query
      • 更新 update
      • 提交 commit
      • 原始碼閱讀
  • 總結

前言

之前小撰寫了mybatis中的執行器,今天來講一下mybatis的快取,大家都知道mybatis有二級快取,一級快取是默認開啟的,而二級快取是可以配置的,其實如果看完小編上次的執行器,大家可以知道,一級快取是在BaseExecutor中實作的,而二級快取是在CachingExecutor中,二級快取開啟可以配置在xml中也可以在介面上加入@CacheNamespace注解,不了解的小伙伴可以看精通Mybatis之Executor執行器這篇文章,那我們接下來詳細講解一下mybatis的一級和二級快取,他們的命中場景,原始碼分析,和spring集成時快取失效的原因等,進入正題,

一級快取

這次小編先寫結論然后通過代碼示例證明,
一級快取資料結構:
通過底層原始碼可以知道快取的資料結構就是一個Map而且是HashMap,

命中場景

先看下圖:
在這里插入圖片描述
關于一級快取的命中可大致分為兩個場景,滿足所有運行引數,第二不觸發或不配置清空快取方法,
上面圖上很清楚就是得滿足上面兩個場景才可以的,
下面小編用示例代碼來說明,運行引數相關的代碼:

public class SqlSessionTest {
    private SqlSessionFactory factory;

    private SqlSession sqlSession;

    @Before
    public void init() throws SQLException {
        // 獲取構建器
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        // 決議XML 并構造會話工廠
        factory = factoryBuilder.build(ExecutorTest.class.getResourceAsStream("/mybatis-config.xml"));
        sqlSession = factory.openSession();
    }

    //不同會話
    @Test
    public void firstCacheTest() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        UserMapper mapper2 = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectByid(10);
        User user2 = mapper2.selectByid(10);
        System.out.println(user == user2);
    }
    //相同sql相同引數
    @Test
    public void firstCacheTest1() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectByid(10);
        User user2 = mapper.selectByid(10);
        System.out.println(user == user2);
    }
    //不同的statementId
    @Test
    public void firstCacheTest2() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //xxx.xxx.xxx.UserMapper.selectByid
        User user = mapper.selectByid(10);
        //xxx.xxx.xxx.UserMapper.selectByid3
        User user2 = mapper.selectByid3(10);
        System.out.println(user == user2);
    }
    //不同的RowBounds
    @Test
    public void firstCacheTest3() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectByid(10);
        RowBounds rowBounds = RowBounds.DEFAULT;
        List<User> userList = sqlSession.selectList("xxx.xxx.xxx.UserMapper.selectByid", 10, rowBounds);
        System.out.println(user == userList.get(0));
        rowBounds =new RowBounds(0,10);
        List<User> userList2 =sqlSession.selectList("xxx.xxx.xxx.UserMapper.selectByid",10,rowBounds);
        System.out.println(user == userList2.get(0));
    }
}

上面執行結果分別是:

//firstCacheTest
false
//firstCacheTest1
true
//firstCacheTest2
false
//firstCacheTest3
true
false

是不是很簡單,上面引數如果查詢的id不同當然命中不了快取了,這個小編就省略了
操作配置相關代碼示例

@Test
    public void firstCacheConfigTest() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectByid(10);
        sqlSession.clearCache();
        User user2 = mapper.selectByid(10);
        System.out.println(user == user2);
    }

    @Test
    public void firstCacheConfigTest1() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectByid3(10);
        User user2 = mapper.selectByid3(10);
        System.out.println(user == user2);
    }


    @Test
    public void firstCacheConfigTest2() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectByid(10);
        //無論update是哪個id都會清空
        mapper.setName(11,"ok");
        User user2 = mapper.selectByid(10);
        System.out.println(user == user2);
    }
	//上面firstCacheConfigTest1時加入了Options
 	@Select({" select * from users where id=#{1}"})
    @Options(flushCache = Options.FlushCachePolicy.TRUE)
    User selectByid3(Integer id);

上面執行結果分別是:

//firstCacheConfigTest
false
//firstCacheConfigTest1
false
//firstCacheConfigTest2
false

還有一個是全域的配置localCacheScope的配置STATEMENT注意這里需要大小寫,這樣快取也就失效了

<settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="localCacheScope" value="STATEMENT"/>
    </settings>

好了講完了一級快取的命中場景,咱們分析一下原始碼吧,

原始碼分析

前言中小編闡明了一級快取中BaseExecutor里面,下面小編先畫個快取邏輯操作的流程圖:
在這里插入圖片描述
上圖流程非常簡單,無法就是查詢的時候是否有快取有就回傳,沒有就使用子類查詢,查詢完畢后封裝進快取然后回傳結果,當然看原始碼的時候其實還有各種判斷,比方說會話是否關閉,請求的結果是否需要處理,包括是否要清除快取和請求引數快取等等,
原始碼閱讀以及關鍵注釋

public abstract class BaseExecutor implements Executor {
	protected int queryStack;
	private boolean closed;
	protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
	protected PerpetualCache localCache;
	protected PerpetualCache localOutputParameterCache;


  @SuppressWarnings("unchecked")
  @Override
  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()) {
      //清空快取 條件第一次查詢并且配置了flushCache=true,對子查詢不受影響
      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
        //情況快取 組態檔里快取作用域為STATEMENT 同樣對子查詢不受影響
        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;
  }
	@Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //更新操作
    clearLocalCache();
    return doUpdate(ms, parameter);
  }
  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
      	//回滾清空快取
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

  @Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    //提交情況快取
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }
}

下面是快取key的結構,這里就能明白為什么命中引數有那么多條件,這邊小編稍微說明一下,會話是不用再次說明的,環境引數在使用的時候一般不會多套,可以忽略
在這里插入圖片描述

這樣原始碼就和結論對起來了,注意clearLocalCache()清空所有一級快取,

spring集成時一級快取失效問題

很多人發現,mybatis集成spring一級快取后會話失效了,以為是spring Bug ,真正原因是Spring 對SqlSession進行了封裝,通過SqlSessionTemplae ,使得每次呼叫Sql,都會重新構建一個SqlSession,具體參見SqlSessionInterceptor,而根據前面所說的命中場景,一級快取必須是同一會話才能命中,所以在這些場景當中不能命中,
怎么解決呢,給Spring 添加事務即可,添加事務之后,SqlSessionInterceptor(會話攔截器)就會去判斷兩次請求是否在同一事務當中,如果是就會共用同一個SqlSession會話來解決,
在這里插入圖片描述

@Test
    public void testBySpring(){
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring.xml");
        UserMapper mapper = context.getBean(UserMapper.class);
        // mapper ->SqlSessionTemplate --> SqlSessionInterceptor-->SqlSessionFactory
        DataSourceTransactionManager transactionManager =
                (DataSourceTransactionManager) context.getBean("txManager");
        // 手動開啟事務
        TransactionStatus status = transactionManager
                .getTransaction(new DefaultTransactionDefinition());
        // 每次都會構造一個新會話 發起呼叫
        User user = mapper.selectByid(10);
        // 每次都會構造一個新會話 發起呼叫
        User user1 =mapper.selectByid(10);
        System.out.println(user == user1);
    }

上面如果沒有開啟事務,結果為false,開啟事務就為true
大家如果除錯代碼的話記得打斷點在
org.mybatis.spring.SqlSessionUtils#getSqlSession方法,下面是小編斷點的堆疊圖大家有空可以看一下

這邊插一嘴大家還記得mybatis和spring的集成原理嗎?可以看小編之前寫的文章

在這里插入圖片描述

二級快取

二級快取也稱作是應用級快取,與一級快取不同的,是它的作用范圍是整個應用,而且可以跨執行緒使用,所以二級快取有更高的命中率,適合快取一些修改較少的資料,在流程上是先訪問二級快取,再訪問一級快取,

快取的完整方案

核心功能包括存盤方案和溢位淘汰演算法
存盤方案:

  • 記憶體:最簡單就是在記憶體當中,不僅實作簡單,而且速度快,記憶體弊端就是不能持久化,且存盤有限,
  • 硬碟:可以持久化,容量大,但訪問速度不如記憶體,一般會結合記憶體一起使用,
  • 第三方集成:在分布式情況,如果想和其它節點共享快取,只能第三方軟體進行集成,比如Redis.

溢位淘汰

  • FIFO:先進先出
  • LRU:最近最少使用
  • WeakReference: 弱參考,將快取物件進行弱參考包裝,當Java進行gc的時候,不論當前的記憶體空間是否足夠,這個物件都會被回收
  • SoftReference:軟參考,與弱參考類似,不同在于只有當空間不足時GC才才回收軟參考物件,

非核心功能:

  • 過期清理:指清理存放資料過久的資料
  • 執行緒安全:保證快取可以被多個執行緒同時使用
  • 寫安全:當拿到快取資料后,可對其進行修改,而不影響原本的快取資料,通常采取做法是對快取物件進行深拷貝,

還有其他一些需求這邊小編就不一一舉例了,這個主要是對大家以后設計功能的時候的多重考慮,

Mybatis二級快取結構以及實作

上面小編說了設計快取需要一套完整的解決方案,那咱們來看一下Mybatis的二級快取是在如何完成以上功能的情況下還有很好的擴展和設計模式,首先我們來看下二級快取的結構圖(mybatis不止這些cache,大家有空自己研究一下,小編只是大致羅列):
在這里插入圖片描述
上面每一個功能都會對應一個組件類,并基于裝飾者加責任鏈的模式,將各個組件進行串聯,在執行快取的基本功能時,其它的快取邏輯會沿著這個責任鏈依次往下傳遞,
設計優點
1、職責單一:各個節點只負責自己的邏輯,不需要關心其它節點,
2、擴展性強:可根據需要擴展節點、洗掉節點,還可以調換順序保證靈活性,(PerpetualCache里面沒有delegate屬性)
3、松耦合:各節點之間不沒強制依賴其它節點,而是通過頂層的Cache介面進行間接依賴,
代碼示例

public class SecondCacheTest {
    private SqlSessionFactory factory;
    private SqlSession sqlSession;
    private Configuration configuration;

    @Before
    public void init() throws SQLException {
        // 獲取構建器
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        // 決議XML 并構造會話工廠
        factory = factoryBuilder.build(ExecutorTest.class.getResourceAsStream("/mybatis-config.xml"));
        sqlSession = factory.openSession();
        configuration = factory.getConfiguration();
    }
    @Test
    public void secondCacheTest(){
        Cache cache = configuration.getCache("xxx.xxx.xxx.UserMapper");
        cache.putObject("user",new User());
        cache.getObject("user");
    }

}

斷點除錯:
在這里插入圖片描述
這邊大家是否和小編一樣,那mybatis對這些快取的組裝是在哪兒的,然后各個快取組件做了什么功能?看原始碼:
首先是組件的實作以上面斷點除錯為例:(其他小伙伴自己看啊)
SynchronizedCache

//加入執行緒同步
public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

LoggingCache

  //啥都沒做
  @Override
  public void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }
  //取出來的時候做了命中率
  @Override
  public Object getObject(Object key) {
    requests++;
    final Object value = delegate.getObject(key);
    if (value != null) {
      hits++;
    }
    if (log.isDebugEnabled()) {
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
  }

SerializedCache(跨執行緒遠程呼叫的時候需要序列化,保證安全性同時序列化哈反序列話是需要時間,效率就會變慢)

 @Override
  public void putObject(Object key, Object object) {
    if (object == null || object instanceof Serializable) {
      delegate.putObject(key, serialize((Serializable) object));
    } else {
      throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
    }
  }

  @Override
  public Object getObject(Object key) {
    Object object = delegate.getObject(key);
    return object == null ? null : deserialize((byte[]) object);
  }

LruCache(默認溢位淘汰快取 最久沒用的淘汰)

public void setSize(final int size) {
	//使用linkedHashMap每次放入是最新的,當到達最大的數量時,將最久的移出即可
	//為什么使用LinkedHashMap,洗掉和添加的效率比較高
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }

  @Override
  public void putObject(Object key, Object value) {
    delegate.putObject(key, value);
    cycleKeyList(key);
  }

  @Override
  public Object getObject(Object key) {
  	//訪問后原本的順序就修改了
    keyMap.get(key); //touch
    return delegate.getObject(key);
  }
  private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

PerpetualCache

private Map<Object, Object> cache = new HashMap<>();
@Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

然后配置快取策略
二級默認快取默認是不開啟的,需要為其宣告快取空間才可以使用,通過@CacheNamespace 或為指定的MappedStatement宣告,宣告之后該快取為該Mapper所獨有,其它Mapper不能訪問,如需要多個Mapper共享一個快取空間可通過@CacheNamespaceRef 或進行參考同一個快取空間,@CacheNamespace 詳細配置見下表:

配置說明
implementation指定快取的存盤實作類,默認是用HashMap存盤在記憶體當中(PerpetualCache)
eviction指定快取溢位淘汰實作類,默認LRU ,清除最少使用
flushInterval設定快取定時全部清空時間,默認不清空,
size指定快取容量,超出后就會按eviction指定演算法進行淘汰
readWritetrue即通過序列化復制,來保證快取物件是可讀寫的,默認true
blocking為每個Key的訪問添加阻塞鎖,防止快取擊穿
properties為上述組件,配置額外引數,key對應組件中的欄位名,Property values for a implementation object.

注:Cache中責任鏈條的組成即通過@CacheNamespace 指導生成,具體邏輯詳見CacheBuilder
大家可以對快取做擴展,在快取策略中修改@CacheNamespace指定的引數后,比方說將implementation 指定為第三方存盤(需要實作Cache介面)等,其實在呼叫的時候完全沒有影響,大家可以試著做一下修改,這邊小編其實在學習程序中做了一系列改動的,包括改動淘汰溢位策略等等,這邊就沒貼出原始碼了,希望各位小伙伴試一下,來增加印象,

快取其他配置
除@CacheNamespace 還可以通過其它引數來控制二級快取()

欄位配置域說明
cacheEnabled二級快取全域開關,默認開啟
useCache<select/update/insert/delete>指定的statement是否開啟,默認開啟
flushCache<select/update/insert/delete>執行sql前是否清空當前二級快取空間,update默認true,query默認false
< cache/>快取空間與@CacheNamespace類似,如果xml和mapper同時配置會報錯
< cache-ref/>參考快取空間 與@CacheNamespaceRef類似

@CacheNamespace和@CacheNamespaceRef的區別以及使用

注意:< cache/>與@CacheNamespace是不能同時用的會報錯(用在相同的namespace里面),如果介面里面的方法查詢走的是xml則@CacheNamespace不起作用,那就需要使用到< cache-ref/>配置了可能這么說大家不明白,那小編下面給了代碼示例,或者反一些也行,即介面里面用@CacheNamespaceRef 注解xml中用 < cache/>,同時注意CacheNamespaceRef 必須指定name或value屬性

@CacheNamespace
public interface UserMapper {

    @Select({" select * from users where id=#{1}"})
    User selectByid(Integer id);
	//這個不會被二級快取
    List<User> selectByUser(User user);
}

在xml中配置

 <cache-ref namespace="xxx.xxx.xxx.UserMapper"/>
    <select id="selectByUser" resultMap="result_user" parameterMap="paramter_user">
        select * from users where 1=1
        <if test="id!=null">
            and id=#{id}
        </if>
        <if test="name!=null">
            and name=#{name}
        </if>
        <if test="age!=null">
            and age=#{age}
        </if>

    </select>

二級快取命中場景

二級快取命中條件先看下圖(除了一個條件與一級快取不同其他都差不多):
在這里插入圖片描述
這邊小撰寫了一個代碼示例(會話提交必須手動提交后才可以):

	@Test
    public void hitRateTest(){
        //兩個會話
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectByid(10);
        //需要提交,否則不會命中
        sqlSession.commit();
        UserMapper mapper2 = sqlSession.getMapper(UserMapper.class);
        User user2 = mapper2.selectByid(10);
    }

執行結果
在這里插入圖片描述
這邊只能通過日志查看,不可能通過兩個user相同,因為里面會經過序列化快取,從上圖可以看出命中率,第一次查詢為0,第二次命中,那命中率的演算法就是命中次數除以請求數,所以為0.5,

為什么需要提交后才能命中快取?

二級快取命中與一級快取唯一不同的引數條件就是得提交,
在這里插入圖片描述

如上圖兩個會話在修改同一資料,當會話二修改后,假如它實時填充到二級快取,而會話一就能過快取獲取修改之后的資料,但實質是修改的資料回滾了,并沒真正的提交到資料庫,這樣就產生了臟讀,所以為了保證資料一致性,二級快取必須是會話提交之才會真正填充,包括對快取的清空,也必須是會話正常提交之后才生效,
要滿足上面的條件,二級快取的結構設計又上升了一個難度,為了實作會話提交之后才變更二級快取,MyBatis對每個會話設立了若干個暫存區,當前的會話對指定快取空間的變更,都存放在對應的暫存區,當前會話提交之后才會提交到每個暫存區對應的快取空間,每個會話都有一個唯一的事務快取管理器,來統一管理這些暫存區,這里暫存區也可叫做事務快取,
下面小編使用一張圖來說明上面的文字:
在這里插入圖片描述
證明:
在這里插入圖片描述

二級快取執行流程

原本會話是通過Executor實作SQL呼叫,這里基于裝飾器模式使用CachingExecutor對SQL呼叫邏輯進行攔截,然后嵌入二級快取相關邏輯,流程圖如下

在這里插入圖片描述

查詢 query

當會話呼叫query() 時,會基于查詢陳述句、引數等資料組成快取Key,然后嘗試從二級快取中讀取資料,讀到就直接回傳,沒有就呼叫被裝飾的Executor去查詢資料庫,然后填充至對應的暫存區,

更新 update

當執行update操作時,同樣會基于查詢的陳述句和引陣列成快取KEY,然后在執行update之前清空快取,這里清空只針對暫存區,同時記錄清空的標記,以便當會話提交之時,依據該標記去清空二級快取空間,

提交 commit

當會話執行commit操作后,會將該會話下所有暫存區的變更,更新到對應二級快取空間去,

原始碼閱讀

大家可以根據以下示例除錯,具體源代碼就不貼出來了:

@Test
    public void hitRateTest3(){
        //兩個會話
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //同一個會話查詢的
        User user = mapper.selectByid(10);
        sqlSession.commit();
        User user = mapper.selectByid(10);
         System.out.println("第一個會話查詢提交==="+user);
        mapper.setName(10,"bob");
        User user = mapper.selectByid(10);
        System.out.println("第一個會話沒提交update查詢==="+user);
        UserMapper mapper2 = factory.openSession().getMapper(UserMapper.class);
        User user1 = mapper2.selectByid(10);
        System.out.println("第二個會話查詢第一個還沒提交update==="+user1);
        sqlSession.commit();
        User user2 = mapper2.selectByid(10);
        System.out.println("第二個會話查詢第一個提交update的==="+user2);
    }

大家一定要好好走一遍啊,會涉及到很多細節的,如果是口述還可以如果是文字的話小編不斷貼代碼反而會繞暈大家的,

總結

今天小編講mybatis的多級快取體系一網打盡了,文章有點長,如果看起來就枯燥乏味了,下次小編想著講這樣的文章分為幾篇講解,這樣會不會更好,好了今天就到這兒,如果你能堅持到最后,并且完全理解那你就是最棒的,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/277829.html

標籤:其他

上一篇:架構師成長記_第六周_06_Redis 發布與訂閱 (與MQ類似)

下一篇:20面試官21-04-17

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more