concurrenthashmap實現原理_Mybatis:PageHelper分頁插件源碼及原理剖析

PageHelper是一款好用的開源免費的Mybatis第三方物理分頁插件,其實我并不想加上好用兩個字,但是為了表揚插件作者開源免費的崇高精神,我毫不猶豫的加上了好用一詞作為贊美。

原本以為分頁插件,應該是很簡單的,然而PageHelper比我想象的要復雜許多,它做的很強大,也很徹底,強大到使用者可能并不需要這么多功能,徹底到一參可以兩用。但是,我認為,作為分頁插件,完成物理分頁任務是根本,其它的很多智能并不是必要的,保持它夠傻夠憨,專業術語叫stupid,簡單就是美。

我們將簡單介紹PageHelper的基本使用和配置參數的含義,重點分析PageHelper作為Mybatis分頁插件的實現原理。

1. PageHelper的maven依賴及插件配置

com.github.pagehelper pagehelper 4.1.6

PageHelper除了本身的jar包外,它還依賴了一個叫jsqlparser的jar包,使用時,我們不需要單獨指定jsqlparser的maven依賴,maven的間接依賴會幫我們引入。

1d42c223f3d3c2cab460e27a9f684a48.png

上面是PageHelper官方給的配置和注釋,雖然寫的很多,不過確實描述的很明白。

dialect:標識是哪一種數據庫,設計上必須。

offsetAsPageNum:將RowBounds第一個參數offset當成pageNum頁碼使用,這就是上面說的一參兩用,個人覺得完全沒必要,offset = pageSize * pageNum就搞定了,何必混用參數呢?

rowBoundsWithCount:設置為true時,使用RowBounds分頁會進行count查詢,個人覺得完全沒必要,實際開發中,每一個列表分頁查詢,都配備一個count數量查詢即可。

reasonable:value=true時,pageNum小于1會查詢第一頁,如果pageNum大于pageSize會查詢最后一頁 ,個人認為,參數校驗在進入Mybatis業務體系之前,就應該完成了,不可能到達Mybatis業務體系內參數還帶有非法的值。

這么一來,我們只需要記住 dialect = mysql 一個參數即可,其實,還有下面幾個相關參數可以配置。

autoDialect:true or false,是否自動檢測dialect。

autoRuntimeDialect:true or false,多數據源時,是否自動檢測dialect。

closeConn:true or false,檢測完dialect后,是否關閉Connection連接。

上面這3個智能參數,不到萬不得已,我們不應該在系統中使用,我們只需要一個dialect = mysql 或者 dialect = oracle就夠了,如果系統中需要使用,還是得問問自己,是否真的非用不可。

2. PageHelper源碼分析

