主頁 > 後端開發 > 吃透Mybatis原始碼-快取的理解(三)

吃透Mybatis原始碼-快取的理解(三)

2022-01-06 08:36:27 後端開發

來來來,給俏如來扎起,感謝老鐵們對俏如來的支持,2021一路有你,2022我們繼續加油!你的肯定是我最大的動力

博主在參加博客之星評比,點擊鏈接 , https://bbs.csdn.net/topics/603957267 瘋狂打Call!五星好評 ????? 感謝


前言

對于Mybatis的快取在上一章節《吃透Mybatis原始碼-Mybatis執行流程》我們有提到一部分,這篇文章我們對將詳細分析一下Mybatis的一級快取和二級快取,

一級快取

市面上流行的ORM框架都支持快取,不管是Hibernate還是Mybatis都支持一級快取和二級快取,目的是把資料快取到JVM記憶體中,減少和資料庫的互動來提高查詢速度,同時MyBatis還可以整合三方快取技術,

Mybatis一級緩默認開啟,是SqlSession級別的,也就是說需要同一個SqlSession執行同樣的SQL和引數才有可能命中快取,如:
在這里插入圖片描述
同一個SqlSession執行同一個SQL,發現控制臺日志只執行了一次SQL記錄,說明第二次查詢是走快取了,但是要注意的是,當SqlSession執行了delete,update,insert陳述句后,快取會被清除,

那么一級快取在哪兒呢?下面給大家介紹一個類,
在這里插入圖片描述
Mybatis中提供的快取都是Cache的實作類,但是真正實作快取的是PerpetualCache,其中維護了一個Map<Object, Object> cache = new HashMap<Object, Object>() 結構來快取資料,其他的快取類采用了裝飾模式對PerpetualCache做增強,比如:LruCache 在PerpetualCache 的基礎上增加了最近最少使用的快取清楚策略,當快取到達上限時候,洗掉最近最少使用的快取 (Least Recently Use),代碼如下

public class LruCache implements Cache {
	//對 PerpetualCache 做裝飾
  private final Cache delegate;

下面對其他的快取類做一個介紹

  • PerpetualCache : 基礎快取類
  • LruCache : LRU 策略的快取 當快取到達上限時候,洗掉最近最少使用的快取 (Least Recently Use),eviction=“LRU”(默 認)
  • FifoCache : FIFO 策略的快取 當快取到達上限時候,洗掉最先入隊的快取,配置eviction=“FIFO”
  • SoftCache WeakCache :帶清理策略的快取 通過 JVM 的軟參考和弱參考來實作快取,當 JVM 記憶體不足時,會自動清理掉這些快取,基于 SoftReference 和 WeakReference
  • SynchronizedCache : 同步快取 基于 synchronized 關鍵字實作,解決并發問題
  • ScheduledCache : 定時調度的快取,在進行 get/put/remove/getSize 等操作前,判斷 快取時間是否超過了設定的最長快取時間(默認是 一小時),如果是則清空快取–即每隔一段時間清 空一次快取
  • SerializedCache :支持序列化的快取 將物件序列化以后存到快取中,取出時反序列化
  • TransactionalCache :事務快取,在二級快取中使用,可一次存入多個快取,移除多個快取 ,通過TransactionalCacheManager 中用 Map 維護對應關系,

一級快取到底存盤在哪兒?

一級快取在SimpleExecutor 的父類 BaseExecutor 執行器中,如下

public abstract class BaseExecutor implements Executor {

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

  protected Transaction transaction;
  protected Executor wrapper;

  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  //一級快取
  protected PerpetualCache localCache;

PerpetualCache快取類原始碼如下

public class PerpetualCache implements Cache {

  private final String id;
  //快取
  private Map<Object, Object> cache = new HashMap<Object, Object>();

那么一級快取在什么時候創建的?

在 BaseExecutor 中的構造器中創建了一級快取,而執行器Executor 是保存在SqlSession中的,也就是說當創建SqlSession的時候,就會創建 SimpleExecutor,而在SimpleExecutor的構造器中會呼叫BaseExecutor的構造器來創建一級快取,見:org.apache.ibatis.executor.SimpleExecutor#SimpleExecutor

public class SimpleExecutor extends BaseExecutor {
	//執行器構造器
  public SimpleExecutor(Configuration configuration, Transaction transaction) {
  	//呼叫父類構造器
    super(configuration, transaction);
  }

下面是 BaseExecutor 的執行器 org.apache.ibatis.executor.BaseExecutor#BaseExecutor

public abstract class BaseExecutor implements Executor {

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

  protected Transaction transaction;
  protected Executor wrapper;

