Java開發者學習技術體系

01基礎技術體系

我認為知識技能體系化是判斷技術是否過關的第一步。知識體系化包含兩層含義:

1、 能夠知道技術知識圖譜(高清版圖譜掃文末二維碼)的內容

比如分佈式系統中常用的RPC技術,其背後就涉及到網絡IO(Netty)、網絡協議、服務發現(Zookeeper配置中心)、RPC服務治理(限流、熔斷、降級)、負載均衡等。

2、 能夠理清各類技術概念之間的區別和聯繫

在分佈式系統領域中,有很多相似的概念,但又分佈在不同的產品或層級中。比如負載均衡這個詞,DNS、LVS、Ngnix、F5等產品都能實現,而且在大型分佈式系統中他們會同時存在,那麼就要搞清楚他們各自的位於什麼層級,解決了什麼問題。

再比如緩存這項技術,有分佈式緩存、本地緩存、數據庫緩存,在往下還有硬件層級的緩存。同樣都是緩存,他們之間的區別又是什麼?

如果你仔細去觀察,大廠的後端開發工程師總是能對整個技術體系了如指掌,從而在系統設計與技術選型階段就能夠做出較為合理的架構。 

02實踐經驗的積累

       能否快速解決實戰中的業務問題是判斷技術是否過關的第二步。
       大家在面試的過程中,都會有一種體會:我的知識體系已經建立了,但在回答面試官問題的時候,總感覺像在背答案,而且也沒有辦法針對性的回答面試官問題。比如在面試官問到這些問題時:

  1. 我們知道消息隊列可應用於耦系統,應對異步消費等場景,那如何在網絡不可靠的場景下保證業務數據處理的正確性?
  2. 我們都知道在分佈式系統會用到緩存,那該如何設置緩存失效機制才能避免系統出現緩存雪崩?
  3. 我們都或多或少的知道系統發布上線的流程,但在大流量場景下採用何種發布機制才能盡可能的做到平滑?

能完善的解決這些問題是區分一個程序員是否有經驗的重要標誌,知識的體系化是可以從書本不斷的凝練來獲得,但經驗的積累需要通過實戰的不斷總結

對很多人來說很為難的一點是,平時寫着的業務代碼,很少有機會接觸到大廠的優秀實踐,那麼這時候更需要從如下兩個角度逼問:

1、當流量規模再提高几個量級,那麼我的系統會出現什麼問題?

2、假如其中一個環節出現了問題,那麼該怎麼保證系統的穩定性?

03技術的原理

上面的提到都是將技術用於業務實踐,以及高效的解決業務中出現的問題。但這是否就意味着自己的技術已經過關了呢?我認為還不能。

判斷技術是否過關的第三步是能否洞察技術背後的設計思想和原理。

如果你參加過一些大廠面試,還會問到一些開放性的問題:

1、 寫一段程序,讓其運行時的表現為觸發了5次Young GC、3次Full GC、然後3次Young GC;

2、 如果一個Java進程突然消失了,你會怎麼去排查這種問題?

3、 給了一段Spring加載Bean的代碼片段,闡述一下具體的執行流程?

       是不是看上去很難,是不是和自己準備的“題庫”中的問題不一樣?不知道從何處下手?如果你有這種感覺,那麼說明你的技術還需要繼續修鍊。

       你要明白的是這種開放性的問題,提問的角度千變萬化,但最終落腳點卻都是基本原理。如果你不了解GC的觸發條件,你就肯定無法答出第一題;同樣,如果你對Spring啟動機制了解的很清楚,那麼無論他給出的是什麼樣的代碼,你都能回答出代碼經歷的過程。如果你能以不變應萬變,那麼恭喜你,你的技術過關了。

       上面提到了很多技術問題,這裏我不做詳細的解釋,都能在下面的技術圖譜中找到答案:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

人機對話技術研究進展與思考

嘉賓:袁彩霞 博士 北京郵電大學 副教授

整理:Hoh Xil

來源:阿里小蜜 & DataFun AI Talk

出品:DataFun

注:歡迎轉載,轉載請在留言區內留言。

導讀:本次分享的主題為人機對話技術研究進展與思考。主要梳理了我們團隊近兩年的工作,渴望可以通過這樣的介紹,能給大家一個關於人機對話 ( 包括它的科學問題和應用技術 ) 方面的啟示,幫助我們進行更深入的研究和討論。主要包括:

  1. Spoken dialogue system:a bird view ( 首先我們來看什麼是人機對話,尤其是 Spoken dialogue。其實說 Spoken 的時候,有兩層含義:第一個 spoken 就是 speech,第二個我們處理的語言本身具有 spoken 的特性。但是,稍後會講的 spoken 是指我們已經進行語音識別之後,轉換為文本的一個特殊的自然語言,後面討論的口語對話不過多地討論它的口語特性,主要是講人和機器之間的自然語言對話。)

  2. X-driven dialogue system:緊接着來講解我們近些年的研究主線 X-driven dialogue syatem,X 指構建一個對話系統時,所採用的數據是什麼,從最早的 dialogue -> FAQ -> KB -> KG -> document 以及我們一直在嘗試的圖文多模態數據。

  3. Concluding remarks ( 結束語 )

01

Spoken dialogue system:a bird view

學術界關於對話系統有着不同的劃分,這種劃分目前看來不是非常準確,也不是特別標準的劃分了。但是,接下來的內容,主要是圍繞着這兩個主線:

限定領域,專門指任務型對話 ( 圍繞某一特定用戶對話目標而展開的 )。對於任務型對話,對話系統的優化目標就是如何以一個特別高的回報、特別少的對話輪次、特別高的成功率來達成用戶的對話目標。所以即便是限定領域,我們這裏討論的也是特別限定的、專門有明確的用戶對話目標的一種對話。

開放領域,not purely task-oriented, 已經不再是純粹的對話目標驅動的對話,包括:閑聊、推薦、信息服務等等,後面逐步展開介紹。

我們在研究一個問題或者做論文答辯和開題報告時,經常討論研究對象的意義在哪裡。圖中,前面講的是應用意義,後面是理論意義。我們實驗室在北京郵電大學叫智能科學與技術實驗室,其實她的前身叫人工智能實驗室。所以從名字來看,我們做了非常多的 AI 基礎理論的研究,我們在研究這些理論的時候,也會講 AI 的終極目的是研製一種能夠從事人類思維活動的計算機系統。人類思維活動建立在獲取到的信號的基礎上。人類獲取信號的方式大體有五類,包括視覺、聽覺、觸覺、味覺、嗅覺等,其中視覺和聽覺是兩個比較高級的傳感器通道,尤其是視覺通道,佔據了人類獲得信息的80%以上。所以我們從這兩個角度,設立了兩個研究對象:第一個是語言,第二個是圖像。而我們在研究語言的時候,發現語言有一個重要的屬性,叫交互性,交互性最典型的一個體現就是對話;同時,語言不是一個獨立的模態,語言的處理離不開跟它相關的另一個通道,就是視覺通道。所以我們早期更多是為了把交互和多模態這樣的屬性納入到語言建模的範圍,以其提升其它自然語言處理系統的性能,這就是我們研究的一個動機。

  1. Block diagram

上圖為 CMU 等在1997年提出來的人機對話框架,基於這個框架人們開發出了非常多優秀的應用系統,比如應用天氣領域的 “Jupiter”。這個框架從提出到商業化應用,一直到今天,我們都還沿着這樣的一個系統架構在進行開發,尤其是任務驅動的對話。

這就是具體的對話系統的技術架構。

  1. Specific domain

這個架構發展到現在,在功能模塊上,已經有了一個很清晰的劃分:

首先進行語音識別,然後自然語言理解,緊接着做對話管理,將對話管理的輸出交給自然語言生成模塊,最後形成自然語言應答返回給用戶。這就是一個最典型的 specific domain 的架構。早期 task 限定的 dialogue,基本上都是按照這個架構來做的。這個架構雖然是一個 Pipeline,但是從研究的角度來講,每一個模塊和其它模塊之間都會存在依賴關係。因此,我們試圖從研究的角度把不同的功能模塊進行統一建模。在這個建模過程中,又會產生新的學術性問題,我們旨在在這樣的問題上可以產生驅動性的技術。

  1. Open domain

Open domain,也就是“閑聊”,實現上主要分為途徑:

第一個是基於匹配/規則的閑聊系統;第二個是基於檢索的閑聊系統;第三個是基於編解碼結構的端到端對話系統。當然,實際情境中,這幾個途徑往往結合在一起使用。

02

X-Driven dialogue system

目前無論是任務型對話還是閑聊式對話,都採用數據驅動的方法,因此依據在構建人機對話系統時所用到的數據不同,建模技術和系統特性也就體現出巨大的不同。我們把使用的數據記為 X,於是就有了不同的 X 驅動的對話。

  1. Our roadmap

如果想讓機器學會像人一樣對話,我們可以提供的最自然的數據就是 dialogue。我們從2003年開始做對話驅動的對話;2012年開始做 FAQ 驅動的對話;2015年開始做知識庫 ( KB ) 驅動的對話;2016年開始做知識圖譜 ( KG ) 驅動的對話,相比於 KB,KG 中的知識點產生了關聯,有了這種關聯人們就可以在大規模的圖譜上做知識推理;2017年開始做文檔驅動的對話。這就是我們研究的大致脈絡。

  1. Dialogue-driven dialogue

早期在做 Dialogue driven 的時候,多依賴人工採集數據,但是,從2013年以來,逐步開放了豐富的涵蓋多領域多場景的公開數據集。比如最近的 MultiWOZ,從 task specific 角度講,數據質量足夠好、數據規模足夠大,同時涵蓋的對話情景也非常豐富。但是,目前公開的中文數據集還不是很多。

這個是和任務型對話無關的數據集,也就是採集的人與人對話的數據集。尤其以 Ubuntu 為例,從15年更新至今,已經積累了非常大規模的數據。

以 Dialogue 為輸入,我們開展了任務型和非任務型兩個方向的工作。先來看下任務型對話:

2.1 NLU

當一個用戶輸入過來,第一個要做的就是自然語言理解 ( NLU ),NLU 要做的三件事為:Domain 識別;Intent 識別;信息槽識別或叫槽填充。這三個任務可以分別獨立地或採用管道式方法做,也可以聯合起來進行建模。在聯合建模以外,我們還做了一些特別的研究。比如我們在槽識別的時候,總是有新槽,再比如有些槽值非常奇怪,例如 “XX手機可以一邊打電話一邊視頻嗎?”,對應着槽值 “視頻電話”,採用序列標註的方式,很難識別它,因為這個槽值非常不規範。用戶輸入可能像這樣語義非常鬆散,不連續,也可能存在非常多噪音,在進行聯合建模時,傳統的序列標註或分類為思想,在實際應用中已經不足以解決問題了。

我們針對這個問題做了比較多的探討,右圖為我們2015年的一個工作:在這三個任務聯合建模的同時,在槽填充這個任務上將序列標註和分類進行同時建模,來更好地完成 NLU。

在 NLU 領域還有一個非常重要的問題,隨着開發的業務領域越來越多,我們發現多領域對話產生了諸多非常重要的問題,例如在數據層有些 domain 數據可能很多,有些 domain 數據可能很少,甚至沒有,於是就遇到冷啟動的問題。因此,我們做了非常多的 domain transfer 的工作。上圖為我們2016年的一個工作,我們會把數據比較多的看成源領域,數據比較少的看成目標領域。於是,嘗試了基於多種遷移學習的 NLU,有的是在特徵層進行遷移,有的是在數據層遷移,有的是在模型層進行遷移。圖中是兩個典型的在特徵層進行遷移的例子,不僅關注領域一般特徵,而且關注領域專門特徵,同時採用了對抗網絡來生成一個虛擬的特徵集的模型。

2.2 NLU+DM

緊接着,我們研究了 NLU 和對話管理 ( DM ) 進行聯合建模,因為我們發現人人對話的時候,不見得是聽完對方說完話,理解了對方的意圖,然後才形成對話策略,有可能這兩個過程是同時發生的。甚或 DM 還可以反作用於 NLU。早期我們基於的一個假設是, NLU 可能不需要一個顯式的過程,甚至不需要一個顯式的 NLU 的功能,我們認為 NLU 最終是服務於對話管理 ( DM ),甚至就是對話管理 ( DM ) 的一部分。所以,2013年的時候,我們開始了探索,有兩位特別優秀的畢業生在這兩個方面做了特別多的工作。比如,如何更好地聯合建模語言理解的輸出和對話管理的策略優化。這是我們在 NLU 和 DM 聯合建模的工作,同時提升了 NLU 和 DM 的性能。

在聯合模型中,我們發現,DM 的建模涉及到非常多的 DRL ( 深度強化學習 ) 的工作,訓練起來非常困難,比如如何設計一個好的用戶模擬器,基於規則的,基於統計的,基於語言模型的,基於 RL 的等等我們嘗試了非常多的辦法,也取得了一系列有趣的發現。2018年時我們研究一種不依賴於規則的用戶模擬器,業界管這個問題叫做 “Self”-play,雖然我們和 “Self”-play 在網絡結構上差異挺大的,但是我們還是借鑒了 “Self”-play 訓練的特性,把我們自己的系統叫做 “Self”-play。在這樣的機制引導下,我們研究了不依賴於規則,不依賴於有標記數據的用戶模擬器,使得這個用戶模擬器可以像 Agent 一樣,和我們所構造的對話的 Agent 進行交互,在交互的過程中完成對用戶的模擬。

在訓練過程中還有一個重要的問題,就是 reward 怎麼來,我們知道在 task oriented 時,reward 通常是人類專家根據業務邏輯/規範制定出來的。事實上,當我們在和環境交互的時候不知道 reward 有多大,但是環境會隱式地告訴我們 reward 是多大,所以我們做了非常多的臨接對和 reward reshaping 的工作。

2.3 小結

Dialogue-driven dialogue 這種形式的對話系統,總結來看:

優點:

定義非常好,邏輯清晰,每一個模塊的輸入輸出也非常清晰,同時有特別堅實的數學模型可以對它進行建模。

缺點:

由於非常依賴數據,同時,不論是在 NLU 還是 NLG 時,我們都是採用有監督的模型來做的,所以它依賴於大量的、精細的標註數據。

而 DM 往往採用 DRL 來做。NIPS2018 時的一個 talk,直接指出:任何一個 RL 都存在的問題,就是糟糕的重現性、復用性、魯棒性。

  1. FAQ-driven dialogue

FAQ 是工業界非常常見的一種情景:有大量的標準問,以及這個標準問的答案是什麼。基於這個標準問,一個用戶的問題來了,如何找到和它相似的問題,進而把答案返回給用戶,於是這個 Service 就結束了。

實際中,我們如何建 FAQ?更多的時候,我會把這個問題和我們庫的標準問題做一個相似度的計算或者做一個分類。

我們在做這個工作的時候發現一個特別大的問題,就是 Unbalanced Data 問題。比如,我們有5000個問題,每個問題都有標準答案,有些問題可能對應的用戶問題特別多,比如 “屏幕碎了” 可能會有1000多種不同的問法,還有些問題,可能在幾年的時間里都沒有人問到過。所以,面對數據不均衡的問題,我們從2016年開始做了 Data transfer 的工作。

大致的思路是:我有一個標準問題,但是很糟糕,這個標準問題沒有用戶問題,也就是沒有訓練語料。接下來發現另外一個和這個標準問很相似的其它標準問有很多的訓練語料,於是藉助這個標準問,來生成虛擬樣本,進而削弱了 Unbalance。

具體的方法:我們把目標領域的標準問看成 Query,把和它相似的標準問題及其對應的用戶問題看成 Context,採用了 MRC 機器閱讀理解的架構來生成一個答案,作為目標問題的虛擬的用戶問題,取得了非常好的效果,並且嘗試了三種不同的生成用戶問題的方法。

實際項目中,FAQ 中的 Q 可能有非常多的問題,例如3000多個類,需要做極限分類,這就導致性能低下,且非常耗時,不能快速響應用戶的問題。於是我們做了一個匹配和分類進行交互的 model,取得了不錯的效果。

目前,大部分人都認為 FAQ 驅動的 dialogue 不叫 dialogue,因為我們通常說的 dialogue 輪次是大於兩輪的。而 FAQ 就是一個 QA 系統,沒有交互性。有時候帶來的用戶體驗非常不友好,比如當答案非常長的時候,系統要把長長的答案返回,就會一直講,導致用戶比較差的體驗。於是,我們基於 FAQ 發展出了一個多輪對話的數據,如右圖,這是我們正在開展的一個工作。

  1. KB-driven dialogue