@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))public class PageHelper implements Interceptor { //sql工具類 private SqlUtil sqlUtil; //屬性參數信息 private Properties properties; //配置對象方式 private SqlUtilConfig sqlUtilConfig; //自動獲取dialect,如果沒有setProperties或setSqlUtilConfig,也可以正常進行 private boolean autoDialect = true; //運行時自動獲取dialect private boolean autoRuntimeDialect; //多數據源時,獲取jdbcurl后是否關閉數據源 private boolean closeConn = true; //緩存 private Map urlSqlUtilMap = new ConcurrentHashMap(); private ReentrantLock lock = new ReentrantLock();// ...}

上面是官方源碼以及源碼所帶的注釋,我們再補充一下。

SqlUtil:數據庫類型專用sql工具類,一個數據庫url對應一個SqlUtil實例,SqlUtil內有一個Parser對象,如果是mysql,它是MysqlParser,如果是oracle,它是OracleParser,這個Parser對象是SqlUtil不同實例的主要存在價值。執行count查詢、設置Parser對象、執行分頁查詢、保存Page分頁對象等功能,均由SqlUtil來完成。

SqlUtilConfig:Spring Boot中使用,忽略。

autoRuntimeDialect:多個數據源切換時,比如mysql和oracle數據源同時存在,就不能簡單指定dialect,這個時候就需要運行時自動檢測當前的dialect。

Map urlSqlUtilMap:它就用來緩存autoRuntimeDialect自動檢測結果的,key是數據庫的url,value是SqlUtil。由于這種自動檢測只需要執行1次,所以做了緩存。

ReentrantLock lock:這個lock對象是比較有意思的現象,urlSqlUtilMap明明是一個同步ConcurrentHashMap,又搞了一個lock出來同步ConcurrentHashMap做什么呢?是否是畫蛇添足?

在《Java并發編程實戰》一書中有詳細論述,簡單的說,ConcurrentHashMap可以保證put或者remove方法一定是線程安全的,但它不能保證put、get、remove的組合操作是線程安全的,為了保證組合操作也是線程安全的,所以使用了lock。

com.github.pagehelper.PageHelper.java源碼。

 // Mybatis攔截器方法  public Object intercept(Invocation invocation) throws Throwable { if (autoRuntimeDialect) { // 多數據源 SqlUtil sqlUtil = getSqlUtil(invocation); return sqlUtil.processPage(invocation); } else { // 單數據源 if (autoDialect) { initSqlUtil(invocation); } // 指定了dialect return sqlUtil.processPage(invocation); } } public synchronized void initSqlUtil(Invocation invocation) { if (this.sqlUtil == null) { this.sqlUtil = getSqlUtil(invocation); if (!autoRuntimeDialect) { properties = null; sqlUtilConfig = null; } autoDialect = false; } } public void setProperties(Properties p) { checkVersion(); //多數據源時,獲取jdbcurl后是否關閉數據源 String closeConn = p.getProperty("closeConn"); //解決#97 if(StringUtil.isNotEmpty(closeConn)){ this.closeConn = Boolean.parseBoolean(closeConn); } //初始化SqlUtil的PARAMS SqlUtil.setParams(p.getProperty("params")); //數據庫方言 String dialect = p.getProperty("dialect"); String runtimeDialect = p.getProperty("autoRuntimeDialect"); if (StringUtil.isNotEmpty(runtimeDialect) && runtimeDialect.equalsIgnoreCase("TRUE")) { this.autoRuntimeDialect = true; this.autoDialect = false; this.properties = p; } else if (StringUtil.isEmpty(dialect)) { autoDialect = true; this.properties = p; } else { autoDialect = false; sqlUtil = new SqlUtil(dialect); sqlUtil.setProperties(p); } } public SqlUtil getSqlUtil(Invocation invocation) { MappedStatement ms = (MappedStatement) invocation.getArgs()[0]; //改為對dataSource做緩存 DataSource dataSource = ms.getConfiguration().getEnvironment().getDataSource(); String url = getUrl(dataSource); if (urlSqlUtilMap.containsKey(url)) { return urlSqlUtilMap.get(url); } try { lock.lock(); if (urlSqlUtilMap.containsKey(url)) { return urlSqlUtilMap.get(url); } if (StringUtil.isEmpty(url)) { throw new RuntimeException("無法自動獲取jdbcUrl,請在分頁插件中配置dialect參數!"); } String dialect = Dialect.fromJdbcUrl(url); if (dialect == null) { throw new RuntimeException("無法自動獲取數據庫類型,請通過dialect參數指定!"); } SqlUtil sqlUtil = new SqlUtil(dialect); if (this.properties != null) { sqlUtil.setProperties(properties); } else if (this.sqlUtilConfig != null) { sqlUtil.setSqlUtilConfig(this.sqlUtilConfig); } urlSqlUtilMap.put(url, sqlUtil); return sqlUtil; } finally { lock.unlock(); } }

autoRuntimeDialect:多數據源,會創建多個SqlUtil。

autoDialect:單數據源,只會創建1個SqlUtil。單數據源時,也可以當做多數據源來使用。

指定了dialect:只會創建1個SqlUtil。

3. PageSqlSource

public abstract class PageSqlSource implements SqlSource { /** * 獲取正常的BoundSql * * @param parameterObject * @return */ protected abstract BoundSql getDefaultBoundSql(Object parameterObject); /** * 獲取Count查詢的BoundSql * * @param parameterObject * @return */ protected abstract BoundSql getCountBoundSql(Object parameterObject); /** * 獲取分頁查詢的BoundSql * * @param parameterObject * @return */ protected abstract BoundSql getPageBoundSql(Object parameterObject); /** * 獲取BoundSql * * @param parameterObject * @return */ @Override public BoundSql getBoundSql(Object parameterObject) { Boolean count = getCount(); if (count == null) { return getDefaultBoundSql(parameterObject); } else if (count) { return getCountBoundSql(parameterObject); } else { return getPageBoundSql(parameterObject); } }}

getDefaultBoundSql:獲取原始的未經改造的BoundSql。

getCountBoundSql:不需要寫count查詢,插件根據分頁查詢sql,智能的為你生成的count查詢BoundSql。

getPageBoundSql:獲取分頁查詢的BoundSql。

舉例:

DefaultBoundSql:

select stud_id as studId , name, email, dob, phone from students

CountBoundSql:

select count(0) from students --由PageHelper智能完成

PageBoundSql:

select stud_id as studId , name, email, dob, phone from students limit ?, ?
644c3c9ecdd617f2fe5fb62b3b66f412.png
public class PageStaticSqlSource extends PageSqlSource { private String sql; private List parameterMappings; private Configuration configuration; private SqlSource original; @Override protected BoundSql getDefaultBoundSql(Object parameterObject) { String tempSql = sql; String orderBy = PageHelper.getOrderBy(); if (orderBy != null) { tempSql = OrderByParser.converToOrderBySql(sql, orderBy); } return new BoundSql(configuration, tempSql, parameterMappings, parameterObject); } @Override protected BoundSql getCountBoundSql(Object parameterObject) { // localParser指的就是MysqlParser或者OracleParser // localParser.get().getCountSql(sql),可以根據原始的sql,生成一個count查詢的sql return new BoundSql(configuration, localParser.get().getCountSql(sql), parameterMappings, parameterObject); } @Override protected BoundSql getPageBoundSql(Object parameterObject) { String tempSql = sql; String orderBy = PageHelper.getOrderBy(); if (orderBy != null) { tempSql = OrderByParser.converToOrderBySql(sql, orderBy); } // getPageSql可以根據原始的sql,生成一個帶有分頁參數信息的sql,比如 limit ?, ? tempSql = localParser.get().getPageSql(tempSql); // 由于sql增加了分頁參數的?號占位符,getPageParameterMapping()就是在原有List基礎上,增加兩個分頁參數對應的ParameterMapping對象,為分頁參數賦值使用 return new BoundSql(configuration, tempSql, localParser.get().getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject); }}

假設List原來的size=2,添加分頁參數后,其size=4,具體增加多少個,看分頁參數的?號數量。

其他PageSqlSource,原理和PageStaticSqlSource一模一樣。

解析sql,并增加分頁參數占位符,或者生成count查詢的sql,都依靠Parser來完成。

4. com.github.pagehelper.parser.Parser

f44784f3cbb3ddaa9d0a379a43c3caf0.png
public class MysqlParser extends AbstractParser { @Override public String getPageSql(String sql) { StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); sqlBuilder.append(sql); sqlBuilder.append(" limit ?,?"); return sqlBuilder.toString(); } @Override public Map setPageParameter(MappedStatement ms, Object parameterObject, BoundSql boundSql, Page> page) { Map paramMap = super.setPageParameter(ms, parameterObject, boundSql, page); paramMap.put(PAGEPARAMETER_FIRST, page.getStartRow()); paramMap.put(PAGEPARAMETER_SECOND, page.getPageSize()); return paramMap; }}

我們可以清楚的看到,MysqlParser是如何添加分頁占位符和分頁參數的。

public abstract class AbstractParser implements Parser, Constant { public String getCountSql(final String sql) { return sqlParser.getSmartCountSql(sql); }}

生成count sql,則是前文提到的jsqlparser工具包來完成的,是另外一個開源的sql解析工具包。

5. SqlUtil.doProcessPage()分頁查詢

// PageSqlSource裝飾原SqlSource public void processMappedStatement(MappedStatement ms) throws Throwable { SqlSource sqlSource = ms.getSqlSource(); MetaObject msObject = SystemMetaObject.forObject(ms); SqlSource pageSqlSource; if (sqlSource instanceof StaticSqlSource) { pageSqlSource = new PageStaticSqlSource((StaticSqlSource) sqlSource); } else if (sqlSource instanceof RawSqlSource) { pageSqlSource = new PageRawSqlSource((RawSqlSource) sqlSource); } else if (sqlSource instanceof ProviderSqlSource) { pageSqlSource = new PageProviderSqlSource((ProviderSqlSource) sqlSource); } else if (sqlSource instanceof DynamicSqlSource) { pageSqlSource = new PageDynamicSqlSource((DynamicSqlSource) sqlSource); } else { throw new RuntimeException("無法處理該類型[" + sqlSource.getClass() + "]的SqlSource"); } msObject.setValue("sqlSource", pageSqlSource); //由于count查詢需要修改返回值,因此這里要創建一個Count查詢的MS msCountMap.put(ms.getId(), MSUtils.newCountMappedStatement(ms)); }// 執行分頁查詢private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable { //保存RowBounds狀態 RowBounds rowBounds = (RowBounds) args[2]; //獲取原始的ms MappedStatement ms = (MappedStatement) args[0]; //判斷并處理為PageSqlSource if (!isPageSqlSource(ms)) { processMappedStatement(ms); } //設置當前的parser,后面每次使用前都會set,ThreadLocal的值不會產生不良影響 ((PageSqlSource)ms.getSqlSource()).setParser(parser); try { //忽略RowBounds-否則會進行Mybatis自帶的內存分頁 args[2] = RowBounds.DEFAULT; //如果只進行排序 或 pageSizeZero的判斷 if (isQueryOnly(page)) { return doQueryOnly(page, invocation); } //簡單的通過total的值來判斷是否進行count查詢 if (page.isCount()) { page.setCountSignal(Boolean.TRUE); //替換MS args[0] = msCountMap.get(ms.getId()); //查詢總數 Object result = invocation.proceed(); //還原ms args[0] = ms; //設置總數 page.setTotal((Integer) ((List) result).get(0)); if (page.getTotal() == 0) { return page; } } else { page.setTotal(-1l); } //pageSize>0的時候執行分頁查詢,pageSize<=0的時候不執行相當于可能只返回了一個count if (page.getPageSize() > 0 && ((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0) || rowBounds != RowBounds.DEFAULT)) { //將參數中的MappedStatement替換為新的qs page.setCountSignal(null); BoundSql boundSql = ms.getBoundSql(args[1]); args[1] = parser.setPageParameter(ms, args[1], boundSql, page); page.setCountSignal(Boolean.FALSE); //執行分頁查詢 Object result = invocation.proceed(); //得到處理結果 page.addAll((List) result); } } finally { ((PageSqlSource)ms.getSqlSource()).removeParser(); } //返回結果 return page; }

源碼中注意關鍵的四點即可:

1、msCountMap.put(ms.getId(), MSUtils.newCountMappedStatement(ms)),創建count查詢的MappedStatement對象,并緩存于msCountMap。

2、如果count=true,則執行count查詢,結果total值保存于page對象中,繼續執行分頁查詢。

3、執行分頁查詢,將查詢結果保存于page對象中,page是一個ArrayList對象。

4、args[2] = RowBounds.DEFAULT,改變Mybatis原有分頁行為;

args[1] = parser.setPageParameter(ms, args[1], boundSql, page),改變原有參數列表(增加分頁參數)。

6. PageHelper的兩種使用方式

6e69be19f4cd49656f54de55e68c6482.png

第一種、直接通過RowBounds參數完成分頁查詢 。

List list = studentMapper.find(new RowBounds(0, 10));Page page = ((Page) list;

第二種、PageHelper.startPage()靜態方法

//獲取第1頁,10條內容,默認查詢總數count PageHelper.startPage(1, 10);//緊跟著的第一個select方法會被分頁 List list = studentMapper.find(); Page page = ((Page) list;

注:返回結果list,已經是Page對象,Page對象是一個ArrayList。

原理:使用ThreadLocal來傳遞和保存Page對象,每次查詢,都需要單獨設置PageHelper.startPage()方法。

public class SqlUtil implements Constant { private static final ThreadLocal LOCAL_PAGE = new ThreadLocal();}

本文中經常提到的count查詢,其實是PageHelper幫助我們生成的一個MappedStatement內存對象,它可以免去我們在XXXMapper.xml內單獨聲明一個sql count查詢,我們只需要寫一個sql分頁業務查詢即可。

PageHelper使用建議(性能最好):

1、明確指定dialect。
2、明確編寫sql分頁業務和與它對應的count查詢,別圖省事。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532182.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532182.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532182.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

計算機網絡dst,計算機網絡基礎課程—Socket接口

什么是TCP/IP------本課程的主要部分TCP/IP如何工作-----TCP/IP軟件結構與實現如何用TCP/IP-------TCP/IP應用程序編程接口前面說過&#xff0c;TCP/IP標準并不指定應用程序與TCP/IP協議軟件的接口&#xff0c;但并不是說沒有提供任何指導&#xff0c;首先&#xff0c;它指定了…

python輸入圓的半徑公式_[圖文]鐵路曲線正矢的計算公式

一、圓曲線正矢的計算1.1 圓曲線正矢的計算公式取圓曲線上兩點拉一直線,叫做弦。弦上任意點至曲線上的垂直距離叫矢或叫矢距。在弦中央點的矢距叫正矢(下圖)。AB一弦;AC、CB一半弦;CD一正矢;EF一矢距正矢計算公式為其中: f-正矢 C-弦長 R-半徑 式中單位均為m。公式用文字表示即…

2021高考成績查詢抖音,2021抖音很火的高考唯美的文案20個

1、用孜孜不倦去追求&#xff0c;拼搏的精神來鉆研&#xff0c;讓知識澆灌心田&#xff0c;用激情去迎接困難&#xff0c;用青春寫意一片贊嘆&#xff0c;祝愿大學生活充實飽滿&#xff0c;學富五車&#xff0c;才高八斗&#xff0c;開創美麗的明天。2、如食糖蜜心里甜&#xf…

docker 安裝nacos_康過來!Nacos配置和管理微服務的使用

Nacos 具有如下特性:服務發現和服務健康監測&#xff1a;支持基于DNS和基于RPC的服務發現&#xff0c;支持對服務的實時的健康檢查&#xff0c;阻止向不健康的主機或服務實例發送請求&#xff1b;動態配置服務&#xff1a;動態配置服務可以讓您以中心化、外部化和動態化的方式管…

HTML5中volume樣式自定義,html5中關于volume屬性的使用詳解

Audio對象屬性&#xff1a; volume 描述&#xff1a;設置或返回音頻的音量&#xff0c;取值范圍(0——1)下面是我做的音樂播放器如何調節音頻音量的代碼&#xff1a;//增加切換音量事件(function(){var height $("#myAudio ul.control li.volume .alert-box .volume-wrap…

matlab中如何調用gpu進行并行計算_極致安卓-Termux/Aid learning開啟WebGL手機GPU并行計算...

在我的之前的測評中&#xff0c;我利用Termux和Aid Learning測試過基于C/C的openmp并行程序&#xff0c;基于Java的并行程序&#xff0c;還有基于MPI以及基于Java的分布式集群并行。但是很遺憾&#xff0c;一直無法成功開發基于OpenCL的GPU并行編程。這是主要是因為Android并沒…

python默認編碼方式_關于設置python默認編碼方式的問題

2019-8-27 07:45:36 本帖最后由 傻紙 于 2019-8-27 10:02 編輯 查了一會資料得出的結論是如果你用的是python3.x&#xff0c;那么就最好別去設置sys.defaultencoding或者sys.stdout.encoding 記住在需要編碼的時候用encode&#xff0c;解碼的時候decode就可以了。。。 這個問題…

計算機科學與技術是屬于什么學科,計算機科學與技術專業屬于什么大類 屬于哪個學科...

近日&#xff0c;有很多人咨詢小編計算機科學與技術專業屬于什么大類 屬于哪個學科&#xff1f;現在小編統一回復一下大家計算機科學與技術專業屬于工學類&#xff0c;下面是關于計算機科學與技術專業詳細的介紹。1計算機科學與技術專業門類及學科介紹專業名稱專業代碼門類學科…

matlab imread_MATLAB圖像處理:29:在幾何變換輸出中指定填充值

本示例說明如何指定imwarp執行幾何變換時使用的填充值。執行轉換時&#xff0c;輸出圖像中通常會有一些像素不屬于原始輸入圖像。必須為這些像素分配一些值&#xff0c;稱為填充值。默認情況下&#xff0c;imwarp將這些像素設置為零&#xff0c;并顯示為黑色。使用FillValues參…

小學生學計算機,學計算機對小學生的好處

內容提要:隨著計算機應用的日益社會化和家庭化,計算機在人們工作、學習和生活等各個方面正發揮著越來越重要的作用&#xff0c;而計算機應用基礎也相應成為現代社會人們必修的文化基礎課。現在的小學生將是未來的創新型人才&#xff0c;他們的計算機水平如何&#xff0c;直接關…

micopython 18b20_MicroPython控制8*8LED點陣顯示溫度

MicroPython顧名思義就是可以在單片機上跑的Python&#xff0c;借助Micro Python&#xff0c;用戶完全可以通過Python腳本語言實現硬件底層的訪問和控制&#xff0c;比如說控制LED燈泡、LCD顯示器、讀取電壓、控制電機、訪問SD卡等。目前支持MicroPython的開發板有好幾種&#…

計算機組裝維護文獻,組裝計算機論文,關于《計算機組裝維護》課程教學相關參考文獻資料-免費論文范文...

導讀:此文是一篇組裝計算機論文范文,為你的畢業論文寫作提供有價值的參考。【摘 要】計算機硬件知識的教與學對于計算機相關專業的學生來說,重要性是不言而喻的.由于教學資源缺乏,面對這門以操作性強為特點的課程,如何利用學校有限的條件培養出符合職業資格標準的學生是該專業教…

ip變更會影響賬號登陸嗎_【教程】PUBG賬號被盜導致封禁申訴解封教程

很多朋友詢問PUBG在被盜號后被盜號者開掛導致永封該如何申訴解封&#xff0c;現在結合一些玩家被盜號及成功申訴的經歷&#xff0c;詳列一下步驟。本方法只適用于被盜后開掛導致封禁的賬號&#xff0c;那些自己開掛被封的孤兒不用往下看了。一.先向steam客服申訴 找回自己的ste…

html5專著,參考文獻專著

參考文獻類型&#xff1a;專著[M]&#xff0c;會議論文集[C]&#xff0c;報紙文章[N]&#xff0c;期刊文章[J]&#xff0c; 學位論文[D]&#xff0c; 報告[R]&#xff0c; 標準[S]&#xff0c; 專利[P]&#xff0c;論文集中的析出文獻[A]關于你又知道多少呢?下面是小編為大家整…

濾鏡怎么調_手機、電腦怎么剪輯視頻?真心求推薦實用工具

自從加入了短視頻自媒體運營這個行業以后&#xff0c;我就開始接觸到各種各樣的手機、電腦視頻剪輯、制作軟件&#xff0c;用它們來處理、完成被安排到的工作任務。很多時候&#xff0c;我也用它們來剪視頻&#xff0c;借此來練練手、積累下素材。記得剛進入這個行業的時候&…

2021計算機基礎知識題庫,2021~2021計算機基礎知識練習題

2021~2021計算機基礎知識練習題 2021~2021計算機基礎知識練習題 北京聯合大學 2021~2021計算機基礎知識練習題 一、選擇題 1.記錄在存儲介質上的一組相關信息的集合稱為______。 A)程序 B)磁盤 C)軟件 D)文件 2.當一個文件更名后&#xff0c;文件的內容會______。 A)完全消失 B…

計算機背板知識,你知道背板的選購技巧嗎?

原標題&#xff1a;你知道背板的選購技巧嗎&#xff1f;背板就是母板&#xff0c;子板插在上面構成系統&#xff0c;計算機背板說成背板也成立&#xff0c;只不過背板更多的知識線路板而已&#xff0c;沒有實際的器件&#xff0c;只起信號通路作用。背板在設備機箱的后面。一般…

git 切換分支_git 入門教程之分支總覽

分支就是一條獨立的時間線,既有分支,必有主干,正如一棵樹談到樹枝,必有樹干一樣的道理.我們先前對git 的全部操作默認都是在主干上進行的,這個主干也是一種特殊的分支,名為 master 分支.無論是穿越歷史還是撤銷更改,我們都或多或少接觸過時間線,git 管理的版本串在一起就組成了…

計算機應用技術專業全國排名,計算機應用技術專業全國排名

排名學校名稱等級排名學校名稱等級排名學校名稱等級1清華大學A19武漢大學A37合肥工業大學A2浙江大學A20華南理工大學A38蘇州大學A3北京航空航天大學A21電子科技大學A39江蘇大學A4華中科技大學A22大連理工大學A40大連海事大學A5北京大學A23天津大學A41中山大學A6上海交通大學A24…