  //一級快取
  protected PerpetualCache localCache;
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;


  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
    //創建一級快取
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

一級快取怎么存盤的?

一級快取是在執行查詢的時候會先走二級快取,二級快取么有就會走一級快取,以及快取沒有就會走資料庫查詢,然后放入一級快取和二級快取,我們來看一下原始碼流程 ,見:org.apache.ibatis.executor.CachingExecutor#query

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //構建快取的Key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    //執行查詢
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

這里在嘗試構建Cachekey ,cachekey時由:MappedStatement的id(如:cn.xx.xx.xxMapper.selectByid) ,分頁,Sql,引數值一起構建而成的,一級二級快取都是如此,

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //開啟了二級快取才會存在Cache  
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        //走二級快取查詢資料
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          //二級快取沒有,走資料庫查詢資料
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //寫入二級快取
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

這里我們看到,在執行org.apache.ibatis.executor.CachingExecutor#query 查詢的時候會先走二級快取,二級快取沒有會繼續呼叫 org.apache.ibatis.executor.BaseExecutor#query 查詢,而BaseExecutor#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;
  }

上面代碼會先走一級快取拿資料,如果一級快取沒有,就走資料庫獲取資料,然后加入一級快取org.apache.ibatis.executor.BaseExecutor#queryFromDatabase

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;
  }

到這里我們就看到了一級快取和二級快取的執行流程,注意的是:先執行二級快取再執行一級快取,

這里畫一個一級快取的圖
在這里插入圖片描述

二級快取

第一步:二級快取需要在mybatis-config.xml 配置中開啟,如下

<setting name="cacheEnabled" value="true"/>

當然其實該配置默認是開啟的,也就是默認會使用 CachingExecutor 裝飾基本的執行器,
第二步驟:需要在mapper.xml中配置 < cache/>如下

<mapper namespace="cn.whale.mapper.StudentMapper">
	<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
		 size="1024" 
		 eviction="LRU" 
		 flushInterval="120000" 
		 readOnly="false"/> 
...省略...

解釋一下上面的配置,首先<cache/> 是在某個mapper.xml中指定的,也就是說二級快取作用于當前的namespace.

  • type : 代表的是使用什么型別的快取,只要是實作了 Cache 介面的實作類都可以
  • size :快取的個數,默認是1024 個物件
  • eviction : 快取剔除策略 ,LRU – 最近最少使用的:移除最長時間不被使用的物件(默認);FIFO – 先進先出:按物件進入快取的順序來移除它們 ;SOFT – 軟參考:移除基于垃圾回收器狀態和軟參考規則的物件;WEAK – 弱參考:更積極地移除基于垃圾收集器狀態和弱參考規則的物件
  • flushInterval :定時自動清空快取間隔 自動重繪時間,單位 ms,未配置時只有呼叫時重繪
  • readOnly :快取時候只讀
  • blocking :是否使用可重入鎖實作 快取的并發控制 true,會使用 BlockingCache 對 Cache 進行裝飾 默認 false

Mapper.xml 配置了之后,select()會被快取,update()、delete()、insert() 會重繪快取,下面是測驗案例
在這里插入圖片描述

可以看到,這里使用了2個SqlSesion 2次執行了相同的SQL,引數相同,看控制臺日志只執行了一次SQL,說明是命中的二級快取,因為滿足條件:同一個 namespace下的相同的SQL被執行,盡管使用的SqlSession不是同一個,

但是你可能注意到一個細節,就是session.commit() 為什么要提交事務呢?這就要說到二級快取的存盤結構了,如果不執行commit是不會寫入二級快取的,在 CachingExecutor 中有一個屬性private final TransactionalCacheManager tcm = new TransactionalCacheManager(); 看名字肯能夠看出二級快取和事務有關系,結構如下

public class CachingExecutor implements Executor {

  private final Executor delegate;
  //二級快取,通過TransactionalCacheManager來管理
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

TransactionalCacheManager 中維護了一個 HashMap<Cache, TransactionalCache>()

public class TransactionalCacheManager {
  //二級快取的HashMap
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();

在TransactionCache中維護了一個 Map<Object, Object> entriesToAddOnCommit;

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;

  ...省略...
  //寫入二級快取
  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

當執行查詢的時候,從資料庫查詢出來資料回寫入TransactionalCache的entriesToAddOnCommit中,我們來看一下二級快取寫入的流程,見:org.apache.ibatis.executor.CachingExecutor#query

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
      //如果mapper.xml配置了 <cache/> 就會創建 Cache
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        //從二級快取獲取
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //寫入二級快取
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

如果mapper.xml配置了 就會創建 Cache,Cache不為null,才會走到二級快取的流程,此時代碼來到org.apache.ibatis.cache.TransactionalCacheManager#putObject

public class TransactionalCacheManager {
  //存盤二級快取
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();