KB 最早人們認為它就是一個結構化的數據庫,通常存儲在關係型數據庫中。比如要訂一個酒店,這個酒店有各種屬性,如位置、名稱、戶型、價格、面積等等。早期 CMU 的對話系統,所有的模塊都要和 Hub 進行交互,最後 Hub 和後端的數據庫進行交互。數據庫的價值非常大,但是早期人們在建模人機對話的時候,都忽視了數據庫。這裏就會存在一個問題:機器和用戶交互了很久,而在檢索數據庫時發現沒有答案,或者答案非常多,造成用戶體驗非常糟糕。

從2012年開始,我們開始把 KB 引入我們的對話系統。圖中的對話系統叫做 “teach-and-learn” bot,這是一個多模態的對話,但是每個涉及到的 object,我們都會把它放到 DB 中。和用戶交互過程中,不光看用戶的對話狀態,還要看數據庫狀態。這個想法把工作往前推進了一些。

直到2016年,MSR 提出的 KB-InfoBot,第一次提出了進行數據庫操作時,要考慮它的可導性,否則,就沒辦法在 RL 網絡中像其它的 Agent action 一樣進行求導。具體的思路:把數據庫的查詢和 Belief State 一起總結起來做同一個 belief,進而在這樣的 belief 基礎上做各種對話策略的優化。

在上述方法的基礎上,我們做了有效的改良,包括 entropy regularities 工作。是每次和用戶進行交互時,數據庫的 entropy 會發生變化。比如當機器問 “你想訂哪裡的酒店?”,用戶答 “阿里中心附近的。”,於是數據庫立刻進行了一次 entropy 計算進行更新,接着繼續問 “你想訂哪一天的?”,用戶答 “訂7月28號的”,於是又進行了一次 entropy 計算進行更新。這樣在和用戶進行交互的時候,不光看用戶的 dialogue 輸入,還看 DB 的 entropy 輸入,以這兩項共同驅動 Agent action 進行優化。

這裏我們做了特別多的工作,信息槽從1個到5個,數據庫的規模從大到小,都做了特別多的嘗試,這樣在和用戶交互的時候,agent 可以自主的查詢檢索,甚至可以填充和修改數據庫。

這是我們2017發布的一個工作,KB driven-dialogue,其優點:

控制萬能高頻回復 ( 提高答應包含的有用信息 )

賦予 agent 對話主動性

  1. KG-driven dialogue

剛剛講的基於 KB 的 dialogue 任務,基本都認為對話任務就是在進行槽填充的任務,如果一個 agent 是主動性的,通過不停的和用戶進行交互來採集槽信息,所以叫槽填充,當槽填完了,就相當於對話任務成功了。但是,當我們在定義槽的時候,我們認為槽是互相獨立的,並且是扁平的。然而,實際中許多任務的槽之間存在相關性,有的是上下位關係,有的是約束關係,有的是遞進關係等等。這樣自然的就引出了知識圖譜,知識圖譜可以較好地描述上述的相關性。於是,產生了兩個新問題:

知識圖譜驅動的對話理解:實體鏈接

知識圖譜驅動的對話管理:圖路徑規劃

這裏主要講下第二個問題。

舉個例子,我們在辦理電信業務,開通一個家庭寬帶,需要提供相關的證件,是自己去辦,還是委託人去辦,是房東還是租戶等等,需要提供各種不同的材料。於是這個情景就產生了條件的約束,某一個 node 和其它 node 是上下位的關係,比如證件可以是身份證,也可以是護照或者戶口簿等等。所以我們可以通過知識圖譜來進行處理。

當一個用戶的對話過來,首先會鏈接到不同的 node,再基於 node 和對話歷史構造一個對話的 state,我們會維持一個全局的 state 和一個活躍的 state,同時活躍的 state 會定義三種不同的操作動作,一個是祖先節點,一個是兄弟節點,還有一個是孩子節點。所以,在這樣的知識圖譜上如何尋優,比如當通過某種計算得到,它應該在某個節點上進行交互的時候,我們就應該輸出一個 action,這個 action 要和用戶確認他是一個租戶,還是自有住房等等。所以,這個 action 是有區別於此前所提到的在特定的、扁平的 slot 槽上和用戶進行信息的確認、修改等還是有很大不同的。解決這樣的問題,一個非常巨大的挑戰就是狀態空間非常大。比如圖中的節點大概有120個,每個節點有3個不同的狀態,知識從節點的狀態來看就有3的120次冪種可能。這也是我們正在開展的待解決的一個問題。

在端到端的對話模型 ( 閑聊 ) 中,也開始逐步地引入知識圖譜。下面介紹兩個比較具有代表性的引入知識圖譜后的人機對話。其中右邊是2018年 IJCAI 的傑出論文,清華大學黃民烈老師團隊的工作,引入了通過 KG 來表示的 Commonsense,同時到底在編碼器端還是在解碼器端引入知識,以及如何排序,排序的時候如何結合對話的 history 做知識的推理等等,都做了特別全面的研究。

另一個比較有代表性的工作是在 ICLR2019 提出的,在架構中引入了 Local Memory 和 Global Memory 相融合的技術,通過這種融合,在編碼器端和解碼器端同時加入了知識的推理。

總結下 KB/KG-driven dialogue:

優點:

已經有大規模公開的數據 ( e.g.,InCar Assistant,MMD,M2M )。

訓練過程可控&穩定,因為這裏多數都是有監督學習。

缺點:

因為採用有監督的方式進行訓練,所以存在如下問題:

① 環境確定性假設
② 缺少對動作的建模
③ 缺少全局的動作規劃
Agent 被動,完全依賴於訓練數據,所以模型是不賦予 Agent 主動性的。

構建 KB 和 KG 成本昂貴!

  1. Document-driven dialogue

Document 驅動的對話,具有如下優點:

① 應用場景真實豐富:

情景對話 ( conversation ),比如針對某個熱門事件在微博會產生很多對話,這就是一個情景式的對話。

電信業務辦理 ( service ),比如10086有非常多的套餐,如何從中選擇一個用戶心儀的套餐?每個套餐都有說明書,我們可以圍繞套餐的說明書和用戶進行交互,如 “您希望流量多、還是語音多”,如果用戶回答 “流量多”,就可以基於文本知道給用戶推薦流量多的套餐,如果有三個候選,機器就可以基於這三個候選和用戶繼續進行交互,縮小候選套餐範圍,直到用戶選出心儀的套餐。

電商產品推薦 ( recommendation ),可以根據商品的描述,進行各種各樣的對話。這裏的輸入不是一個 dialogue,也不是一個 KB,甚至結構化的內容非常少,完全是一個 free document,如何基於這些 document 進行推薦,是一個很好的應用場景。

交互式信息查詢 ( retrieval ),很多時候,一次查詢的結果可能不能用戶的需求,如何基於非結構化的查詢結果和用戶進行交互,來更好地達成用戶的查詢目的。

……

② 數據獲取直接便捷:

相比於 dialogue、FAQ、KB、KQ 等,Document 充斥着互聯網的各種各樣的文本,都可以看成是文本的數據,獲取方便,成本非常低。

③ 領域移植性強:

基於文本,不再基於專家定義的 slot,也不再基於受限的 KB/KG,從技術上講,所構造的 model 本身是和領域無關的,所以它賦予了 model 很強的領域移植性。

這是我們正在進行的工作,情景對話偏向於閑聊,沒有一個用戶目標。這裏需要解決的問題有兩個:

如何引入文檔:編碼端引入文檔、解碼端引入文檔

如何編碼文檔:文檔過長、冗餘信息過多

數據:

我們在 DoG 數據的基礎上模擬了一些數據,形成了如上圖所示的數據集,分 Blind 和 Non-blind 兩種情形構造了不同的數據集。

我們發現,基於文本的端到端的聊天,有些是基於內容的閑聊,有些還需要回答特定的問題。所以,評價的時候,可以直接用 F1 評價回答特定問題,用閑聊通用的評價來評價基於內容的聊天。

剛剛講的是偏閑聊式的對話,接下來講下任務型對話。

這裏的動機分為兩種情況:單文檔和多文檔,我們目前在挑戰多文檔的情況,單文檔則採用簡單的多輪閱讀理解來做。

多文檔要解決的問題:

如何定義對話動作:因為是基於Document進行交互,而不再是基於slot進行交互,所以需要重新定義對話動作。

如何建模文檔差異:以剛剛10086的例子為例,總共有10個業務,通過一輪對話,挑選出3個,這3個業務每個業務可能都有10種屬性,那麼其中一些屬性值相同的屬性,沒必要再和用戶進行交互,只需要交互它們之間不同的點,所以交互的角度需要隨對話進行動態地變化。

數據:

這裏採用的數據是 WikiMovies 和模擬數據,具體見上圖。

  1. A very simple sketch of dialogue

上圖為任務型對話和非任務型對話的幾個重要節點,大家可以了解下。

03

Concluding remarks

任務型對話具有最豐富的落地場景。

純閑聊型對話系統的學術價值尚不清楚。

任務型和非任務型邊界愈加模糊,一個典型的人人對話既包括閑聊,又包括信息獲取、推薦、服務。

引入外部知識十分必要,外部知識形態各異,建模方法也因而千差萬別。

Uncertainty 和 few-shot 問題,是幾乎所有的對話系統最大的 “卡脖子” 問題。

X-driven dialogue 中的 X 還有哪些可能?剛剛提到的 X 都還是基於文本的。事實上,從2005年開始,我們已經開始做 image 和文本數據融合的對話;從2013年開始做 Visual QA/Dialogue,挑戰了 GuessWhat 和 GuessWhich 任務。

X=multi media ( MM Dialogue ),X 還可以很寬泛的就是指多媒體,不光有 image 還可能有 text,2018年已經有了相關的任務,並且開源了非常多的電商業務。這是一個非常有挑戰性的工作,也使人機對話本身更加擬人化。

04

Reference

這是部分的參考文獻,有些是我們自己的工作,有些是特別傑出的別人的工作。

今天的分享就到這裏,感謝大家的聆聽,也感謝我們的團隊。

歡迎關注DataFunTalk同名公眾號,收看第一手原創技術文章。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新能源積分交易制度 或替代財政補貼

新能源汽車業發展日益成熟之時,單純的財政補貼帶來的車企騙補等諸多負面效應逐漸顯現。對此,有專家提出建議採用積分交易機制替代財政補貼政策,促進車企在傳統燃油車節能和新能源車技術進步兩方面共同發展。  
  中國汽車技術研究中心新能源汽車積分政策負責人時間表示,相比真金白銀的財政補貼,新能源積分交易制度靈活性更高,是當下國家推動新能源產業發展的一種可行方式。   新能源積分交易制度,即政府將企業年度“零排放”車型的銷售情況記錄成積分,以積分為依據來考核企業在節能減排方面是否達標。若企業積分不達標,可以購買同行業其餘公司的積分,或者向政府繳納高額罰款。   中國若要實施積分交易制度 政府部門職責需合理分配。   為此,積分政策負責人時間給出幾點建議:作為政策的制定者政府,需要部門間進行合理的職責分配,物質保障方面,中國新能源基礎設施建設尚需進一步完善。意識形態方面,當前,消費者對新能源車的認識還不充足,對制度實施造成一定阻礙。企業方面,不同規模的企業須在政策上區別對待,為中小企業的發展提供空間;當企業規模有所變更時,應當提供相應的扶持政策。   文章來源:人民網

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

特斯拉Gigafactory趕工,可望提前量產

美國電動車商特斯拉(Tesla Motors Inc.)的平價車款「Model 3」預定2018年問世,為了趕上需求,特斯拉造價50億美元的「Gigafactory」超級電池廠正在加緊趕工,將比原定時程提前數年、明(2017)年初就可以開始量產車用鋰電池。

華爾街日報、Electrek 24日報導,坐落於內華達州雷諾市的Gigafactory目前占地超過3,000英畝,特斯拉已加倍聘請建築工人,共計1,000名人員一週兩班每天輪流趕工,預計明年初就可大功告成。特斯拉技術長兼共同創辦人JB Straubel表示,在汽車量產前,電池和電池組的組裝廠房一定要事先完工,因此無論是建廠計畫或是電池的擴產時間表都會加快進度。

特斯拉電池供應夥伴Panasonic Corp.已承諾要為該廠提供16億美元的資金,目前則因為找不到合適的人才而傷透腦筋。特斯拉執行長Elon Musk預估,Gigafactory完工之後,2020年將可年產105GW的電池,足以供應120萬台Model S豪華電動轎車所需,但其中有1/3將用於定置型電池儲存裝置(stationary battery storage product)。

假如這座廠房能如期完工、擴產,則其產能將是全球現有電池廠的10倍之多,這也使得北美的鋰礦開採活動大增。

OilPrice.com 21日報導,電池過去幾年來的需求倍數成長,鋰已成為今(2016)年來最夯的金屬、擊敗黃金,雖然最近幾個月的價格漲勢稍緩,但強勁的基本面顯示其長期前景依舊看俏。另外,在戴姆勒(Daimler)、日產汽車等業者的推波助瀾下,預估到了2016年插電式電動車銷售量(plug-in electric vehicle,簡稱PEV)有望年增62%,2017年、2018年更有望成長60%、100%。這相當於2018年會賣出60萬輛PEV。

(本文內容由授權提供)

 

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

星際爭霸2 AI開發(持續更新)

準備

我的環境是python3.6,sc2包0.11.1
機器學習包下載鏈接:
地圖下載鏈接
pysc2是DeepMind開發的星際爭霸Ⅱ學習環境。 它是封裝星際爭霸Ⅱ機器學習API,同時也提供Python增強學習環境。
以神族為例編寫代碼,神族建築科技圖如下:

採礦

# -*- encoding: utf-8 -*-
'''
@File    :   __init__.py.py    
@Modify Time      @Author       @Desciption
------------      -------       -----------
2019/11/3 12:32   Jonas           None
'''

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer


class SentdeBot(sc2.BotAI):
    async def on_step(self, iteration: int):
        await self.distribute_workers()


run_game(maps.get("AcidPlantLE"), [
    Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
],realtime = True)

注意
game_data.py的assert self.id != 0註釋掉
pixel_map.py的assert self.bits_per_pixel % 8 == 0, "Unsupported pixel density"註釋掉
否則會報錯

運行結果如下,农民開始採礦

可以正常採礦

建造农民和水晶塔

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import *


class SentdeBot(sc2.BotAI):
    async def on_step(self, iteration: int):
        await self.distribute_workers()
        await self.build_workers()
        await self.build_pylons()

    # 建造农民
    async def build_workers(self):
        # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
        for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
            # 是否有50晶體礦
            if self.can_afford(UnitTypeId.PROBE):
                await self.do(nexus.train(UnitTypeId.PROBE))

    ## 建造水晶
    async def build_pylons(self):
        ## 供應人口和現有人口之差小於5且水晶不是正在建造
        if self.supply_left<5 and not self.already_pending(UnitTypeId.PYLON):
            nexuses = self.units(UnitTypeId.NEXUS).ready
            if nexuses.exists:
                if self.can_afford(UnitTypeId.PYLON):
                    await self.build(UnitTypeId.PYLON,near=nexuses.first)

## 啟動遊戲
run_game(maps.get("AcidPlantLE"), [
    Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
],realtime = True)

運行結果如下,基地造农民,农民造水晶

收集氣體和開礦

代碼如下

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import *


class SentdeBot(sc2.BotAI):
    async def on_step(self, iteration: int):
        await self.distribute_workers()
        await self.build_workers()
        await self.build_pylons()
        await self.build_assimilators()
        await self.expand()

    # 建造农民
    async def build_workers(self):
        # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
        for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
            # 是否有50晶體礦
            if self.can_afford(UnitTypeId.PROBE):
                await self.do(nexus.train(UnitTypeId.PROBE))

    ## 建造水晶
    async def build_pylons(self):
        ## 供應人口和現有人口之差小於5且建築不是正在建造
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            nexuses = self.units(UnitTypeId.NEXUS).ready
            if nexuses.exists:
                if self.can_afford(UnitTypeId.PYLON):
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)

    ## 建造吸收廠
    async def build_assimilators(self):
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            # 在瓦斯泉上建造吸收廠
            vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
            for vaspene in vaspenes:
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                worker = self.select_build_worker(vaspene.position)
                if worker is None:
                    break
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene))

    ## 開礦
    async def expand(self):
        if self.units(UnitTypeId.NEXUS).amount<3 and self.can_afford(UnitTypeId.NEXUS):
            await self.expand_now()

## 啟動遊戲
run_game(maps.get("AcidPlantLE"), [
    Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
], realtime=False)

run_game的realtime設置成False,可以在加速模式下運行遊戲。
運行效果如下:

可以建造吸收廠和開礦

建造軍隊

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import *


class SentdeBot(sc2.BotAI):
    async def on_step(self, iteration: int):
        await self.distribute_workers()
        await self.build_workers()
        await self.build_pylons()
        await self.build_assimilators()
        await self.expand()
        await self.offensive_force_buildings()
        await self.build_offensive_force()

    # 建造农民
    async def build_workers(self):
        # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
        for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
            # 是否有50晶體礦
            if self.can_afford(UnitTypeId.PROBE):
                await self.do(nexus.train(UnitTypeId.PROBE))

    ## 建造水晶
    async def build_pylons(self):
        ## 供應人口和現有人口之差小於5且建築不是正在建造
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            nexuses = self.units(UnitTypeId.NEXUS).ready
            if nexuses.exists:
                if self.can_afford(UnitTypeId.PYLON):
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)

    ## 建造吸收廠
    async def build_assimilators(self):
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            # 在瓦斯泉上建造吸收廠
            vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
            for vaspene in vaspenes:
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                worker = self.select_build_worker(vaspene.position)
                if worker is None:
                    break
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene))

    ## 開礦
    async def expand(self):
        if self.units(UnitTypeId.NEXUS).amount<2 and self.can_afford(UnitTypeId.NEXUS):
            await self.expand_now()

    ## 建造進攻性建築
    async def offensive_force_buildings(self):
        if self.units(UnitTypeId.PYLON).ready.exists:
            pylon = self.units(UnitTypeId.PYLON).ready.random
            if self.units(UnitTypeId.PYLON).ready.exists:
                # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
                if self.units(UnitTypeId.GATEWAY).ready.exists:
                    if not self.units(UnitTypeId.CYBERNETICSCORE):
                        if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                            await self.build(UnitTypeId.CYBERNETICSCORE,near = pylon)
                # 否則建造折躍門
                else:
                    if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                        await self.build(UnitTypeId.GATEWAY,near=pylon)

    # 造兵
    async def build_offensive_force(self):
        # 無隊列化建造
        for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
            if self.can_afford(UnitTypeId.STALKER) and self.supply_left>0:
                await self.do(gw.train(UnitTypeId.STALKER))



## 啟動遊戲
run_game(maps.get("AcidPlantLE"), [
    Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
], realtime=False)

運行結果如下:

可以看到,我們建造了折躍門和控制核心並訓練了追獵者

控制部隊進攻

代碼如下


import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import *
import random

class SentdeBot(sc2.BotAI):
    async def on_step(self, iteration: int):
        await self.distribute_workers()
        await self.build_workers()
        await self.build_pylons()
        await self.build_assimilators()
        await self.expand()
        await self.offensive_force_buildings()
        await self.build_offensive_force()
        await self.attack()

    # 建造农民
    async def build_workers(self):
        # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
        for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
            # 是否有50晶體礦
            if self.can_afford(UnitTypeId.PROBE):
                await self.do(nexus.train(UnitTypeId.PROBE))

    ## 建造水晶
    async def build_pylons(self):
        ## 供應人口和現有人口之差小於5且建築不是正在建造
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            nexuses = self.units(UnitTypeId.NEXUS).ready
            if nexuses.exists:
                if self.can_afford(UnitTypeId.PYLON):
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)

    ## 建造吸收廠
    async def build_assimilators(self):
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            # 在瓦斯泉上建造吸收廠
            vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
            for vaspene in vaspenes:
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                worker = self.select_build_worker(vaspene.position)
                if worker is None:
                    break
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene))

    ## 開礦
    async def expand(self):
        if self.units(UnitTypeId.NEXUS).amount<3 and self.can_afford(UnitTypeId.NEXUS):
            await self.expand_now()

    ## 建造進攻性建築
    async def offensive_force_buildings(self):
        if self.units(UnitTypeId.PYLON).ready.exists:
            pylon = self.units(UnitTypeId.PYLON).ready.random
            # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE,near = pylon)
            # 否則建造折躍門
            elif len(self.units(UnitTypeId.GATEWAY))<=3:
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY,near=pylon)

    ## 造兵
    async def build_offensive_force(self):
        # 無隊列化建造
        for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
            if self.can_afford(UnitTypeId.STALKER) and self.supply_left>0:
                await self.do(gw.train(UnitTypeId.STALKER))

    ## 尋找目標
    def find_target(self,state):
        if len(self.known_enemy_units)>0:
            # 隨機選取敵方單位
            return random.choice(self.known_enemy_units)
        elif len(self.known_enemy_units)>0:
            # 隨機選取敵方建築
            return random.choice(self.known_enemy_structures)
        else:
            # 返回敵方出生點位
            return self.enemy_start_locations[0]

    ## 進攻
    async def attack(self):
        # 追獵者數量超過15個開始進攻
        if self.units(UnitTypeId.STALKER).amount>15:
            for s in self.units(UnitTypeId.STALKER).idle:
                await self.do(s.attack(self.find_target(self.state)))

        # 防衛模式:視野範圍內存在敵人,開始攻擊
        if self.units(UnitTypeId.STALKER).amount>5:
            if len(self.known_enemy_units)>0:
                for s in self.units(UnitTypeId.STALKER).idle:
                    await self.do(s.attack(random.choice(self.known_enemy_units)))

## 啟動遊戲
run_game(maps.get("AcidPlantLE"), [
    Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Medium)
], realtime=False)

運行結果如下

可以看到,4個折躍門訓練追獵者並發動進攻。

擊敗困難電腦

我們目前的代碼只能擊敗中等和簡單電腦,那麼如何擊敗困難電腦呢?
代碼如下


import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import *
import random


class SentdeBot(sc2.BotAI):
    def __init__(self):
        # 經過計算,每分鐘大約165迭代次數
        self.ITERATIONS_PER_MINUTE = 165
        # 最大农民數量
        self.MAX_WORKERS = 65

    async def on_step(self, iteration: int):
        self.iteration = iteration
        await self.distribute_workers()
        await self.build_workers()
        await self.build_pylons()
        await self.build_assimilators()
        await self.expand()
        await self.offensive_force_buildings()
        await self.build_offensive_force()
        await self.attack()

    # 建造农民
    async def build_workers(self):
        # 星靈樞鈕*16(一個基地配備16個农民)大於農民數量並且現有农民數量小於MAX_WORKERS
        if len(self.units(UnitTypeId.NEXUS))*16>len(self.units(UnitTypeId.PROBE)) and len(self.units(UnitTypeId.PROBE))<self.MAX_WORKERS:
                # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
                for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                    # 是否有50晶體礦建造农民
                    if self.can_afford(UnitTypeId.PROBE):
                        await self.do(nexus.train(UnitTypeId.PROBE))

    ## 建造水晶
    async def build_pylons(self):
        ## 供應人口和現有人口之差小於5且建築不是正在建造
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            nexuses = self.units(UnitTypeId.NEXUS).ready
            if nexuses.exists:
                if self.can_afford(UnitTypeId.PYLON):
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)

    ## 建造吸收廠
    async def build_assimilators(self):
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            # 在瓦斯泉上建造吸收廠
            vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
            for vaspene in vaspenes:
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                worker = self.select_build_worker(vaspene.position)
                if worker is None:
                    break
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene))

    ## 開礦
    async def expand(self):
        # (self.iteration / self.ITERATIONS_PER_MINUTE)是一個緩慢遞增的值,動態開礦
        if self.units(UnitTypeId.NEXUS).amount<self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(UnitTypeId.NEXUS):
            await self.expand_now()

    ## 建造進攻性建築
    async def offensive_force_buildings(self):
        print(self.iteration / self.ITERATIONS_PER_MINUTE)
        if self.units(UnitTypeId.PYLON).ready.exists:
            pylon = self.units(UnitTypeId.PYLON).ready.random
            # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
            # 否則建造折躍門
            # (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一個緩慢遞增的值
            elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)
            # 控制核心存在的情況下建造星門
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                if len(self.units(UnitTypeId.STARGATE)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                    if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                        await self.build(UnitTypeId.STARGATE, near=pylon)

    ## 造兵
    async def build_offensive_force(self):
        # 無隊列化建造
        for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
            if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount:

                if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
                    await self.do(gw.train(UnitTypeId.STALKER))

        for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
            if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(UnitTypeId.VOIDRAY))

    ## 尋找目標
    def find_target(self,state):
        if len(self.known_enemy_units)>0:
            # 隨機選取敵方單位
            return random.choice(self.known_enemy_units)
        elif len(self.known_enemy_units)>0:
            # 隨機選取敵方建築
            return random.choice(self.known_enemy_structures)
        else:
            # 返回敵方出生點位
            return self.enemy_start_locations[0]

    ## 進攻
    async def attack(self):
        # {UNIT: [n to fight, n to defend]}
        aggressive_units = {UnitTypeId.STALKER: [15, 5],
                            UnitTypeId.VOIDRAY: [8, 3]}

        for UNIT in aggressive_units:
            # 攻擊模式
            if self.units(UNIT).amount > aggressive_units[UNIT][0] and self.units(UNIT).amount > aggressive_units[UNIT][
                1]:
                for s in self.units(UNIT).idle:
                    await self.do(s.attack(self.find_target(self.state)))
            # 防衛模式
            elif self.units(UNIT).amount > aggressive_units[UNIT][1]:
                if len(self.known_enemy_units) > 0:
                    for s in self.units(UNIT).idle:
                        await self.do(s.attack(random.choice(self.known_enemy_units)))
## 啟動遊戲
run_game(maps.get("AcidPlantLE"), [
    Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Hard)
], realtime=False)

運行結果如下

可以看到,擊敗了困難人族電腦,但是電腦選擇了rush戰術,我們寫得AI腳本會輸掉遊戲。顯然,這不是最佳方案。
“只有AI才能拯救我的勝率”,請看下文。

採集地圖數據

這次我們只造一個折躍門,全力通過星門造虛空光輝艦
修改offensive_force_buildings(self)方法的判斷

elif len(self.units(GATEWAY)) < 1:
                if self.can_afford(GATEWAY) and not self.already_pending(GATEWAY):
                    await self.build(GATEWAY, near=pylon)

註釋或者刪除build_offensive_force(self)的建造追獵者的代碼

        ## 造兵
    async def build_offensive_force(self):
        # 無隊列化建造
        # for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
        #     if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount:
        #
        #         if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
        #             await self.do(gw.train(UnitTypeId.STALKER))

        for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
            if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(UnitTypeId.VOIDRAY))

attack(self)中的aggressive_units註釋掉Stalker
導入numpy和cv2庫

game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)

建立以地圖Heigt為行,Width為列的三維矩陣

for nexus in self.units(NEXUS):
            nex_pos = nexus.position
            print(nex_pos)
            cv2.circle(game_data, (int(nex_pos[0]), int(nex_pos[1])), 10, (0, 255, 0), -1)  # BGR

遍歷星靈樞紐,獲取下一個位置,畫圓,circle(承載圓的img, 圓心, 半徑, 顏色, thickness=-1表示填充)
接下來我們要垂直翻轉三維矩陣,因為我們建立的矩陣左上角是原點(0,0),縱坐標向下延申,橫坐標向右延申。翻轉之後就成了正常的坐標系。

flipped = cv2.flip(game_data, 0)

圖像縮放,達到可視化最佳。

        resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
        cv2.imshow('Intel', resized)
        cv2.waitKey(1)

至此,完整代碼如下

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import *
import random
import numpy as np
import cv2


class SentdeBot(sc2.BotAI):
    def __init__(self):
        # 經過計算,每分鐘大約165迭代次數
        self.ITERATIONS_PER_MINUTE = 165
        # 最大农民數量
        self.MAX_WORKERS = 65

    async def on_step(self, iteration: int):
        self.iteration = iteration
        await self.distribute_workers()
        await self.build_workers()
        await self.build_pylons()
        await self.build_assimilators()
        await self.expand()
        await self.offensive_force_buildings()
        await self.build_offensive_force()
        await self.intel()
        await self.attack()

    async def intel(self):
        # 根據地圖建立的三維矩陣
        game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)
        for nexus in self.units(UnitTypeId.NEXUS):
            nex_pos = nexus.position
            # circle(承載圓的img, 圓心, 半徑, 顏色, thickness=-1表示填充)
            # 記錄星靈樞紐的位置
            cv2.circle(game_data, (int(nex_pos[0]), int(nex_pos[1])), 10, (0, 255, 0), -1)
        # 圖像翻轉垂直鏡像
        flipped = cv2.flip(game_data, 0)
        # 圖像縮放
        # cv2.resize(原圖像,輸出圖像的大小,width方向的縮放比例,height方向縮放的比例)
        resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
        cv2.imshow('Intel', resized)

        # cv2.waitKey(每Xms刷新圖像)
        cv2.waitKey(1)

    # 建造农民
    async def build_workers(self):
        # 星靈樞鈕*16(一個基地配備16個农民)大於農民數量並且現有农民數量小於MAX_WORKERS
        if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
                self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
            # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
            for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                # 是否有50晶體礦建造农民
                if self.can_afford(UnitTypeId.PROBE):
                    await self.do(nexus.train(UnitTypeId.PROBE))

    ## 建造水晶
    async def build_pylons(self):
        ## 供應人口和現有人口之差小於5且建築不是正在建造
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            nexuses = self.units(UnitTypeId.NEXUS).ready
            if nexuses.exists:
                if self.can_afford(UnitTypeId.PYLON):
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)

    ## 建造吸收廠
    async def build_assimilators(self):
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            # 在瓦斯泉上建造吸收廠
            vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
            for vaspene in vaspenes:
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                worker = self.select_build_worker(vaspene.position)
                if worker is None:
                    break
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene))

    ## 開礦
    async def expand(self):
        # (self.iteration / self.ITERATIONS_PER_MINUTE)是一個緩慢遞增的值,動態開礦
        if self.units(UnitTypeId.NEXUS).amount < self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(
                UnitTypeId.NEXUS):
            await self.expand_now()

    ## 建造進攻性建築
    async def offensive_force_buildings(self):
        print(self.iteration / self.ITERATIONS_PER_MINUTE)
        if self.units(UnitTypeId.PYLON).ready.exists:
            pylon = self.units(UnitTypeId.PYLON).ready.random
            # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
            # 否則建造折躍門
            # (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一個緩慢遞增的值
            # elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
            elif len(self.units(UnitTypeId.GATEWAY)) < 1:
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)
            # 控制核心存在的情況下建造星門
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                if len(self.units(UnitTypeId.STARGATE)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                    if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                        await self.build(UnitTypeId.STARGATE, near=pylon)

    ## 造兵
    async def build_offensive_force(self):
        # 無隊列化建造
        for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
            if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(UnitTypeId.VOIDRAY))

    ## 尋找目標
    def find_target(self, state):
        if len(self.known_enemy_units) > 0:
            # 隨機選取敵方單位
            return random.choice(self.known_enemy_units)
        elif len(self.known_enemy_units) > 0:
            # 隨機選取敵方建築
            return random.choice(self.known_enemy_structures)
        else:
            # 返回敵方出生點位
            return self.enemy_start_locations[0]

    ## 進攻
    async def attack(self):
        # {UNIT: [n to fight, n to defend]}
        aggressive_units = {UnitTypeId.VOIDRAY: [8, 3]}

        for UNIT in aggressive_units:
            # 攻擊模式
            if self.units(UNIT).amount > aggressive_units[UNIT][0] and self.units(UNIT).amount > aggressive_units[UNIT][1]:
                for s in self.units(UNIT).idle:
                    await self.do(s.attack(self.find_target(self.state)))
            # 防衛模式
            elif self.units(UNIT).amount > aggressive_units[UNIT][1]:
                if len(self.known_enemy_units) > 0:
                    for s in self.units(UNIT).idle:
                        await self.do(s.attack(random.choice(self.known_enemy_units)))


## 啟動遊戲
run_game(maps.get("AcidPlantLE"), [
    Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Hard)
], realtime=False)

運行結果如下

採集到了地圖位置。

偵察

在intel(self)里創建一個字典draw_dict,UnitTypeId作為key,半徑和顏色是value


        draw_dict = {
            UnitTypeId.NEXUS: [15, (0, 255, 0)],
            UnitTypeId.PYLON: [3, (20, 235, 0)],
            UnitTypeId.PROBE: [1, (55, 200, 0)],
            UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
            UnitTypeId.GATEWAY: [3, (200, 100, 0)],
            UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
            UnitTypeId.STARGATE: [5, (255, 0, 0)],
            UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],

            UnitTypeId.VOIDRAY: [3, (255, 100, 0)]
        }