	public void putObject(Cache cache, CacheKey key, Object value) {
	//通過cache為key拿到 TransactionalCache ,把資料put進去
    getTransactionalCache(cache).putObject(key, value);
  }

存盤資料的是TransactionalCache ,見org.apache.ibatis.cache.decorators.TransactionalCache#putObject

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;

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

我們看到,資料寫到了 TransactionalCache#entriesToAddOnCommit 一個Map中,只有在執行commit的時候資料才會真正寫入二級快取,

我們來看下SqlSession.commit方法是如何觸發二級快取真正的寫入的,見:org.apache.ibatis.session.defaults.DefaultSqlSession#commit()

  @Override
  public void commit() {
    commit(false);
  }

  @Override
  public void commit(boolean force) {
    try {
    //呼叫執行器提交事務
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

代碼來到org.apache.ibatis.executor.CachingExecutor#commit

@Override
  public void commit(boolean required) throws SQLException {
    //提交事務
    delegate.commit(required);
    //呼叫org.apache.ibatis.cache.TransactionalCacheManager#commit提交事務
    tcm.commit();
  }

代碼來到org.apache.ibatis.cache.TransactionalCacheManager#commit

public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      //呼叫 TransactionalCache#commit
      txCache.commit();
    }
  }

代碼來到org.apache.ibatis.cache.decorators.TransactionalCache#commit

public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);
  //真正的二級快取存盤位置,本質是一個 PerpetualCache
  private final Cache delegate;
  //臨時存盤二級快取
  private final Map<Object, Object> entriesToAddOnCommit;
  
  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    //這里在寫入快取,保存到TransactionalCache中的delegate欄位,本質是一個PerpetualCache
    flushPendingEntries();
    //把entriesToAddOnCommit清除掉
    reset();
  }
  
  private void flushPendingEntries() {
	    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
	      //從entriesToAddOnCommit中拿到臨時的快取資料,寫入快取,最侄訓寫入PerpetualCache#cache欄位中
	      delegate.putObject(entry.getKey(), entry.getValue());
	    }
	    for (Object entry : entriesMissedInCache) {
	      if (!entriesToAddOnCommit.containsKey(entry)) {
	        delegate.putObject(entry, null);
	      }
	    }
   }
	
	private void reset() {
	    clearOnCommit = false;
	    //清除entriesToAddOnCommit
	    entriesToAddOnCommit.clear();
	    entriesMissedInCache.clear();
  }

所以我們總結一下二級快取的寫入流程,二級快取通過 TransactionalCacheManager中的一個Map<Cache, TransactionalCache>管理的,當執行query查詢處資料的時候,會把資料寫入TransactionalCache中的 Map<Object, Object> entriesToAddOnCommit 中臨時存盤,當執行commit的時候才會把entriesToAddOnCommit中的資料寫入TransactionalCache中的 Cache delegate ,其本質和一級快取一樣,也是一個 PerpetualCache

當我們做第二次query的時候會嘗試通過 TransactionalCacheManager#getObject 從二級快取獲取資料

public class TransactionalCacheManager {

  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
  //獲取二級快取
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }

然后會從 TransactionalCache中的delegate中獲取快取

public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);
 //二級快取
  private final Cache delegate;
  ...省略...
  
  @Override
  public Object getObject(Object key) {
    // issue #116
    //從二級快取獲取資料
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

所以記得,二級快取一定要commit才會起作用,下面花了一個一級快取和二級快取的結構圖
在這里插入圖片描述

三方快取框架

除了使用Mybatis自帶的快取,也可以使用第三方快取方式,比如:比如 ehcache 和 redis 下面以Redis為例 ,首先匯入mybatis整合redis的依賴

<dependency>
	 <groupId>org.mybatis.caches</groupId>
	 <artifactId>mybatis-redis</artifactId> 
	 <version>1.0.0-beta2</version> 
 </dependency>

第二步驟:在mapper.xml配置快取

<cache type="org.mybatis.caches.redis.RedisCache" 
	eviction="FIFO" 
	flushInterval="60000" 
	size="512" readOnly="true"/>

這里type使用了RedisCache,RedisCache也是實作了Cache介面的,接著我們需要配置Redis的鏈接屬性,默認RedisCache類會讀取名字為 : redis.properties 的組態檔

host=127.0.0.1
password=123456
port=6379
connectionTimeout=5000
soTimeout=5000
database=0

再次執行測驗代碼,查看Redis效果如下
在這里插入圖片描述
博主在參加博客之星評比,點擊鏈接 , https://bbs.csdn.net/topics/603957267 瘋狂打Call!五星好評 ????? 感謝

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

標籤:java

上一篇:【java高性能編程基礎】 - jdk提供的三對執行緒通信的等待/通知機制api

下一篇:GitLab + Jenkins + Maven + Tomcat 實作自動集成、打包、部署

標籤雲
其他(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)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more