迭代同上

for unit_type in draw_dict:
            for unit in self.units(unit_type).ready:
                pos = unit.position
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), draw_dict[unit_type][0], draw_dict[unit_type][1], -1)

存儲三族的主基地名稱(星靈樞紐,指揮中心,孵化場),刻畫敵方建築。

# 主基地名稱
        main_base_names = ["nexus", "supplydepot", "hatchery"]
        # 記錄敵方基地位置
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            if enemy_building.name.lower() not in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (200, 50, 212), -1)
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            if enemy_building.name.lower() in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 15, (0, 0, 255), -1)

刻畫敵方單位,如果是农民畫得小些,其他單位則畫大些。

        for enemy_unit in self.known_enemy_units:

            if not enemy_unit.is_structure:
                worker_names = ["probe", "scv", "drone"]
                # if that unit is a PROBE, SCV, or DRONE... it's a worker
                pos = enemy_unit.position
                if enemy_unit.name.lower() in worker_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (55, 0, 155), -1)
                else:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (50, 0, 215), -1)

在offensive_force_buildings(self)方法中添加建造机械台

            if self.units(CYBERNETICSCORE).ready.exists:
                if len(self.units(ROBOTICSFACILITY)) < 1:
                    if self.can_afford(ROBOTICSFACILITY) and not self.already_pending(ROBOTICSFACILITY):
                        await self.build(ROBOTICSFACILITY, near=pylon)

創建scout(),訓練Observer

async def scout(self):
        if len(self.units(OBSERVER)) > 0:
            scout = self.units(OBSERVER)[0]
            if scout.is_idle:
                enemy_location = self.enemy_start_locations[0]
                move_to = self.random_location_variance(enemy_location)
                print(move_to)
                await self.do(scout.move(move_to))

        else:
            for rf in self.units(ROBOTICSFACILITY).ready.noqueue:
                if self.can_afford(OBSERVER) and self.supply_left > 0:
                    await self.do(rf.train(OBSERVER))

生成隨機位置,很簡單。意思是橫坐標累計遞增-0.2和0.2倍的橫坐標,限制條件為如果x超過橫坐標,那麼就是橫坐標最大值。
縱坐標同理。

    def random_location_variance(self, enemy_start_location):
        x = enemy_start_location[0]
        y = enemy_start_location[1]

        x += ((random.randrange(-20, 20))/100) * enemy_start_location[0]
        y += ((random.randrange(-20, 20))/100) * enemy_start_location[1]

        if x < 0:
            x = 0
        if y < 0:
            y = 0
        if x > self.game_info.map_size[0]:
            x = self.game_info.map_size[0]
        if y > self.game_info.map_size[1]:
            y = self.game_info.map_size[1]

        go_to = position.Point2(position.Pointlike((x,y)))
        return go_to

完整代碼如下

# -*- encoding: utf-8 -*-
'''
@File    :   demo.py
@Modify Time      @Author       @Desciption
------------      -------       -----------
2019/11/3 12:32   Jonas           None
'''

import sc2
from sc2 import run_game, maps, Race, Difficulty, position
from sc2.player import Bot, Computer
from sc2.constants import *
import random
import numpy as np
import cv2


class SentdeBot(sc2.BotAI):
    def __init__(self):
        # 經過計算,每分鐘大約165迭代次數
        self.ITERATIONS_PER_MINUTE = 165
        # 最大农民數量
        self.MAX_WORKERS = 50

    async def on_step(self, iteration: int):
        self.iteration = iteration
        await self.scout()
        await self.distribute_workers()
        await self.build_workers()
        await self.build_pylons()
        await self.build_assimilators()
        await self.expand()
        await self.offensive_force_buildings()
        await self.build_offensive_force()
        await self.intel()
        await self.attack()

    ## 偵察
    async def scout(self):
        if len(self.units(UnitTypeId.OBSERVER)) > 0:
            scout = self.units(UnitTypeId.OBSERVER)[0]
            if scout.is_idle:
                enemy_location = self.enemy_start_locations[0]
                move_to = self.random_location_variance(enemy_location)
                print(move_to)
                await self.do(scout.move(move_to))

        else:
            for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
                if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
                    await self.do(rf.train(UnitTypeId.OBSERVER))

    async def intel(self):
        game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)

        # UnitTypeId作為key,半徑和顏色是value
        draw_dict = {
            UnitTypeId.NEXUS: [15, (0, 255, 0)],
            UnitTypeId.PYLON: [3, (20, 235, 0)],
            UnitTypeId.PROBE: [1, (55, 200, 0)],
            UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
            UnitTypeId.GATEWAY: [3, (200, 100, 0)],
            UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
            UnitTypeId.STARGATE: [5, (255, 0, 0)],
            UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],

            UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
            # OBSERVER: [3, (255, 255, 255)],
        }

        for unit_type in draw_dict:
            for unit in self.units(unit_type).ready:
                pos = unit.position
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), draw_dict[unit_type][0], draw_dict[unit_type][1], -1)

        # 主基地名稱
        main_base_names = ["nexus", "supplydepot", "hatchery"]
        # 記錄敵方基地位置
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            # 不是主基地建築,畫小一些
            if enemy_building.name.lower() not in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (200, 50, 212), -1)
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            if enemy_building.name.lower() in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 15, (0, 0, 255), -1)

        for enemy_unit in self.known_enemy_units:

            if not enemy_unit.is_structure:
                worker_names = ["probe", "scv", "drone"]
                # if that unit is a PROBE, SCV, or DRONE... it's a worker
                pos = enemy_unit.position
                if enemy_unit.name.lower() in worker_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (55, 0, 155), -1)
                else:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (50, 0, 215), -1)

        for obs in self.units(UnitTypeId.OBSERVER).ready:
            pos = obs.position
            cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 255, 255), -1)

        # flip horizontally to make our final fix in visual representation:
        flipped = cv2.flip(game_data, 0)
        resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)

        cv2.imshow('Intel', resized)
        cv2.waitKey(1)

    def random_location_variance(self, enemy_start_location):
        x = enemy_start_location[0]
        y = enemy_start_location[1]

        x += ((random.randrange(-20, 20)) / 100) * enemy_start_location[0]
        y += ((random.randrange(-20, 20)) / 100) * enemy_start_location[1]

        if x < 0:
            x = 0
        if y < 0:
            y = 0
        if x > self.game_info.map_size[0]:
            x = self.game_info.map_size[0]
        if y > self.game_info.map_size[1]:
            y = self.game_info.map_size[1]

        go_to = position.Point2(position.Pointlike((x, y)))
        return go_to

    # 建造农民
    async def build_workers(self):
        # 星靈樞鈕*16(一個基地配備16個农民)大於農民數量並且現有农民數量小於MAX_WORKERS
        if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
                self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
            # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
            for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                # 是否有50晶體礦建造农民
                if self.can_afford(UnitTypeId.PROBE):
                    await self.do(nexus.train(UnitTypeId.PROBE))

    ## 建造水晶
    async def build_pylons(self):
        ## 供應人口和現有人口之差小於5且建築不是正在建造
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            nexuses = self.units(UnitTypeId.NEXUS).ready
            if nexuses.exists:
                if self.can_afford(UnitTypeId.PYLON):
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)

    ## 建造吸收廠
    async def build_assimilators(self):
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            # 在瓦斯泉上建造吸收廠
            vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
            for vaspene in vaspenes:
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                worker = self.select_build_worker(vaspene.position)
                if worker is None:
                    break
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene))

    ## 開礦
    async def expand(self):
        # (self.iteration / self.ITERATIONS_PER_MINUTE)是一個緩慢遞增的值,動態開礦
        if self.units(UnitTypeId.NEXUS).amount < self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(
                UnitTypeId.NEXUS):
            await self.expand_now()

    ## 建造進攻性建築
    async def offensive_force_buildings(self):
        print(self.iteration / self.ITERATIONS_PER_MINUTE)
        if self.units(UnitTypeId.PYLON).ready.exists:
            pylon = self.units(UnitTypeId.PYLON).ready.random
            # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
            # 否則建造折躍門
            # (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一個緩慢遞增的值
            # elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
            elif len(self.units(UnitTypeId.GATEWAY)) < 1:
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)
            # 控制核心存在的情況下建造机械台
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                if len(self.units(UnitTypeId.ROBOTICSFACILITY)) < 1:
                    if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(
                            UnitTypeId.ROBOTICSFACILITY):
                        await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)

            # 控制核心存在的情況下建造星門
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                if len(self.units(UnitTypeId.STARGATE)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                    if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                        await self.build(UnitTypeId.STARGATE, near=pylon)

    ## 造兵
    async def build_offensive_force(self):
        # 無隊列化建造
        # for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
        #     if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount:
        #
        #         if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
        #             await self.do(gw.train(UnitTypeId.STALKER))

        for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
            if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(UnitTypeId.VOIDRAY))

    ## 尋找目標
    def find_target(self, state):
        if len(self.known_enemy_units) > 0:
            # 隨機選取敵方單位
            return random.choice(self.known_enemy_units)
        elif len(self.known_enemy_units) > 0:
            # 隨機選取敵方建築
            return random.choice(self.known_enemy_structures)
        else:
            # 返回敵方出生點位
            return self.enemy_start_locations[0]

    ## 進攻
    async def attack(self):
        # {UNIT: [n to fight, n to defend]}
        aggressive_units = {UnitTypeId.VOIDRAY: [8, 3]}

        for UNIT in aggressive_units:
            # 攻擊模式
            if self.units(UNIT).amount > aggressive_units[UNIT][0] and self.units(UNIT).amount > aggressive_units[UNIT][
                1]:
                for s in self.units(UNIT).idle:
                    await self.do(s.attack(self.find_target(self.state)))
            # 防衛模式
            elif self.units(UNIT).amount > aggressive_units[UNIT][1]:
                if len(self.known_enemy_units) > 0:
                    for s in self.units(UNIT).idle:
                        await self.do(s.attack(random.choice(self.known_enemy_units)))


## 啟動遊戲
run_game(maps.get("AcidPlantLE"), [
    Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Hard)
], realtime=False)

運行結果如下,紅色和粉紅色是敵方單位。

創建訓練數據

統計資源、人口和軍隊人口比,在intel方法添加如下代碼

        # 追蹤資源、人口和軍隊人口比
        line_max = 50
        mineral_ratio = self.minerals / 1500
        if mineral_ratio > 1.0:
            mineral_ratio = 1.0

        vespene_ratio = self.vespene / 1500
        if vespene_ratio > 1.0:
            vespene_ratio = 1.0

        population_ratio = self.supply_left / self.supply_cap
        if population_ratio > 1.0:
            population_ratio = 1.0

        plausible_supply = self.supply_cap / 200.0

        military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
        if military_weight > 1.0:
            military_weight = 1.0

        # 农民/人口      worker/supply ratio
        cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
        # 人口/200    plausible supply (supply/200.0)
        cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
        # (人口-現有人口)/人口  population ratio (supply_left/supply)
        cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
        # 氣體/1500   gas/1500
        cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
        # 晶體礦/1500  minerals minerals/1500
        cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)

運行結果如下,左下角自上而下依次是“农民/人口”,“人口/200”,“(人口-現有人口)/人口”,“氣體/1500”,“晶體礦/1500”

採集進攻行為數據,在attack方法中加入如下代碼

        if len(self.units(UnitTypeId.VOIDRAY).idle) > 0:
            choice = random.randrange(0, 4)
            target = False
            if self.iteration > self.do_something_after:
                if choice == 0:
                    # 什麼都不做
                    wait = random.randrange(20, 165)
                    self.do_something_after = self.iteration + wait

                elif choice == 1:
                    # 攻擊離星靈樞紐最近的單位
                    if len(self.known_enemy_units) > 0:
                        target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))

                elif choice == 2:
                    # 攻擊敵方建築
                    if len(self.known_enemy_structures) > 0:
                        target = random.choice(self.known_enemy_structures)

                elif choice == 3:
                    # 攻擊敵方出生位置
                    target = self.enemy_start_locations[0]

                if target:
                    for vr in self.units(UnitTypeId.VOIDRAY).idle:
                        await self.do(vr.attack(target))
                y = np.zeros(4)
                y[choice] = 1
                print(y)
                self.train_data.append([y, self.flipped])

輸出如下結果

···
[1. 0. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]
[0. 0. 1. 0.]
[1. 0. 0. 0.]
···

為了使用self.flipped = cv2.flip(game_data, 0),修改

        flipped = cv2.flip(game_data, 0)
        resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)

        self.flipped = cv2.flip(game_data, 0)
        resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)

init 方法添加do_something_after和train_data

    def __init__(self):
        self.ITERATIONS_PER_MINUTE = 165
        self.MAX_WORKERS = 50
        self.do_something_after = 0
        self.train_data = []

採集攻擊數據的時候不需要畫圖,我們在類前加HEADLESS = False,intel方法代碼修改如下

        self.flipped = cv2.flip(game_data, 0)

        if not HEADLESS:
            resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)
            cv2.imshow('Intel', resized)
            cv2.waitKey(1)

加入on_end方法,只存儲勝利的數據,在和代碼同級目錄新建train_data文件夾

    def on_end(self, game_result):
        print('--- on_end called ---')
        print(game_result)

        if game_result == Result.Victory:
            np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data))

完整代碼如下

import os
import time

import sc2
from sc2 import run_game, maps, Race, Difficulty, position, Result
from sc2.player import Bot, Computer
from sc2.constants import *
import random
import numpy as np
import cv2

HEADLESS = True
# os.environ["SC2PATH"] = 'F:\StarCraft II'

class SentdeBot(sc2.BotAI):
    def __init__(self):
        # 經過計算,每分鐘大約165迭代次數
        self.ITERATIONS_PER_MINUTE = 165
        # 最大农民數量
        self.MAX_WORKERS = 50
        self.do_something_after = 0
        self.train_data = []

    def on_end(self, game_result):
        print('--- on_end called ---')
        print(game_result)

        if game_result == Result.Victory:
            np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data))

    async def on_step(self, iteration: int):
        self.iteration = iteration
        await self.scout()
        await self.distribute_workers()
        await self.build_workers()
        await self.build_pylons()
        await self.build_assimilators()
        await self.expand()
        await self.offensive_force_buildings()
        await self.build_offensive_force()
        await self.intel()
        await self.attack()

    ## 偵察
    async def scout(self):
        if len(self.units(UnitTypeId.OBSERVER)) > 0:
            scout = self.units(UnitTypeId.OBSERVER)[0]
            if scout.is_idle:
                enemy_location = self.enemy_start_locations[0]
                move_to = self.random_location_variance(enemy_location)
                print(move_to)
                await self.do(scout.move(move_to))

        else:
            for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
                if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
                    await self.do(rf.train(UnitTypeId.OBSERVER))

    async def intel(self):
        game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)

        # UnitTypeId作為key,半徑和顏色是value
        draw_dict = {
            UnitTypeId.NEXUS: [15, (0, 255, 0)],
            UnitTypeId.PYLON: [3, (20, 235, 0)],
            UnitTypeId.PROBE: [1, (55, 200, 0)],
            UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
            UnitTypeId.GATEWAY: [3, (200, 100, 0)],
            UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
            UnitTypeId.STARGATE: [5, (255, 0, 0)],
            UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],

            UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
            # OBSERVER: [3, (255, 255, 255)],
        }

        for unit_type in draw_dict:
            for unit in self.units(unit_type).ready:
                pos = unit.position
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), draw_dict[unit_type][0], draw_dict[unit_type][1], -1)

        # 主基地名稱
        main_base_names = ["nexus", "supplydepot", "hatchery"]
        # 記錄敵方基地位置
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            # 不是主基地建築,畫小一些
            if enemy_building.name.lower() not in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (200, 50, 212), -1)
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            if enemy_building.name.lower() in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 15, (0, 0, 255), -1)

        for enemy_unit in self.known_enemy_units:

            if not enemy_unit.is_structure:
                worker_names = ["probe", "scv", "drone"]
                # if that unit is a PROBE, SCV, or DRONE... it's a worker
                pos = enemy_unit.position
                if enemy_unit.name.lower() in worker_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (55, 0, 155), -1)
                else:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (50, 0, 215), -1)

        for obs in self.units(UnitTypeId.OBSERVER).ready:
            pos = obs.position
            cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 255, 255), -1)


        # 追蹤資源、人口和軍隊人口比
        line_max = 50
        mineral_ratio = self.minerals / 1500
        if mineral_ratio > 1.0:
            mineral_ratio = 1.0

        vespene_ratio = self.vespene / 1500
        if vespene_ratio > 1.0:
            vespene_ratio = 1.0

        population_ratio = self.supply_left / self.supply_cap
        if population_ratio > 1.0:
            population_ratio = 1.0

        plausible_supply = self.supply_cap / 200.0

        military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
        if military_weight > 1.0:
            military_weight = 1.0

        # 农民/人口      worker/supply ratio
        cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
        # 人口/200    plausible supply (supply/200.0)
        cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
        # (人口-現有人口)/人口  population ratio (supply_left/supply)
        cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
        # 氣體/1500   gas/1500
        cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
        # 晶體礦/1500  minerals minerals/1500
        cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)




        # flip horizontally to make our final fix in visual representation:
        self.flipped = cv2.flip(game_data, 0)

        if HEADLESS:
            resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)

            cv2.imshow('Intel', resized)
            cv2.waitKey(1)

    def random_location_variance(self, enemy_start_location):
        x = enemy_start_location[0]
        y = enemy_start_location[1]

        x += ((random.randrange(-20, 20)) / 100) * enemy_start_location[0]
        y += ((random.randrange(-20, 20)) / 100) * enemy_start_location[1]

        if x < 0:
            x = 0
        if y < 0:
            y = 0
        if x > self.game_info.map_size[0]:
            x = self.game_info.map_size[0]
        if y > self.game_info.map_size[1]:
            y = self.game_info.map_size[1]

        go_to = position.Point2(position.Pointlike((x, y)))
        return go_to

    # 建造农民
    async def build_workers(self):
        # 星靈樞鈕*16(一個基地配備16個农民)大於農民數量並且現有农民數量小於MAX_WORKERS
        if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
                self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
            # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
            for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                # 是否有50晶體礦建造农民
                if self.can_afford(UnitTypeId.PROBE):
                    await self.do(nexus.train(UnitTypeId.PROBE))

    ## 建造水晶
    async def build_pylons(self):
        ## 供應人口和現有人口之差小於5且建築不是正在建造
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            nexuses = self.units(UnitTypeId.NEXUS).ready
            if nexuses.exists:
                if self.can_afford(UnitTypeId.PYLON):
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)

    ## 建造吸收廠
    async def build_assimilators(self):
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            # 在瓦斯泉上建造吸收廠
            vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
            for vaspene in vaspenes:
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                worker = self.select_build_worker(vaspene.position)
                if worker is None:
                    break
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene))

    ## 開礦
    async def expand(self):
        # (self.iteration / self.ITERATIONS_PER_MINUTE)是一個緩慢遞增的值,動態開礦
        if self.units(UnitTypeId.NEXUS).amount < self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(
                UnitTypeId.NEXUS):
            await self.expand_now()

    ## 建造進攻性建築
    async def offensive_force_buildings(self):
        # print(self.iteration / self.ITERATIONS_PER_MINUTE)
        if self.units(UnitTypeId.PYLON).ready.exists:
            pylon = self.units(UnitTypeId.PYLON).ready.random
            # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
            # 否則建造折躍門
            # (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一個緩慢遞增的值
            # elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
            elif len(self.units(UnitTypeId.GATEWAY)) < 1:
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)
            # 控制核心存在的情況下建造机械台
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                if len(self.units(UnitTypeId.ROBOTICSFACILITY)) < 1:
                    if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(
                            UnitTypeId.ROBOTICSFACILITY):
                        await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)

            # 控制核心存在的情況下建造星門
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                if len(self.units(UnitTypeId.STARGATE)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                    if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                        await self.build(UnitTypeId.STARGATE, near=pylon)

    ## 造兵
    async def build_offensive_force(self):
        # 無隊列化建造
        # for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
        #     if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount:
        #
        #         if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
        #             await self.do(gw.train(UnitTypeId.STALKER))

        for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
            if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(UnitTypeId.VOIDRAY))

    ## 尋找目標
    def find_target(self, state):
        if len(self.known_enemy_units) > 0:
            # 隨機選取敵方單位
            return random.choice(self.known_enemy_units)
        elif len(self.known_enemy_units) > 0:
            # 隨機選取敵方建築
            return random.choice(self.known_enemy_structures)
        else:
            # 返回敵方出生點位
            return self.enemy_start_locations[0]

    ## 進攻
    async def attack(self):
        if len(self.units(UnitTypeId.VOIDRAY).idle) > 0:
            choice = random.randrange(0, 4)
            target = False
            if self.iteration > self.do_something_after:
                if choice == 0:
                    # 什麼都不做
                    wait = random.randrange(20, 165)
                    self.do_something_after = self.iteration + wait

                elif choice == 1:
                    # 攻擊離星靈樞紐最近的單位
                    if len(self.known_enemy_units) > 0:
                        target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))

                elif choice == 2:
                    # 攻擊敵方建築
                    if len(self.known_enemy_structures) > 0:
                        target = random.choice(self.known_enemy_structures)

                elif choice == 3:
                    # 攻擊敵方出生位置
                    target = self.enemy_start_locations[0]

                if target:
                    for vr in self.units(UnitTypeId.VOIDRAY).idle:
                        await self.do(vr.attack(target))
                y = np.zeros(4)
                y[choice] = 1
                print(y)
                self.train_data.append([y, self.flipped])


## 啟動遊戲
run_game(maps.get("AcidPlantLE"), [
    Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Medium)
], realtime=False)

可以看到train_data文件夾下存儲了勝利數據

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

java中hashmap容量的初始化

HashMap使用HashMap(int initialCapacity)對集合進行初始化。

在默認的情況下,HashMap的容量是16。但是如果用戶通過構造函數指定了一個数字作為容量,那麼Hash會選擇大於該数字的第一個2的冪作為容量。比如如果指定了3,則容量是4;如果指定了7,則容量是8;如果指定了9,則容量是16。

為什麼要設置HashMap的初始化容量

在《阿里巴巴Java開發手冊》中,有一條開發建議是建議我們設置HashMap的初始化容量。

下面我們通過具體的代碼來了解下為什麼會這麼建議。

我們先來寫一段代碼在JDK1.7的環境下運行,來分別測試下,在不指定初始化容量和指定初始化容量的情況下性能情況的不同。

public static void main(String[] args) {
    int aHundredMillion = 10000000;

    // 未初始化容量
    Map<Integer, Integer> map = new HashMap<>();
    long s1 = System.currentTimeMillis();
    for (int i = 0; i < aHundredMillion; i++) {
        map.put(i, i);
    }
    long s2 = System.currentTimeMillis();
    System.out.println("未初始化容量,耗時: " + (s2 - s1)); // 14322

    // 初始化容量為50000000
    Map<Integer, Integer> map1 = new HashMap<>(aHundredMillion / 2);
    long s3 = System.currentTimeMillis();
    for (int i = 0; i < aHundredMillion; i++) {
        map1.put(i, i);
    }
    long s4 = System.currentTimeMillis();
    System.out.println("初始化容量5000000,耗時: " + (s4 - s3)); // 11819

    // 初始化容量為100000000
    Map<Integer, Integer> map2 = new HashMap<>(aHundredMillion);
    long s5 = System.currentTimeMillis();
    for (int i = 0; i < aHundredMillion; i++) {
        map2.put(i, i);
    }
    long s6 = System.currentTimeMillis();
    System.out.println("初始化容量為10000000,耗時: " + (s6 - s5)); // 7978
}

從以上的代碼不難理解,我們創建了3個HashMap,分別使用默認的容量(16)、使用元素個數的一半(5千萬)作為初始容量和使用元素個數(一億)作為初始容量進行初始化,然後分別向其中put一億個KV。

從上面的打印結果中可以得到一個初步的結論:在已知HashMap中將要存放的KV個數的時候,設置一個合理的初始化容量可以有效地提高性能。下面我們來簡單分析一下原因。

我們知道,HashMap是有擴容機制的。所謂的擴容機制,指的是當達到擴容條件的時候,HashMap就會自動進行擴容。而HashMap的擴容條件就是當HashMap中的元素個數(Size)超過臨界值(Threshold)的情況下就會自動擴容。

threshold = loadFactor * capacity

在元素個數超過臨界值的情況下,隨着元素的不斷增加,HashMap就會發生擴容,而HashMap中的擴容機制決定了每次擴容都需要重建hash表,這一操作需要消耗大量資源,是非常影響性能的。因此,如果我們沒有設置初始的容量大小,HashMap就可能會不斷髮生擴容,也就使得程序的性能降低了。

另外,在上面的代碼中我們會發現,同樣是設置了初始化容量,設置的數值不同也會影響性能,那麼當我們已知HashMap中即將存放的KV個數的時候,容量的設置就成了一個問題。

HashMap中容量的初始化

開頭提到,在默認的情況下,當我們設置HashMap的初始化容量時,實際上HashMap會採用第一個大於該數值的2的冪作為初始化容量。

Map<String, String> map = new HashMap<>(1);
map.put("huangq", "yanggb");

Class<?> mapType = map.getClass();
Method capacity = mapType.getDeclaredMethod("capacity");
capacity.setAccessible(true);
System.out.println("capacity : " + capacity.invoke(map)); // 2

當初始化的容量設置成1的時候,通過反射取出來的capacity卻是2。在JDK1.8中,如果我們傳入的初始化容量為1,實際上設置的結果也是1。上面的代碼打印的結果為2的原因,是代碼中給map塞入值的操作導致了擴容,容量從1擴容到了2。事實上,在JDK1.7和JDK1.8中,HashMap初始化容量(capacity)的時機不同。在JDK1.8中,調用HashMap的構造函數定義HashMap的時候,就會進行容量的設定。而在JDK1.7中,要等到第一次put操作時才進行這一操作。

因此,當我們通過HashMap(int initialCapacity)設置初始容量的時候,HashMap並不一定會直接採用我們傳入的數值,而是經過計算,得到一個新值,目的是提高hash的效率。比如1->1、3->4、7->8和9->16。

HashMap中初始容量的合理值

通過上面的分析我們可以知道,當我們使用HashMap(int initialCapacity)來初始化容量的時候,JDK會默認幫我們計算一個相對合理的值當做初始容量。那麼,是不是我們只需要把已知的HashMap中即將存放的元素個數直接傳給initialCapacity就可以了呢?

initialCapacity = (需要存儲的元素個數 / 負載因子) + 1

這裏的負載因子就是loaderFactor,默認值為0.75。

initialCapacity = expectedSize / 0.75F + 1.0F

上面這個公式是《阿里巴巴Java開發手冊》中的一個建議,在Guava中也是提供了相同的算法,更甚之,這個算法實際上是JDK8中putAll()方法的實現。這是公式的得出是因為,當HashMap內部維護的哈希表的容量達到75%時(默認情況下),就會觸發rehash(重建hash表)操作。而rehash的過程是比較耗費時間的。所以初始化容量要設置成expectedSize/0.75 + 1的話,可以有效地減少衝突,也可以減小誤差。

總結

當我們想要在代碼中創建一個HashMap的時候,如果我們已知這個Map中即將存放的元素個數,給HashMap設置初始容量可以在一定程度上提升效率。

但是,JDK並不會直接拿用戶傳進來的数字當做默認容量,而是會進行一番運算,最終得到一個2的冪。而為了最大程度地避免擴容帶來的性能消耗,通常是建議可以把默認容量的数字設置成expectedSize / 0.75F + 1.0F。

在日常開發中,可以使用Guava提供的一個方法來創建一個HashMap,計算的過程Guava會幫我們完成。

Map<String, String> map = Maps.newHashMapWithExpectedSize(10);

最後要說的一點是,這種算法實際上是一種使用內存換取性能的做法,在真正的應用場景中要考慮到內存的影響。

 

“當你認真喜歡一個人的時候,你的全世界都是她。”

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

【python測試開發棧】帶你徹底搞明白python3編碼原理

在之前的文章中,我們介紹過編碼格式的發展史:[文章傳送門-todo]。今天我們通過幾個例子,來徹底搞清楚python3中的編碼格式原理,這樣你之後寫python腳本時碰到編碼問題,才能有章可循。

我們先搞清楚幾個概念:

  • 系統默認編碼:指python解釋器默認的編碼格式,在python文件頭部沒有聲明其他編碼格式時,python3默認的編碼格式是utf-8。
  • 本地默認編碼:操作系統默認的編碼,常見的Windows的默認編碼是gbk,Linux的默認編碼是UTF-8。
  • python文件頭部聲明編碼格式:修改的是文件的默認編碼格式,只是會影響python解釋器讀取python文件時的編碼格式,並不會改變系統默認編碼和本地默認編碼。

通過python自帶的庫,可以查看系統默認編碼和本地默認編碼

Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
>>> import locale
>>> locale.getdefaultlocale()
('zh_CN', 'cp936')
>>>

注意,因為我在windows系統的電腦上 進行測試,所以系統默認編碼返回“cp936”, 這是代碼頁(是字符編碼集的別名),而936對應的就是gbk。如果你在linux或者mac上執行上面的代碼,應該會返回utf-8編碼。

其實總結來看,容易出現亂碼的場景,基本都與讀寫程序有關,比如:讀取/寫入某個文件,或者從網絡流中讀取數據等,因為這個過程中涉及到了編碼解碼的過程,只要編碼和解碼的編碼格式對應不上,就容易出現亂碼。下面我們舉兩個具體的例子,來驗證下python的編碼原理,幫助你理解這個過程。注意:下面的例子都是在pycharm中寫的。

01默認的編碼格式

我們新建一個encode_demo.py的文件,其文件默認的編碼格式是UTF-8(可以從pycharm右下角看到編碼格式),代碼如下:

"""
    @author: asus
    @time: 2019/11/21
    @function: 驗證編碼格式
"""
import sys, locale


def write_str_default_encode():
    s = "我是一個str"
    print(s)
    print(type(s))
    print(sys.getdefaultencoding())
    print(locale.getdefaultlocale())

    with open("utf_file", "w", encoding="utf-8") as f:
        f.write(s)
    with open("gbk_file", "w", encoding="gbk") as f:
        f.write(s)
    with open("jis_file", "w", encoding="shift-jis") as f:
        f.write(s)


if __name__ == '__main__':
    write_str_default_encode()

我們先來猜測下結果,因為我們沒有聲明編碼格式,所以python解釋器默認用UTF-8去解碼文件,因為文件默認編碼格式就是UTF-8,所以字符串s可以正常打印。同時以UTF-8編碼格式寫文件不會出現亂碼,而以gbk和shift-jis(日文編碼)寫文件會出現亂碼(這裏說明一點,我是用pycharm直接打開生成的文件查看的,編輯器默認編碼是UTF-8,如果在windows上用記事本打開則其默認編碼跟隨系統是GBK,gbk_file和utf_file均不會出現亂碼,只有jis_file是亂碼),我們運行看下結果:

# 運行結果
我是一個str
<class 'str'>
utf-8
('zh_CN', 'cp936')

# 寫文件utf_file、gbk_file、jis_file文件內容分別是:
我是一個str
����һ��str
�䐥�꘢str

和我們猜測的結果一致,下面我們做個改變,在文件頭部聲明個編碼格式,再來看看效果。

02 python頭文件聲明編碼格式

因為上面文件encode_demo.py的格式是UTF-8,那麼我們就將其變為gbk編碼。同樣的我們先來推測下結果,在pycharm中,在python文件頭部聲明編碼為gbk后(頭部加上 # coding=gbk ),文件的編碼格式變成gbk,同時python解釋器會用gbk去解碼encode_demo.py文件,所以運行結果應該和用UTF-8編碼時一樣。運行結果如下:

# 運行結果
我是一個str
<class 'str'>
utf-8
('zh_CN', 'cp936')

# 寫文件utf_file、gbk_file、jis_file文件內容分別是:
我是一個str
����һ��str
�䐥�꘢str

結果確實是一樣的,證明我們推論是正確的。接下來我們再做個嘗試,假如我們將(# coding=gbk)去掉(需要注意,在pycharm中將 # coding=gbk去掉,並不會改變文件的編碼格式,也就是說encode_demo.py還是gbk編碼),我們再運行一次看結果:

  File "D:/codespace/python/pythonObject/pythonSample/basic/encodeDemo/encode_demo.py", line 4
SyntaxError: Non-UTF-8 code starting with '\xd1' in file D:/codespace/python/pythonObject/pythonSample/basic/encodeDemo/encode_demo.py on line 5, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

運行直接報錯了,我們加個斷點,看看具體的異常信息:

看錯誤提示是UnicodeDecodeError,python解釋器在對encode_demo.py文件解碼時,使用默認的UTF-8編碼,但是文件本身是gbk編碼,所以當碰到有中文沒辦法識別時,就拋出DecodeError。

03 敲黑板,划重點

python3中的str和bytes

python3的重要特性之一就是對字符串和二進制流做了嚴格的區分,我們聲明的字符串都是str類型,不過Str和bytes是可以相互轉換的:

def str_transfor_bytes():
    s = '我是一個測試Str'
    print(type(s))
    # str 轉bytes
    b = s.encode()
    print(b)
    print(type(b))
    # bytes轉str
    c = b.decode('utf-8')
    print(c)
    print(type(c))


if __name__ == '__main__':
    str_transfor_bytes()

需要注意一點:在調用encode()和decode()方法時,如果不傳參數,則會使用python解釋器默認的編碼格式UTF-8(如果不在python頭文件聲明編碼格式)。但是如果傳參的話,encode和decode使用的編碼格式要能對應上。

python3默認編碼是UTF-8?還是Unicode?

經常在很多文章里看到,python3的默認編碼格式是Unicode,但是我在本文中卻一直在說python3的默認編碼格式是UTF-8,那麼哪種說法是正確的呢?其實兩種說法都對,主要得搞清楚Unicode和UTF-8的區別(之前文章有提到):

  • Unicode是一個字符集,說白了就是把各種編碼的映射關係全都整合起來,不過它是不可變長的,全部都以兩個字節或四個字節來表示,佔用的內存空間比較大。
  • UTF-8是Unicode的一種實現方式,主要對 Unicode 碼的數據進行轉換,方便存儲和網絡傳輸 。它是可變長編碼,比如對於英文字母,它使用一個字節就可以表示。

在python3內存中使用的字符串全都是Unicode碼,當python解釋器解析python文件時,默認使用UTF-8編碼。

open()方法默認使用本地編碼

在上面的例子中,我們往磁盤寫入文件時,都指定了編碼格式。如果不指定編碼格式,那麼默認將使用操作系統本地默認的編碼格式,比如:Linux默認是UTF-8,windows默認是GBK。其實這也好理解,因為和磁盤交互,肯定要考慮操作系統的編碼格式。這有區別於encode()和decode()使用的是python解釋器的默認編碼格式,千萬別搞混淆了。

總結

不知道你看完上面的例子后,是否已經徹底理解了python3的編碼原理。不過所有的編碼問題,都逃不過“編碼”和“解碼”兩個過程,當你碰到編碼問題時,先確定源文件使用的編碼,再確定目標文件需要的編碼格式,只要能匹配,一般就可以解決編碼的問題。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

面向對象和面向過程詳解

1.前言

其實一直對面向過程和面向對象的概念和區別沒有很深入的理解,在自己不斷想完善自己的知識體系中,今天借這個時間,寫一篇博客。來深入的了解面向過程與面向對象!好記性不如爛筆頭!!  

2.面向對象與面向過程的區別

面向過程就是分析出解決問題所需要的步驟,然後用函數把這些步驟一步一步實現,使用的時候一個一個依次調用就可以了;面向對象是把構成問題事務分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描敘某個事物在整個解決問題的步驟中的行為。

舉一個下五子棋通俗例子吧 哈哈哈 我感覺這兒例子很容易讓人理解

面向過程的設計思路就是首先分析問題的步驟:

1、開始遊戲,

2、黑子先走,

3、繪製畫面,

4、判斷輸贏,

5、輪到白子,

6、繪製畫面,

7、判斷輸贏,

8、返回步驟2,

9、輸出最後結果。

把上面每個步驟用分別的函數來實現,問題就解決了。

面向對象的設計則是從另外的思路來解決問題。整個五子棋可以分為

1、黑白雙方,這兩方的行為是一模一樣的,

2、棋盤系統,負責繪製畫面,

3、規則系統,負責判定諸如犯規、輸贏等。第一類對象(玩家對象)負責接受用戶輸入,並告知第二類對象(棋盤對象)棋子布局的變化,

棋盤對象接收到了棋子的i變化就要負責在屏幕上面显示出這種變化,同時利用第三類對象(規則系統)來對棋局進行判定。

可以明顯地看出,面向對象是以功能來劃分問題,而不是步驟。同樣是繪製棋局,這樣的行為在面向過程的設計中分散在了總多步驟中,很可能出現不同的繪製版本,因為通常設計人員會考慮到實際情況進行各種各樣的簡化。而面向對象的設計中,繪圖只可能在棋盤對象中出現,從而保證了繪圖的統一。

功能上的統一保證了面向對象設計的可擴展性。比如要加入悔棋的功能,如果要改動面向過程的設計,那麼從輸入到判斷到显示這一連串的步驟都要改動,甚至步驟之間的循序都要進行大規模調整。如果是面向對象的話,只用改動棋盤對象就行了,棋盤系統保存了黑白雙方的棋譜,簡單回溯就可以了,而显示和規則判斷則不用顧及,同時整個對對象功能的調用順序都沒有變化,改動只是局部的。

再比如我要把這個五子棋遊戲改為圍棋遊戲,如果你是面向過程設計,那麼五子棋的規則就分佈在了你的程序的每一個角落,要改動還不如重寫。但是如果你當初就是面向對象的設計,那麼你只用改動規則對象就可以了,五子棋和圍棋的區別不就是規則嗎?(當然棋盤大小好像也不一樣,但是你會覺得這是一個難題嗎?直接在棋盤對象中進行一番小改動就可以了。)而下棋的大致步驟從面向對象的角度來看沒有任何變化。

當然,要達到改動只是局部的需要設計的人有足夠的經驗,使用對象不能保證你的程序就是面向對象,初學者或者很蹩腳的程序員很可能以面向對象之虛而行面向過程之實,這樣設計出來的所謂面向對象的程序很難有良好的可移植性和可擴展性

三、面向過程與面向對象的優缺點
很多資料上全都是一群很難理解的理論知識,整的我頭都大了,後來發現了一個比較好的文章,寫的真是太棒了,通俗易懂,想要不明白都難!

用面向過程的方法寫出來的程序是一份蛋炒飯,而用面向對象寫出來的程序是一份蓋澆飯。所謂蓋澆飯,北京叫蓋飯,東北叫燴飯,廣東叫碟頭飯,就是在一碗白米飯上面澆上一份蓋菜,你喜歡什麼菜,你就澆上什麼菜。我覺得這個比喻還是比較貼切的。

蛋炒飯製作是把米飯和雞蛋混在一起炒勻。蓋澆飯呢,則是把米飯和蓋菜分別做好,你如果要一份紅燒肉蓋飯呢,就給你澆一份紅燒肉;如果要一份青椒土豆蓋澆飯,就給澆一份青椒土豆絲。

蛋炒飯的好處就是入味均勻,吃起來香。如果恰巧你不愛吃雞蛋,只愛吃青菜的話,那麼唯一的辦法就是全部倒掉,重新做一份青菜炒飯了。蓋澆飯就沒這麼多麻煩,你只需要把上面的蓋菜撥掉,更換一份蓋菜就可以了。蓋澆飯的缺點是入味不均,可能沒有蛋炒飯那麼香。

到底是蛋炒飯好還是蓋澆飯好呢?其實這類問題都很難回答,非要比個上下高低的話,就必須設定一個場景,否則只能說是各有所長。如果大家都不是美食家,沒那麼多講究,那麼從飯館角度來講的話,做蓋澆飯顯然比蛋炒飯更有優勢,他可以組合出來任意多的組合,而且不會浪費。

蓋澆飯的好處就是”菜”“飯”分離,從而提高了製作蓋澆飯的靈活性。飯不滿意就換飯,菜不滿意換菜。用軟件工程的專業術語就是”可維護性“比較好,”飯” 和”菜”的耦合度比較低。蛋炒飯將”蛋”“飯”攪和在一起,想換”蛋”“飯”中任何一種都很困難,耦合度很高,以至於”可維護性”比較差。軟件工程追求的目標之一就是可維護性,可維護性主要表現在3個方面:可理解性、可測試性和可修改性。面向對象的好處之一就是顯著的改善了軟件系統的可維護性。
看了這篇文章,簡單的總結一下!

面向過程

優點:性能比面向對象高,因為類調用時需要實例化,開銷比較大,比較消耗資源;比如嵌入式開發、 Linux/Unix等一般採用面向過程開發,性能是最重要的因素。
缺點:沒有面向對象易維護、易復用、易擴展
面向對象

優點:易維護、易復用、易擴展,由於面向對象有封裝、繼承、多態性的特性,可以設計出低耦合的系統,使系統 更加靈活、更加易於維護

缺點:性能比面向過程低

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

fastjason常用方法

什麼是fastjson?

Fastjson是一個Java語言編寫的高性能功能完善的JSON庫。它採用一種“假定有序快速匹配”的算法,把JSON Parse的性能提升到極致,是目前Java語言中最快的JSON庫。Fastjson接口簡單易用,已經被廣泛使用在緩存序列化、協議交互、Web輸出、Android客戶端等多種應用場景。

主要特點:

  • 快速FAST (比其它任何基於Java的解析器和生成器更快,包括jackson)
  • 強大(支持普通JDK類包括任意Java Bean Class、Collection、Map、Date或enum)
  • 零依賴(沒有依賴其它任何類庫除了JDK)

背景

最近關於fastjson的消息,引起了很多人的關注!

fastjson爆出重大漏洞,攻擊者可使整個業務癱瘓

漏洞描述

常用JSON組件FastJson存在遠程代碼執行漏洞,攻擊者可通過精心構建的json報文對目標服務器執行任意命令,從而獲得服務器權限。此次爆發的漏洞為以往漏洞中autoType的繞過。

影響範圍

FastJson < 1.2.48

很多開發者才猛然發現,fastjson已經深入到我們開發工作的方方面面。那麼除了趕快升級你的json外,我們來挖挖fastjson最常用的用法。

fastjson常用方式

1.maven依賴(記得升級到1.2.48以上版本哦)

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.62</version>
        </dependency>    

2.FastJson對於json格式字符串的解析主要用到了一下三個類:

(1)JSON:fastJson的解析器,用於JSON格式字符串與JSON對象及javaBean之間的轉換。

(2)JSONObject:fastJson提供的json對象。

(3)JSONArray:fastJson提供json數組對象。

3.常用方式

3.1 string和java對象

 

實例1:對象轉json字符串

        Map<String,String> map=new HashMap<String,String>();
        map.put("code","0");
        map.put("message","ok");
        String json=JSON.toJSONString(map);
        System.out.println(json);

輸出結果為:

{"code":"0","message":"ok"}

實例2:字符串轉對象

        Map<String,String> map=new HashMap<String,String>();
        map.put("code","0");
        map.put("message","ok");
        String json=JSON.toJSONString(map);
        System.out.println(json);
        
        Map obj=(Map)JSON.parse(json);
        System.out.println("code="+obj.get("code")+",message="+obj.get("message"));

輸出結果

{"code":"0","message":"ok"}
code=0,message=ok

3.2 工具類JSONObject

    public static void main(String[] args) {
        Map<String,String> map=new HashMap<String,String>();
        map.put("code","0");
        map.put("message","ok");
        String json=JSON.toJSONString(map);
        System.out.println(json);
        
        Map obj=(Map)JSON.parse(json);
        System.out.println("code="+obj.get("code")+",message="+obj.get("message"));        
        
        String code=JSON.parseObject(json).getString("code");
        String message=JSON.parseObject(json).getString("message");
        System.out.println("code="+code+",message="+message);
    }

輸出結果

{"code":"0","message":"ok"}
code=0,message=ok
code=0,message=ok

3.3 數組對象

List<user> list=new ArrayList<user>(JSONArray.parseArray(jsonString,user.class)); 

Fastjson 與各種JSON庫的性能比較:

 

json庫 序列化性能 反序列化性能 jar大小
fastjson 1201 1216 fastjson-1.1.26.jar(356k)
fastjson-1.1.25-android.jar(226k)
jackson 1408 1915 jackson-annotations-2.1.1.jar(34k)
jackson-core-2.1.1.jar(206k)
jackson-databind-2.1.1.jar(922k)
總共1162k
gson 7421 5065 gson-2.2.2.jar(189k)
json-lib 27555 87292 json-lib-2.4-jdk15.jar(159k)


本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

Maven系列第9篇:多環境構建支持,核心開發必備!

maven系列目標:從入門開始開始掌握一個高級開發所需要的maven技能。

這是maven系列第9篇。

整個maven系列的內容前後是有依賴的,如果之前沒有接觸過maven,建議從第一篇看起,本文尾部有maven完整系列的連接。

如果你作為公司核心開發,打算使用maven來搭建項目骨架,這篇文章的內容是你必須要掌握的。

平時我們在開發系統的時候,會有開發環境、測試環境、線上環境,每個環境中配置文件可能都是不一樣的,比如:數據庫的配置,靜態資源的配置等等,所以我們希望構建工具能夠適應不同環境的構建工作,能夠靈活處理,並且操作足夠簡單。Maven作為一款優秀的構建工具,這方面做的足夠好了,能夠很好的適應不同環境的構建工作,本文主要講解maven如何靈活的處理各種不同環境的構建工作,廢話不多說,上乾貨。

重點提示

本文中的所有案例均在上一篇的b2b項目上進行操作,上一篇還沒有看的可以移步過去看一下:

所有mvn命令均在b2b/pom.xml所在目錄執行。

Maven屬性

自定義屬性

maven屬性前面我們有用到過,可以自定義一些屬性進行重用,如下:

<properties>
    <spring.verion>5.2.1.RELEASE</spring.verion>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.verion}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.verion}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.verion}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${spring.verion}</version>
    </dependency>
</dependencies>

可以看到上面依賴了4個spring相關的構建,他們的版本都是一樣的,在properties元素中自定義了一個spring.version屬性,值為spring的版本號,其他幾個地方使用${}直接進行引用,這種方式好處還是比較明顯的,升級spring版本的時候非常方便,只需要修改一個地方,方便維護。

上面這個是maven自定義屬性,需要先在properties中定義,然後才可以在其他地方使用${屬性元素名稱}進行引用。

maven的屬性主要分為2大類,第一類就是上面說的自定義屬性,另外一類是不需要自定義的,可以直接拿來使用的。

2類屬性在pom.xml中都是採用${屬性名稱}進行引用,maven運行的時候會將${}替換為屬性實際的值。

下面我們來看一下maven中不需要自定義的5類屬性。

內置屬性

${basedir}:表示項目根目錄,即包含pom.xml文件的目錄
${version}:表示項目的版本號

POM屬性

用戶可以使用該屬性引用pom.xml文件中對應元素的值,例如${project.artifactId}就可以取到project->artifactId元素的值,常用的有:

${pom.build.sourceDirectory}:項目的主源碼目錄,默認為src/main/java/
${project.build.testSourceDirectory}:項目的測試源碼目錄,默認為src/test/java/
${project.build.directory}:項目構建輸出目錄,默認為target/
${project.build.outputDirectory}:項目主代碼編譯輸出目錄,默認為target/classes
${project.build.testOutputDirectory}:項目測試代碼編譯輸出目錄,默認為target/test-classes
${project.groupId}:項目的groupId
${project.artifactId}:項目的artifactId
${project.version}:項目的version,與${version}等價
${project.build.finalName}:項目打包輸出文件的名稱,默認為${project.artifactId}-${project.version}

Settings屬性

這種屬性以settings.開頭來引用~/.m2/settings.xml中的內容,如:

${settings.localRepository}

指向用戶本地倉庫的地址。

java系統屬性

所有java系統屬性都可以使用maven屬性來進行引用,例如${user.home}指向了當前用戶目錄。

java系統屬性可以通過mvn help:system命令看到。

環境變量屬性

所有的環境變量都可以使用env.開頭的方式來進行引用,如:

${env.JAVA_HOME}

可以獲取環境變量JAVA_HOME的值。

用戶可以使用mvn help:system命令查看所有環境變量的值。

上面的maven屬性,我們在pom.xml中通過${屬性名稱}可以靈活的引用,對我們寫pom.xml文件幫助還是比較大的。

實操案例

將下面配置放在b2b-account-service/pom.xml中:

<properties>
    <!-- 項目的主源碼目錄,默認為src/main/java/ -->
    <a>${pom.build.sourceDirectory}</a>
    <!-- 項目的測試源碼目錄,默認為src/test/java/ -->
    <b>${project.build.testSourceDirectory}</b>
    <!-- 項目構建輸出目錄,默認為target/ -->
    <c>${project.build.directory}</c>
    <!-- 項目主代碼編譯輸出目錄,默認為target/classes -->
    <d>${project.build.outputDirectory}</d>
    <!-- 項目測試代碼編譯輸出目錄,默認為target/test-classes -->
    <e>${project.build.testOutputDirectory}</e>
    <!-- 項目的groupId -->
    <f>${project.groupId}</f>
    <!-- 項目的artifactId -->
    <g>${project.artifactId}</g>
    <!-- 項目的version,與${version}等價-->
    <h>${project.version}</h>
    <!-- 項目打包輸出文件的名稱,默認為${project.artifactId}-${project.version} -->
    <i>${project.build.finalName}</i>

    <!-- setting屬性 -->
    <!-- 獲取本地倉庫地址-->
    <a1>${settings.localRepository}</a1>

    <!-- 系統屬性 -->
    <!-- 用戶目錄 -->
    <a2>${user.home}</a2>

    <!-- 環境變量屬性,獲取環境變量JAVA_HOME的值 -->
    <a3>${env.JAVA_HOME}</a3>
</properties>

然後在b2b/pom.xml所在目錄執行下面命令:

D:\code\IdeaProjects\b2b>mvn help:effective-pom -pl :b2b-account-service > 1.xml

上面這個命令會將mvn ...執行的結果輸出到b2b/1.xml目錄,mvn這段命令有看不懂的朋友,可以去看看前面的文章。

b2b目錄中產生了一個1.xml,如下:

我們打開1.xml看一下,部分內容如下:

<properties>
  <a>D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\src\main\java</a>
  <a1>${settings.localRepository}</a1>
  <a2>C:\Users\Think</a2>
  <a3>D:\installsoft\Java\jdk1.8.0_121</a3>
  <b>D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\src\test\java</b>
  <b2>D:\code\IdeaProjects\b2b</b2>
  <c>D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target</c>
  <d>D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\classes</d>
  <e>D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\test-classes</e>
  <f>com.javacode2018</f>
  <g>b2b-account-service</g>
  <h>1.0-SNAPSHOT</h>
  <i>b2b-account-service-1.0-SNAPSHOT</i>
</properties>

大家去和上面的pom.xml中的對比一下,感受一下!

多套環境構建問題

b2b-account-service會操作數據庫,所以我們需要一個配置文件來放數據庫的配置信息,配置文件一般都放在src/main/resources中,在這個目錄中新建一個jdbc.properties文件,內容如下:

jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root

現在系統需要打包,我們運行下面命令

mvn clean package -pl :b2b-account-service

問題來了:

上面jdbc的配置的是開發庫的db信息,打包之後生成的jar中也是上面開發環境的配置,那麼上測試環境是不是我們需要修改上面的配置,最終上線的時候,上面的配置是不是又得重新修改一次,相當痛苦的。

我們能不能寫3套環境的jdbc配置,打包的時候去指定具體使用那套配置?

還是你們聰明,maven支持這麼做,pom.xml的project元素下面提供了一個profiles元素可以用來對多套環境進行配置。

在講profiles的使用之前,需要先了解資源文件打包的過程。

理解資源文件打包過程

我們將src/main/resouces/jdbc.properties複製一份到src/test/resources目錄,2個文件如下:

我們先運行一下下面命令:

mvn clean package -pl :b2b-account-service

輸出如下:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[WARNING] Using platform encoding (GBK actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[WARNING] Using platform encoding (GBK actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.572 s
[INFO] Finished at: 2019-11-21T17:13:20+08:00
[INFO] ------------------------------------------------------------------------

再去看一下項目的target目錄,如下圖:

下面我們來對這個過程做詳細分析:

從輸出中可以看到有下面幾行:

[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[WARNING] Using platform encoding (GBK actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource

從上面輸出中可以看出,使用了插件maven-resources-plugin的resources目標,將src/main/resouces目錄中的資源文件複製到了target/classess目錄,複製了一個文件,複製過程中使用是GBK編碼。

還有幾行輸出如下:

[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[WARNING] Using platform encoding (GBK actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource

從上面輸出中可以看出,使用了插件maven-resources-plugin的testResources目標,將src/main/resouces目錄中的資源文件複製到了target/classess目錄,複製了一個文件,複製過程中使用是GBK編碼。

resources目錄中的文件一般放的都是配置文件,配置文件一般最好我們都不會寫死,所以此處有幾個問題:

  1. 這個插件複製資源文件如何設置編碼?
  2. 複製的過程中是否能夠對資源文件進行替換,比如在資源文件中使用一些佔位符,然後複製過程中對這些佔位符進行替換。

maven-resources-plugin這個插件還真好,他也想到了這個功能,幫我們提供了這樣的功能,下面我們來看看。

設置資源文件複製過程採用的編碼

這個之前有提到過,有好幾種方式,具體可以去前面的文章看一下。這裏只說一種:

<properties>
    <encoding>UTF-8</encoding>
</properties>

設置資源文件內容動態替換

資源文件中可以通過${maven屬性}來引用maven屬性中的值,打包的過程中這些會被替換掉,替換的過程默認是不開啟的,需要手動開啟配置。

修改src/main/resource/jdbc.properties內容如下:

jdbc.url=${jdbc.url}
jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}

修改src/test/resource/jdbc.properties內容如下:

jdbc.url=${jdbc.url}
jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}

b2b-account-service/pom.xml中加入下面內容:

<properties>
    <!-- 指定資源文件複製過程中採用的編碼方式 -->
    <encoding>UTF-8</encoding>
    <jdbc.url>jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8</jdbc.url>
    <jdbc.username>root</jdbc.username>
    <jdbc.password>root</jdbc.password>
</properties>

開啟動態替換配置,需要在pom.xml中加入下面配置:

<build>
    <resources>
        <resource>
            <!-- 指定資源文件的目錄 -->
            <directory>${project.basedir}/src/main/resources</directory>
            <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
            <filtering>true</filtering>
        </resource>
    </resources>
    <testResources>
        <testResource>
            <!-- 指定資源文件的目錄 -->
            <directory>${project.basedir}/src/test/resources</directory>
            <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
            <filtering>true</filtering>
        </testResource>
    </testResources>
</build>

注意上面開啟動態替換的元素是filtering

上面build元素中的resourcestestResources是用來控制構建過程中資源文件配置信息的,比資源文件位於哪個目錄,需要複製到那個目錄,是否開啟動態過濾等信息。

resources元素中可以包含多個resource,每個resource表示一個資源的配置信息,一般使用來控制主資源的複製的。

testResources元素和testResources類似,是用來控制測試資源複製的。

最終pom.xml內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-account-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
        <jdbc.url>jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8</jdbc.url>
        <jdbc.username>root</jdbc.username>
        <jdbc.password>root</jdbc.password>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.javacode2018</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>
    </build>

</project>

運行下面命令看效果:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.798 s
[INFO] Finished at: 2019-11-21T17:53:48+08:00
[INFO] ------------------------------------------------------------------------

4個文件截圖看一下效果,如下:

target中的2個資源文件內容被動態替換掉了。

上面會將資源文件中${}的內容使用maven屬性中的值進行替換,${}中包含的內容默認會被替換,那麼我們是否可以自定義${}這個格式,比如我希望被##包含內容進行替換,這個就涉及到替換中分隔符的指定了,需要設置插件的一些參數。

自定義替換的分隔符

自定義分隔符,需要我們配置maven-resources-plugin插件的參數,如下:

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>2.6</version>
        <configuration>
            <!-- 是否使用默認的分隔符,默認分隔符是${*}和@ ,這個地方設置為false,表示不啟用默認分隔符配置-->
            <useDefaultDelimiters>false</useDefaultDelimiters>
            <!-- 自定義分隔符 -->
            <delimiters>
                <delimiter>$*$</delimiter>
                <delimiter>##</delimiter>
            </delimiters>
        </configuration>
    </plugin>
</plugins>

delimiters中可以配置多個delimiter,可以配置#*#,其中的*表示屬性名稱,那麼資源文件中的#屬性名#在複製的過程中會被替換掉,*前後都是#,表示前後分隔符都一樣,那麼可以簡寫為#,所以#*##寫法是一樣的,我們去看一下源碼,delimiters的默認值如下:

this.delimiters.add("${*}");
this.delimiters.add("@");

案例

我們將pom.xml修改成下面這樣:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-account-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
        <jdbc.url>jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8</jdbc.url>
        <jdbc.username>root</jdbc.username>
        <jdbc.password>root</jdbc.password>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.javacode2018</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!-- 是否使用默認的分隔符,默認分隔符是${*}和@ ,這個地方設置為false,表示不啟用默認分隔符配置-->
                    <useDefaultDelimiters>false</useDefaultDelimiters>
                    <!-- 自定義分隔符 -->
                    <delimiters>
                        <delimiter>$*$</delimiter>
                        <delimiter>##</delimiter>
                    </delimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

將兩個jdbc.properties修改成下面這樣:

jdbc.url=${jdbc.url}
jdbc.username=$jdbc.username$
jdbc.password=##jdbc.password##

再運行一下下面的命令:

mvn clean package -pl :b2b-account-service

看一下target/classes中的jdbc.properties文件,變成了下面這樣:

jdbc.url=${jdbc.url}
jdbc.username=root
jdbc.password=root

可以看到後面兩符合分隔符的要求,被替換了,第一個沒有被替換。

指定需要替換的資源文件

src/main/resources中新增一個const.properties文件,內容如下:

email=javacode2018@163.com
jdbc.url=${jdbc.url}
jdbc.username=$jdbc.username$
jdbc.password=##jdbc.password##

執行下面命令:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.667 s
[INFO] Finished at: 2019-11-21T18:26:01+08:00
[INFO] ------------------------------------------------------------------------

看一下target/classes中,如下圖:

target/classes/const.properties內容變成下面這樣了:

email=javacode2018@163.com
jdbc.url=${jdbc.url}
jdbc.username=root
jdbc.password=root

說明const.properteis也被替換了。2個資源文件都被複制到了target中,如果我們不想讓cont.properties被複制到target/classes目錄,我們怎麼做?我們需要在資源構建的過程中排除他,可以使用exclude元素信息進行排除操作。

修改pom.xml中resources元素配置如下:

<resources>
    <resource>
        <!-- 指定資源文件的目錄 -->
        <directory>${project.basedir}/src/main/resources</directory>
        <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
        <filtering>true</filtering>
        <includes>
            <include>**/jdbc.properties</include>
        </includes>
        <excludes>
            <exclude>**/const.properties</exclude>
        </excludes>
    </resource>
</resources>

上面使用includes列出需要被處理的,使用excludes排除需要被處理的資源文件列表,採用通配符的寫法,**匹配任意深度的文件路徑,*匹配任意個字符。

再執行下面命令:

mvn clean package -pl :b2b-account-service

再看一下target/classes中,如下:

const.properties被排除了,確實沒有被複制過來。

如果此時我想讓const.propertis只是被複制到target下面,但是不要去替換裏面的內容,該怎麼做呢?此時需要配置多個resouce元素了,如下案例。

多個resource元素的使用案例

修改pom.xml中resources,如下:

<resources>
    <resource>
        <!-- 指定資源文件的目錄 -->
        <directory>${project.basedir}/src/main/resources</directory>
        <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
        <filtering>true</filtering>
        <includes>
            <include>**/jdbc.properties</include>
        </includes>
    </resource>
    <resource>
        <directory>${project.basedir}/src/main/resources</directory>
        <includes>
            <include>**/const.properties</include>
        </includes>
    </resource>
</resources>

現在pom.xml內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-account-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
        <jdbc.url>jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8</jdbc.url>
        <jdbc.username>root</jdbc.username>
        <jdbc.password>root</jdbc.password>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.javacode2018</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
                <includes>
                    <include>**/jdbc.properties</include>
                </includes>
            </resource>
            <resource>
                <directory>${project.basedir}/src/main/resources</directory>
                <includes>
                    <include>**/const.properties</include>
                </includes>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!-- 是否使用默認的分隔符,默認分隔符是${*}和@ ,這個地方設置為false,表示不啟用默認分隔符配置-->
                    <useDefaultDelimiters>false</useDefaultDelimiters>
                    <!-- 自定義分隔符 -->
                    <delimiters>
                        <delimiter>$*$</delimiter>
                        <delimiter>##</delimiter>
                    </delimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

執行命令看效果:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.116 s
[INFO] Finished at: 2019-11-21T18:38:59+08:00
[INFO] ------------------------------------------------------------------------

上面有如下輸出:

[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 1 resource

插件處理了2個資源文件。

我們看一下target/classes目錄下兩個文件的內容。

jdbc.proerties:

jdbc.url=${jdbc.url}
jdbc.username=root
jdbc.password=root

const.properties:

email=javacode2018@163.com
jdbc.url=${jdbc.url}
jdbc.username=$jdbc.username$
jdbc.password=##jdbc.password##

2個資源都被複制到target/classes目錄了,只有jdbc.properties中的內容被替換了。

關於資源文件處理的,更詳細的過程可以去看這個插件的源碼,下面我們來說多環境處理的問題。

使用profiles處理多環境構建問題

maven支持讓我們配置多套環境,每套環境中可以指定自己的maven屬性,mvn命令對模塊進行構建的時候可以通過-P參數來指定具體使用哪個環境的配置,具體向下看。

profiles元素支持定義多套環境的配置信息,配置如下用法:

<profiles>
    <profile>測試環境配置信息</profile>
    <profile>開發環境配置信息</profile>
    <profile>線上環境配置信息</profile>
    <profile>環境n配置信息</profile>
</profiles>

profiles中包含多個profile元素,每個profile可以表示一套環境,profile示例如下:

<profile>
    <id>dev</id>
    <properties>
        <jdbc.url>dev jdbc url</jdbc.url>
        <jdbc.username>dev jdbc username</jdbc.username>
        <jdbc.password>dev jdbc password</jdbc.password>
    </properties>
</profile>

id:表示這套環境的標識信息,properties可以定義環境中使用到的屬性列表。

執行mvn命令編譯的時候可以帶上一個-P profileid來使用指定的環境進行構建。

指定環境進行構建

b2b-account-service/pom.xml加入下面配置:

<profiles>
    <!-- 開發環境使用的配置 -->
    <profile>
        <id>dev</id>
        <properties>
            <jdbc.url>dev jdbc url</jdbc.url>
            <jdbc.username>dev jdbc username</jdbc.username>
            <jdbc.password>dev jdbc password</jdbc.password>
        </properties>
    </profile>
    <!-- 測試環境使用的配置 -->
    <profile>
        <id>test</id>
        <properties>
            <jdbc.url>test jdbc url</jdbc.url>
            <jdbc.username>test jdbc username</jdbc.username>
            <jdbc.password>test jdbc password</jdbc.password>
        </properties>
    </profile>
    <!-- 線上環境使用的配置 -->
    <profile>
        <id>prod</id>
        <properties>
            <jdbc.url>test jdbc url</jdbc.url>
            <jdbc.username>test jdbc username</jdbc.username>
            <jdbc.password>test jdbc password</jdbc.password>
        </properties>
    </profile>
</profiles>

將pom.xml的project->properties中的下面幾個元素幹掉,這些和profile中的重複了,需要幹掉:

<jdbc.url>jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8</jdbc.url>
<jdbc.username>root</jdbc.username>
<jdbc.password>root</jdbc.password>

此時pom.xml內容如下,建議大家直接貼進去:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-account-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
    </properties>

    <!-- 配置多套環境 -->
    <profiles>
        <!-- 開發環境使用的配置 -->
        <profile>
            <id>dev</id>
            <properties>
                <jdbc.url>dev jdbc url</jdbc.url>
                <jdbc.username>dev jdbc username</jdbc.username>
                <jdbc.password>dev jdbc password</jdbc.password>
            </properties>
        </profile>
        <!-- 測試環境使用的配置 -->
        <profile>
            <id>test</id>
            <properties>
                <jdbc.url>test jdbc url</jdbc.url>
                <jdbc.username>test jdbc username</jdbc.username>
                <jdbc.password>test jdbc password</jdbc.password>
            </properties>
        </profile>
        <!-- 線上環境使用的配置 -->
        <profile>
            <id>prod</id>
            <properties>
                <jdbc.url>prod jdbc url</jdbc.url>
                <jdbc.username>prod jdbc username</jdbc.username>
                <jdbc.password>prod jdbc password</jdbc.password>
            </properties>
        </profile>
    </profiles>

    <dependencies>
        <dependency>
            <groupId>com.javacode2018</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
                <includes>
                    <include>**/jdbc.properties</include>
                </includes>
            </resource>
            <resource>
                <directory>${project.basedir}/src/main/resources</directory>
                <includes>
                    <include>**/const.properties</include>
                </includes>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!-- 是否使用默認的分隔符,默認分隔符是${*}和@ ,這個地方設置為false,表示不啟用默認分隔符配置-->
                    <useDefaultDelimiters>false</useDefaultDelimiters>
                    <!-- 自定義分隔符 -->
                    <delimiters>
                        <delimiter>$*$</delimiter>
                        <delimiter>##</delimiter>
                    </delimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

修改src/main/resource/jdbc.properties內容如下:

jdbc.url=$jdbc.url$
jdbc.username=$jdbc.username$
jdbc.password=##jdbc.password##

運行下面的構建命令:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service -Pdev
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.361 s
[INFO] Finished at: 2019-11-21T18:51:16+08:00
[INFO] ------------------------------------------------------------------------

注意上面命令中多了一個-Pdev參數,-P後面跟的是pom.xml中profile的id,表示需要使用那套環境進行構建。此時我們使用的是dev環境,即開發環境。

看一下target/classes/jdbc.properties,內容變成了下面這樣:

jdbc.url=dev jdbc url
jdbc.username=dev jdbc username
jdbc.password=dev jdbc password

上面文件的內容和pom.xml中dev的profile中的properties對比一下,內容是不是一樣的,說明資源文件中內容被替換為了dev環境的配置,我們再來試試test環境,執行下面命令:

mvn clean package -pl :b2b-account-service -Ptest

看一下target/classes/jdbc.properties,內容變成了下面這樣:

jdbc.url=test jdbc url
jdbc.username=test jdbc username
jdbc.password=test jdbc password

看到了么,神奇的效果出現了,test環境起效了。

開啟默認環境配置

我們在執行下面命令:

mvn clean package -pl :b2b-account-service

看一下target/classes/jdbc.properties,內容變成了下面這樣:

jdbc.url=$jdbc.url$
jdbc.username=$jdbc.username$
jdbc.password=##jdbc.password##

內容沒有被替換,因為我們沒有通過-P來指定具體使用哪個環境進行構建,所以出現了這種現象。

但是我們可以指定一個默認開啟的配置,我們默認開啟dev的配置,修改dev的profile元素,在這個元素下面加上:

<activation>
    <activeByDefault>true</activeByDefault>
</activation>

activeByDefault表示默認開啟這個環境的配置,默認值是false,這個地方我們設置為true,表示開啟默認配置。

此時dev的配置如下:

<profile>
    <id>dev</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <properties>
        <jdbc.url>dev jdbc url</jdbc.url>
        <jdbc.username>dev jdbc username</jdbc.username>
        <jdbc.password>dev jdbc password</jdbc.password>
    </properties>
</profile>

執行下面命令:

mvn clean package -pl :b2b-account-service

看一下target/classes/jdbc.properties,內容變成了下面這樣:

jdbc.url=dev jdbc url
jdbc.username=dev jdbc username
jdbc.password=dev jdbc password

這次我們沒有指定環境,默認使用了dev環境。

通過maven屬性來控制環境的開啟

剛才上面說了通過-P profileId的方式來指定環境,現在我們想通過自定義的屬性值來控制使用哪個環境。

可以在profile元素中加入下面配置

<activation>
    <property>
        <name>屬性xx</name>
        <value>屬性xx的值</value>
    </property>
</activation>

那麼我們可以在mvn後面跟上下面的命令可以開啟匹配的環境:

mvn ... -D屬性xx=屬性xx的值

-D可以通過命令行指定一些屬性的值,這個前面有講過,-D後面的屬性會和activation->properties中的name、value進行匹配,匹配成功的環境都會被開啟。

將pom.xml中profiles元素修改成下面這樣:

<!-- 配置多套環境 -->
<profiles>
    <!-- 開發環境使用的配置 -->
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
            <property>
                <name>env</name>
                <value>env_dev</value>
            </property>
        </activation>
        <properties>
            <jdbc.url>dev jdbc url</jdbc.url>
            <jdbc.username>dev jdbc username</jdbc.username>
            <jdbc.password>dev jdbc password</jdbc.password>
        </properties>
    </profile>
    <!-- 測試環境使用的配置 -->
    <profile>
        <id>test</id>
        <activation>
            <property>
                <name>env</name>
                <value>env_test</value>
            </property>
        </activation>
        <properties>
            <jdbc.url>test jdbc url</jdbc.url>
            <jdbc.username>test jdbc username</jdbc.username>
            <jdbc.password>test jdbc password</jdbc.password>
        </properties>
    </profile>
    <!-- 線上環境使用的配置 -->
    <profile>
        <id>prod</id>
        <activation>
            <property>
                <name>env</name>
                <value>env_prod</value>
            </property>
        </activation>
        <properties>
            <jdbc.url>prod jdbc url</jdbc.url>
            <jdbc.username>prod jdbc username</jdbc.username>
            <jdbc.password>prod jdbc password</jdbc.password>
        </properties>
    </profile>
</profiles>

運行命令:

mvn clean package -pl :b2b-account-service -Denv=env_prod

target/classes/jdbc.properties內容如下:

jdbc.url=prod jdbc url
jdbc.username=prod jdbc username
jdbc.password=prod jdbc password

說明使用到了prod環境的配置。

啟動的時候指定多個環境

可以在-P參數後跟多個環境的id,多個之間用逗號隔開,當使用多套環境的時候,多套環境中的maven屬性會進行合併,如果多套環境中屬性有一樣的,後面的會覆蓋前面的。

運行下面命令看效果:

mvn clean package -pl :b2b-account-service -Pdev,test,prod

修改pom.xml中的profiles元素,如下:

<!-- 配置多套環境 -->
<profiles>
    <!-- 開發環境使用的配置 -->
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
            <property>
                <name>env</name>
                <value>env_dev</value>
            </property>
        </activation>
        <properties>
            <jdbc.url>dev jdbc url</jdbc.url>
        </properties>
    </profile>
    <!-- 測試環境使用的配置 -->
    <profile>
        <id>test</id>
        <activation>
            <property>
                <name>env</name>
                <value>env_test</value>
            </property>
        </activation>
        <properties>
            <jdbc.username>test jdbc username</jdbc.username>
        </properties>
    </profile>
    <!-- 線上環境使用的配置 -->
    <profile>
        <id>prod</id>
        <activation>
            <property>
                <name>env</name>
                <value>env_prod</value>
            </property>
        </activation>
        <properties>
            <jdbc.password>prod jdbc password</jdbc.password>
        </properties>
    </profile>
</profiles>

注意看一下上面3個環境中都只有一個自定義屬性了

下面我們同時使用3個環境,執行下面命令:

mvn clean package -pl :b2b-account-service -Pdev,test,prod

target中的jdbc.properties文件變成了這樣:

jdbc.url=dev jdbc url
jdbc.username=test jdbc username
jdbc.password=prod jdbc password

查看目前有哪些環境

命令:

mvn help:all-profiles

案例:

D:\code\IdeaProjects\b2b>mvn help:all-profiles -pl :b2b-account-service
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-help-plugin:3.2.0:all-profiles (default-cli) @ b2b-account-service ---
[INFO] Listing Profiles for Project: com.javacode2018:b2b-account-service:jar:1.0-SNAPSHOT
  Profile Id: dev (Active: true , Source: pom)
  Profile Id: prod (Active: false , Source: pom)
  Profile Id: test (Active: false , Source: pom)

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.527 s
[INFO] Finished at: 2019-11-21T19:16:34+08:00
[INFO] ------------------------------------------------------------------------

從輸出中可以看到有3個環境,他們的id,默認激活的是那個等信息。

查看目前激活的是哪些環境

命令:

mvn help:active-profiles

案例:

D:\code\IdeaProjects\b2b>mvn help:active-profiles -pl :b2b-account-service -Ptest,prod
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-help-plugin:3.2.0:active-profiles (default-cli) @ b2b-account-service ---
[INFO]
Active Profiles for Project 'com.javacode2018:b2b-account-service:jar:1.0-SNAPSHOT':

The following profiles are active:

 - test (source: com.javacode2018:b2b-account-service:1.0-SNAPSHOT)
 - prod (source: com.javacode2018:b2b-account-service:1.0-SNAPSHOT)



[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.387 s
[INFO] Finished at: 2019-11-21T19:25:56+08:00
[INFO] ------------------------------------------------------------------------

從輸出中可以看到啟用了2套環境,分別是testprod

新問題:配置太分散了

我們的系統中有2個模塊需要用到數據庫的配置,這兩個模塊是:

b2b-account-service
b2b-order-service

上面我們介紹了b2b-account-service不同環境的構建操作,是在pom.xml中進行配置的,b2b-order-service中數據的配置也可以這麼做,如果以後有更多的模塊都需要連接不同的數據庫,是不是每個模塊中都需要配置這樣的pom.xml,那時候數據庫的配置分散在幾十個pom.xml文件中,如果運維需要修改數據庫的配置的時候,需要去每個模塊中去修改pom.xml中的屬性,這種操作會讓人瘋掉的,我們可以怎麼做?

我們可以將數據庫所有的配置放在一個文件中,運維只用修改這一個文件就行了,然後執行構件操作,重新發布上線,就ok了。

maven支持我們這麼做,可以在profile中指定一個外部屬性文件xx.properties,文件內容是這種格式的:

key1=value1
key2=value2
keyn=value2

然後在profile元素中加入下面配置:

<build>
    <filters>
        <filter>xx.properties文件路徑(相對路徑或者完整路徑)</filter>
    </filters>
</build>

上面的filter元素可以指定多個,當有多個外部屬性配置文件的時候,可以指定多個來進行引用。

然後資源文件複製的時候就可以使用下面的方式引用外部資源文件的內容:

xxx=${key1}

案例

新建文件b2b/config/dev.properties,內容如下:

# b2b-account-service jdbc配置信息
b2b-account-service.jdbc.url=dev_account_jdbc_url
b2b-account-service.jdbc.username=dev_account_jdbc_username
b2b-account-service.jdbc.password=dev_account_jdbc_password

# b2b-order-service jdbc配置信息
b2b-order-service.jdbc.url=dev_order_jdbc_url
b2b-order-service.jdbc.username=dev_order_jdbc_username
b2b-order-service.jdbc.password=dev_order_jdbc_password

新建文件b2b/config/test.properties,內容如下:

# b2b-account-service jdbc配置信息
b2b-account-service.jdbc.url=test_account_jdbc_url
b2b-account-service.jdbc.username=test_account_jdbc_username
b2b-account-service.jdbc.password=test_account_jdbc_password

# b2b-order-service jdbc配置信息
b2b-order-service.jdbc.url=test_order_jdbc_url
b2b-order-service.jdbc.username=test_order_jdbc_username
b2b-order-service.jdbc.password=test_order_jdbc_password

新建文件b2b/config/prod.properties,內容如下:

# b2b-account-service jdbc配置信息
b2b-account-service.jdbc.url=prod_account_jdbc_url
b2b-account-service.jdbc.username=prod_account_jdbc_username
b2b-account-service.jdbc.password=prod_account_jdbc_password

# b2b-order-service jdbc配置信息
b2b-order-service.jdbc.url=prod_order_jdbc_url
b2b-order-service.jdbc.username=prod_order_jdbc_username
b2b-order-service.jdbc.password=prod_order_jdbc_password

b2b-account-service/src/main/resource/jdbc.properties文件內容:

jdbc.url=${b2b-account-service.jdbc.url}
jdbc.username=${b2b-account-service.jdbc.username}
jdbc.password=${b2b-account-service.jdbc.password}

看到了沒,這裏面${}中的屬性名稱取的是上面b2b/config目錄中資源文件中的屬性名稱。

b2b-order-service/src/main/resource/jdbc.properties文件內容:

jdbc.url=${b2b-order-service.jdbc.url}
jdbc.username=${b2b-order-service.jdbc.username}
jdbc.password=${b2b-order-service.jdbc.password}

b2b-account-service/pom.xml內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-account-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.javacode2018</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
    </properties>

    <!-- 配置多套環境 -->
    <profiles>
        <!-- 開發環境使用的配置 -->
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
                <property>
                    <name>env</name>
                    <value>env_dev</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/dev.properties</filter>
                </filters>
            </build>
        </profile>
        <!-- 測試環境使用的配置 -->
        <profile>
            <id>test</id>
            <activation>
                <property>
                    <name>env</name>
                    <value>env_test</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/test.properties</filter>
                </filters>
            </build>
        </profile>
        <!-- 線上環境使用的配置 -->
        <profile>
            <id>prod</id>
            <activation>
                <property>
                    <name>env</name>
                    <value>env_prod</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/prod.properties</filter>
                </filters>
            </build>
        </profile>
    </profiles>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>
    </build>

</project>

b2b-order-service/pom.xml內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-order-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>b2b-order-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
    </properties>

    <!-- 配置多套環境 -->
    <profiles>
        <!-- 開發環境使用的配置 -->
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
                <property>
                    <name>env</name>
                    <value>env_dev</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/dev.properties</filter>
                </filters>
            </build>
        </profile>
        <!-- 測試環境使用的配置 -->
        <profile>
            <id>test</id>
            <activation>
                <property>
                    <name>env</name>
                    <value>env_test</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/test.properties</filter>
                </filters>
            </build>
        </profile>
        <!-- 線上環境使用的配置 -->
        <profile>
            <id>prod</id>
            <activation>
                <property>
                    <name>env</name>
                    <value>env_prod</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/prod.properties</filter>
                </filters>
            </build>
        </profile>
    </profiles>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>
    </build>

</project>

見證奇迹的時刻來了。

執行下面命令:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service,:b2b-order-service -Pdev
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] b2b-account-service                                                [jar]
[INFO] b2b-order-service                                                  [jar]
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT                          [1/2]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO]
[INFO] -----------------< com.javacode2018:b2b-order-service >-----------------
[INFO] Building b2b-order-service 1.0-SNAPSHOT                            [2/2]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-order-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-order\b2b-order-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-order-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-order-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-order-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory D:\code\IdeaProjects\b2b\b2b-order\b2b-order-service\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-order-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-order-service ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-order-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-order\b2b-order-service\target\b2b-order-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for b2b-account-service 1.0-SNAPSHOT:
[INFO]
[INFO] b2b-account-service ................................ SUCCESS [  2.510 s]
[INFO] b2b-order-service .................................. SUCCESS [  0.257 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.026 s
[INFO] Finished at: 2019-11-21T20:11:32+08:00
[INFO] ------------------------------------------------------------------------

看一下兩個模塊target/classes下面jdbc.properties內容,如下圖:

使用了dev環境的配置,dev環境中處理資源文件替換的時候採用了config/dev.properties文件的內容,這部分配置如下,重點在於build->filters元素

<profile>
    <id>dev</id>
    <activation>
        <activeByDefault>true</activeByDefault>
        <property>
            <name>env</name>
            <value>env_dev</value>
        </property>
    </activation>
    <build>
        <filters>
            <filter>../../config/dev.properties</filter>
        </filters>
    </build>
</profile>

我們再使用prod環境看一下效果,執行下面命令:

mvn clean package -pl :b2b-account-service,:b2b-order-service -Pprod

效果圖如下:

感受一下,這次使用了b2b/config/prod.properties文件的內容對資源文件進行了替換,這次將所有配置的信息集中在config目錄的幾個文件中進行處理了,對於運維和開發來說都非常方便了。

profile元素更強大的功能

profile元素可以用於對不同環境的構建進行配置,project中包含的元素,在profile元素中基本上都有,所以profile可以定製更複雜的構建過程,不同的環境依賴的構件、插件、build過程、測試過程都是不一樣的,這些都可以在profile中進行指定,也就是說不同的環境所有的東西都可以通過profile元素來進行個性化的設置,這部分的功能有興趣的大家可以自己去研究一下,截個圖大家感受一下:

從上圖中可以看出這些屬性都可以根據環境進行定製的。

還是那句話,上面這些用法大家會經常用到的,建議大家下去了多練練。看和操作,所獲取到的是不能比的,看的過程中可能覺得一切都知道了,但是實際操作就不一樣了,可能中間會遇到各種問題,然後自己會想辦法解決這些問題,領會和學到的東西是不一樣的!

Maven系列目錄

更多好文章

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!