一起玩轉微服務(2)——框架與工具

一、微服務架構有哪些優勢?

  • 獨立開發 – 所有微服務都可以根據各自的功能輕鬆開發·
  • 獨立部署 – 基於其服務,可以在任何應用程序中單獨部署它們·
  • 故障隔離 – 即使應用程序的一項服務不起作用,系統仍可繼續運行· 混合技術堆棧 – 可以使用不同的語言和技術來構建同一應用程序的不同服務·
  • 粒度縮放 – 單個組件可根據需要進行縮放,無需將所有組件縮放在一起

二、微服務有哪些特點?

  • 解耦 – 系統內的服務很大程度上是分離的。因此,整個應用程序可以輕鬆構建,更改和擴展·
  • 組件化 – 微服務被視為可以輕鬆更換和升級的獨立組件·
  • 業務能力 – 微服務非常簡單,專註於單一功能·
  • 自治 – 開發人員和團隊可以彼此獨立工作,從而提高速度·
  • 持續交付 – 通過軟件創建,測試和批準的系統自動化,允許頻繁發布軟件·
  • 責任 – 微服務不關注應用程序作為項目。相反,他們將應用程序視為他們負責的產品·
  • 分散治理 – 重點是使用正確的工具來做正確的工作。這意味着沒有標準化模式或任何技術模式。開發人員可以自由選擇最有用的工具來解決他們的問題·
  • 敏捷 – 微服務支持敏捷開發。任何新功能都可以快速開發並再次丟棄

三、微服務架構如何運作?

微服務架構具有以下組件:

 

 

 

  • 客戶端 – 來自不同設備的不同用戶發送請求。·
  • 身份提供商 – 驗證用戶或客戶身份並頒發安全令牌。·
  • API 網關 – 處理客戶端請求。·
  • 靜態內容 – 容納系統的所有內容。·
  • 管理 – 在節點上平衡服務並識別故障。·
  • 服務發現 – 查找微服務之間通信路徑的指南。·
  • 內容交付網絡 – 代理服務器及其數據中心的分佈式網絡。·
  • 遠程服務 – 啟用駐留在 IT 設備網絡上的遠程訪問信息。

四、什麼是 REST / RESTful 以及它的用途是什麼?

Representational State Transfer(REST)/ RESTful Web 服務是一種幫助計算機系統通過 Internet 進行通信的架構風格。這使得微服務更容易理解和實現。微服務可以使用或不使用 RESTful API 實現,但使用 RESTful API 構建鬆散耦合的微服務總是更容易。

五、什麼是不同類型的微服務測試?

在使用微服務時,由於有多個微服務協同工作,測試變得非常複雜。因此,測試分為不同的級別。·

  • 在底層,我們有面向技術的測試,如單元測試和性能測試。這些是完全自動化的。·
  • 在中間層面,我們進行了諸如壓力測試和可用性測試之類的探索性測試。·
  • 在頂層, 我們的 驗收測試數量很少。這些驗收測試有助於利益相關者理解和驗證軟件功能。

六、常用的微服務框架

各種語言都有自己的微服務框架,主要包括:

  • 主流微服務框架:SpringCloud、Dubbo、spring cloud alibaba
  • 新銳微服務框架:Istio

下面一起看一下常用的微服務框架體系。

1. Spring 頂級項目

首先,我們一起來看一下Spring的頂級項目都包括哪些:

  • Spring IO platform:用於系統部署,是可集成的,構建現代化應用的版本平台,具體來說當你使用maven dependency引入Spring jar包時它就在工作了。
  • Spring Boot:旨在簡化創建產品級的 Spring 應用和服務,簡化了配置文件,使用嵌入式web服務器,含有諸多開箱即用微服務功能,可以和Spring Cloud聯合部署。
  • Spring Framework:即通常所說的Spring 框架,是一個開源的Java/Java EE全功能棧應用程序框架,其它Spring項目如Spring Boot也依賴於此框架。
  • Spring Cloud:微服務工具包,為開發者提供了在分佈式系統的配置管理、服務發現、斷路器、智能路由、微代理、控制總線等開發工具包。
  • Spring XD:是一種運行時環境(服務器軟件,非開發框架),組合Spring技術,如Spring batch、Spring Boot、Spring data,採集大數據並處理。
  • Spring Data:是一個數據訪問及操作的工具包,封裝了很多種數據及數據庫的訪問相關技術,包括:jdbc、Redis、MongoDB、Neo4j等。
  • Spring Batch:批處理框架,或說是批量任務執行管理器,功能包括任務調度、日誌記錄/跟蹤等。
  • Spring Security:是一個能夠為基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。
  • Spring Integration:面向企業應用集成(EAI/ESB)的編程框架,支持的通信方式包括HTTP、FTP、TCP/UDP、JMS、RabbitMQ、Email等。
  • Spring Social:一組工具包,一組連接社交服務API,如Twitter、Facebook、LinkedIn、GitHub等,有幾十個。
  • Spring AMQP:消息隊列操作的工具包,主要是封裝了RabbitMQ的操作。
  • Spring HATEOAS:是一個用於支持實現超文本驅動的 REST Web 服務的開發庫。
  • Spring Mobile:是Spring MVC的擴展,用來簡化手機上的Web應用開發。
  • Spring for Android:是Spring框架的一個擴展,其主要目的在乎簡化Android本地應用的開發,提供RestTemplate來訪問Rest服務。
  • Spring Web Flow:目標是成為管理Web應用頁面流程的最佳方案,將頁面跳轉流程單獨管理,並可配置。
  • Spring LDAP:是一個用於操作LDAP的Java工具包,基於Spring的JdbcTemplate模式,簡化LDAP訪問。
  • Spring Session:session管理的開發工具包,讓你可以把session保存到redis等,進行集群化session管理。
  • Spring Web Services:是基於Spring的Web服務框架,提供SOAP服務開發,允許通過多種方式創建Web服務。
  • Spring Shell:提供交互式的Shell可讓你使用簡單的基於Spring的編程模型來開發命令,比如Spring Roo命令。
  • Spring Roo:是一種Spring開發的輔助工具,使用命令行操作來生成自動化項目,操作非常類似於Rails。
  • Spring Scala:為Scala語言編程提供的Spring框架的封裝(新的編程語言,Java平台的Scala於2003年底/2004年初發布)。
  • Spring BlazeDS Integration:一個開發RIA工具包,可以集成Adobe Flex、BlazeDS、Spring以及Java技術創建RIA。
  • Spring Loaded:用於實現java程序和web應用的熱部署的開源工具。
  • Spring REST Shell:可以調用Rest服務的命令行工具,敲命令行操作Rest服務。

2. 背景

對於使用java技術更多的團隊來說,dubbo,Spring Cloud的知名度相對較高,而且在業界的影響力非常高。那麼我們就來一起討論一下這兩種框架,做一個綜合的評比。
Dubbo
Dubbo是阿里出品的服務化組件,應用於多個部門,後來開源之後,也衍生出像dubbox這樣的框架,對其進行了進一步的增強。Dubbo 是一個分佈式服務框架,是國內互聯網公司開源做的比較不錯的阿里開放的微服務化治理框架,致力於提供高性能和透明化的RPC遠程服務調用方案,以及SOA服務治理方案。目前Dubbo已經正式進入Apache孵化器。

現在可以再apache 下面找到dubbo

http://dubbo.apache.org/zh-cn/

 

其核心部分包含:

  • 遠程通訊: 提供對多種基於長連接的NIO框架抽象封裝,包括多種線程模型,序列化,以及“請求-響應”模式的信息交換方式;
  • 集群容錯: 提供基於接口方法的透明遠程過程調用,包括多協議支持,以及軟負載均衡,失敗容錯,地址路由,動態配置等集群支持;
  • 自動發現: 基於註冊中心目錄服務,使服務消費方能動態的查找服務提供方,使地址透明,使服務提供方可以平滑增加或減少機器。

 

Spring Cloud
Spring Cloud從其命名中就可以知道,是Spring Source的產物,可以說Spring的出現整個改變了企業級開發,Spring Cloud,除了具有 Spring 社區的強大背景外,還有 Netflix 強大的後盾與技術輸出。Netflix 作為一家成功實踐微服務架構的互聯網公司,在幾年前就把幾乎整個微服務框架棧開源貢獻給了社區,這些框架開源的整套微服務架構套件是 Spring Cloud 的核心。

  • Eureka: 服務註冊發現框架;
  • Zuul: 服務網關;
  • Karyon: 服務端框架;
  • Ribbon: 客戶端框架;
  • Hystrix: 服務容錯組件;
  • Archaius: 服務配置組件;
  • Servo: Metrics組件;
  • Blitz4j: 日誌組件。

從背景上來看,前者在國內的影響力比較大,後者在國外的影響力比較大,使用的公司也更多一些。

spring-cloud-alibaba

地址:

https://spring.io/projects/spring-cloud-alibaba

服務限流降級:默認支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降級功能的接入,可以在運行時通過控制台實時修改限流降級規則,還支持查看限流降級 Metrics 監控。
服務註冊與發現:適配 Spring Cloud 服務註冊與發現標準,默認集成了 Ribbon 的支持。
分佈式配置管理:支持分佈式系統中的外部化配置,配置更改時自動刷新。
消息驅動能力:基於 Spring Cloud Stream 為微服務應用構建消息驅動能力。
分佈式事務:使用 @GlobalTransactional 註解, 高效並且對業務零侵入地解決分佈式事務問題。。
阿里雲對象存儲:阿里雲提供的海量、安全、低成本、高可靠的雲存儲服務。支持在任何應用、任何時間、任何地點存儲和訪問任意類型的數據。
分佈式任務調度:提供秒級、精準、高可靠、高可用的定時(基於 Cron 表達式)任務調度服務。同時提供分佈式的任務執行模型,如網格任務。網格任務支持海量子任務均勻分配到所有 Worker(schedulerx-client)上執行。
阿里雲短信服務:覆蓋全球的短信服務,友好、高效、智能的互聯化通訊能力,幫助企業迅速搭建客戶觸達通道。

Istio
Istio 作為用於微服務服務聚合層管理的新銳項目,是 Google、IBM、Lyft(海外共享出行公司、Uber勁敵) 首個共同聯合開源的項S目,提供了統一的連接,安全,管理和監控微服務的方案。
目前首個測試版是針對 Kubernetes 環境的,社區宣稱在未來幾個月內會為虛擬機和 Cloud Foundry 等其他環境增加支持。 Istio 將流量管理添加到微服務中,併為增值功能(如安全性,監控,路由,連接管理和策略)創造了基礎。

  • HTTP、gRPC 和 TCP 網絡流量的自動負載均衡;
  • 提供了豐富的路由規則,實現細粒度的網絡流量行為控制;
  • 流量加密、服務間認證,以及強身份聲明;
  • 全範圍(Fleet-wide)的策略執行;
  • 深度遙測和報告。

3. 社區活躍度

我們選擇一個開源框架,社區的活躍度是我們極為關注的一個要點。社區越活躍,解決問題的速度越快,框架也會越來越完善,不然當我們碰到問題,就不得不自己解決。
先看一下dubbo和dubbox,
下圖是dubbo目前的情況,在2017年,阿里已經宣布有團隊重新維護dubbo項目,可以看到dubbo項目已經重新開始修復裡邊的一些bug,並且進入不斷的完善的過程中了。Dubbo 目前在 GitHub 上有超過 16000 個 star 和超過 12000 的 fork 數,是國內影響力最大的開源項目之一。

 

 

反倒是繼承而來的dubbox,已經有很長時間沒有進行維護了。

 

 

再看Spring Cloud,沿襲着Spring一慣的風格,更新非常及時,響應的速度也非常快,社區非常活躍。

4. 架構完整度

根據微服務架構在各方面的要素,看看Spring Cloud和Dubbo都提供了哪些支持。表N展示了Dubbo和Spring Cloud的區別。

 

其實單純從提供的數量上來比較,有一些不公平,因為dubbo是幾年前出來的,極大的改變了人們對於分佈式系統的認知。而上邊寫無的部分其實也可以整合其它的框架,所以,對於這一點的評比上來說,選用何種框架取決於團隊目前的情況,而不是一下子就全部轉型,要階梯式的實現整個應用架構。

5. 總結

使用Dubbo構建的微服務架構就像組裝電腦,各環節我們的選擇自由度很高,但是最終結果很有可能因為一條內存質量不行就點不亮了,總是讓人不怎麼放心,但是如果你是一名高手,那這些都不是問題;而Spring Cloud就像品牌機,在Spring Source的整合下,做了大量的兼容性測試,保證了機器擁有更高的穩定性,但是如果要在使用非原裝組件外的東西,就需要對其基礎有足夠的了解。

最後呢,說一下工具,個人偏好是eclipse派!
https://spring.io/tools

 

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

JS遍歷對象的幾種方法

幾天前一個小夥伴問我 Object.getOwnPropertyNames() 是干什麼用的

平時還真沒有使用到這個方法,一時不知如何回答

從方法名稱來分析,應該是返回的是對象自身屬性名組成的數組

那和 Object.keys() 方法不就一樣了嗎

感覺事情並不這麼簡單,於是我仔細看了一下這幾種遍歷對象的方法的區別

for in

for in 循環是最基礎的遍歷對象的方式,它還會得到對象原型鏈上的屬性

// 創建一個對象並指定其原型,bar 為原型上的屬性
const obj = Object.create({
  bar: 'bar'
})

// foo 為對象自身的屬性
obj.foo = 'foo'

for (let key in obj) {
  console.log(obj[key]) // foo, bar
}

可以看到對象原型上的屬性也被循環出來了

在這種情況下可以使用對象的 hasOwnProperty() 方法過濾掉原型鏈上的屬性

for (let key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(obj[key]) // foo
  }
}

這時候原型上的 bar 屬性就被過濾掉了

Object.keys

Object.keys() 是 ES5 新增的一個對象方法,該方法返回對象自身屬性名組成的數組,它會自動過濾掉原型鏈上的屬性,然後可以通過數組的 forEach() 方法來遍歷

Object.keys(obj).forEach((key) => {
  console.log(obj[key]) // foo
})

另外還有 Object.values() 方法和 Object.entries() 方法,這兩方法的作用範圍和 Object.keys() 方法類似,因此不再說明

for in 循環和 Object.keys() 方法都不會返回對象的不可枚舉屬性

如果需要遍歷不可枚舉的屬性,就要用到前面提到的 Object.getOwnPropertyNames() 方法了

Object.getOwnPropertyNames

Object.getOwnPropertyNames() 也是 ES5 新增的一個對象方法,該方法返回對象自身屬性名組成的數組,包括不可枚舉的屬性,也可以通過數組的 forEach 方法來遍歷

// 創建一個對象並指定其原型,bar 為原型上的屬性
// baz 為對象自身的屬性並且不可枚舉
const obj = Object.create({
  bar: 'bar'
}, {
  baz: {
    value: 'baz',
    enumerable: false
  }
})

obj.foo = 'foo'

// 不包括不可枚舉的 baz 屬性
Object.keys(obj).forEach((key) => {
  console.log(obj[key]) // foo
})

// 包括不可枚舉的 baz 屬性
Object.getOwnPropertyNames(obj).forEach((key) => {
  console.log(obj[key]) // baz, foo
})

ES2015 新增了 Symbol 數據類型,該類型可以作為對象的鍵,針對該類型 ES2015 同樣新增了 Object.getOwnPropertySymbols() 方法

Object.getOwnPropertySymbols

Object.getOwnPropertySymbols() 方法返回對象自身的 Symbol 屬性組成的數組,不包括字符串屬性

Object.getOwnPropertySymbols(obj).forEach((key) => {
  console.log(obj[key])
})

什麼都沒有,因為該對象還沒有 Symbol 屬性

// 給對象添加一個不可枚舉的 Symbol 屬性
Object.defineProperties(obj, {
  [Symbol('baz')]: {
    value: 'Symbol baz',
    enumerable: false
  }
})

// 給對象添加一個可枚舉的 Symbol 屬性
obj[Symbol('foo')] = 'Symbol foo'

Object.getOwnPropertySymbols(obj).forEach((key) => {
  console.log(obj[key]) // Symbol baz, Symbol foo
})

Reflect.ownKeys

Reflect.ownKeys() 方法是 ES2015 新增的靜態方法,該方法返回對象自身所有屬性名組成的數組,包括不可枚舉的屬性和 Symbol 屬性

Reflect.ownKeys(obj).forEach((key) => {
  console.log(obj[key]) // baz, foo, Symbol baz, Symbol foo
})

對比

方式 基本屬性 原型鏈 不可枚舉 Symbol
for in
Object.keys()
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Reflect.ownKeys()

結論

這其中只有 for in 循環會得到對象原型鏈上的屬性,其它方法都只適用於對象自身的屬性

ES 語言後續添加的新特性不會對以前的代碼產生副作用,比如在 ES2015 之前就存在的 for in 循環,Object.keys() 和 Object.getOwnPropertyNames() 是肯定不會返回 Symbol 屬性的

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

斯里蘭卡逾百頭鯨魚擱淺 漁民徹夜忙搶救

摘錄自2020年11月3日公視報導

斯里蘭卡首都可倫坡附近,發生有120頭鯨魚擱淺事件,當地民眾無法顧及防疫宵禁措施,徹夜協助要把鯨魚推回大海,但其中還是有兩頭鯨魚因為擱淺、受傷死亡。

警方說,當時不斷有鯨魚沖上岸,民眾根本搶救不及,後來海岸警衛隊及海軍部隊也加入搶救行列,利用小型巡邏艇將鯨魚送回深水區。斯里蘭卡海洋環境保護署則表示,這是斯里蘭卡首度有這麼多的鯨魚擱淺,情況很不尋常,懷疑可能跟澳洲日前發生大規模鯨魚擱淺事件有相似之處。

澳洲的塔斯馬尼亞在九月間,發生470頭領航鯨集體擱淺的意外。當時救援隊伍歷經五天時間,成功將其中110頭送回大海,其餘鯨魚則不幸死亡。為了避免造成二度海洋生態浩劫,當時清運鯨魚屍體也成了棘手問題。至於鯨魚為何一再被沖上岸始終成謎,科學家研判,領航鯨是高度社會化動物,擱淺原因可能跟牠們喜歡群聚生活、彼此互動密切,跟隨著一兩隻偏離航道的領航鯨而集體擱淺。

海洋
國際新聞
斯里蘭卡
鯨豚擱淺

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

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

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

東芝挑戰綠能新趨勢 本月內建立亞洲首座虛擬電廠

摘錄自2020年11月5日中國時報報導

日本東芝集團旗下能源系統公司宣布,本月內將與德國公司次世代電廠(Next Kraftwerke)成立合資新公司東芝次世代電廠(東芝 Next Kraftwerke),建立亞洲首座虛擬電廠(virtual power plant,縮寫VPP),透過通信技術和物聯網技術,雲端中央控制全國數個太陽能發電設備,使其像傳統電廠一樣運作。

VPP是在IT技術及物聯網技術更加成熟後,最近10年才興起的分散式電廠概念,有別於傳統集中式電廠,聚集不同類型技術電力,透過電網來調度電力,因此能解決風力、太陽能等再生能源發電量與運動時間不穩定問題,例如在發電後將多餘電量送往其他需要地點或者加以儲存備用,也可以客製化不斷變換負載狀況,更加靈活而有效率,同時也能節省能源。

東芝次世代電廠初步資本額是1.8億日圓(約4991萬台幣),公司就設在東芝能源系統公司本部、神奈川縣川崎市,東芝持股51%,次世代電廠持股49%。東芝已有準確預測發電量的技術,內部人士指出透過中央統一依據需求量來控制各發電設備發電量和調度,可將電力價格波動控制到較低程度。

能源轉型
國際新聞
日本
綠能

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

最大冰山直逼南喬治亞恐毀生態

摘錄自2020年11月5日蘋果日報報導

全球最大的冰山A68a正沖向英國南大西洋海外領地南喬治亞(South Georgia),可能破壞大片野生動物棲息地,令大量企鵝和海豹死亡。

A68a面積約4,00平方公里,估計重數千億噸,外形像一隻伸出的食指。它2017年中分離南極州,沿着「冰山巷」漂浮,現已到達南喬治亞西南數百公里。

由於A68a在水下的深度估計只有約200米,停下來前有可能沖上南喬治亞海岸,堵塞企鵝和海豹的覓食通道,嚴重破壞生態。英國南極調查局(BAS)學者塔林(Geraint Tarling)稱冰山或停留在當地十年,令企鵝和海豹要花更長時間到原本覓食地區,未必能趕及回到原地餵飼幼兒,導致牠們餓死。

不過,BAS遙距感測和地圖技術員弗雷特韋爾(Peter Fretwell)洋流仍有機會令A68a改為向西北漂浮,然後在較溫的水域解體,避免這場災難,當局正密切了解冰山在最壞情況下會對生態和漁業造成多大打擊。

生物多樣性
氣候變遷
國際新聞
南極
冰山

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

drf之框架基礎

(一)drf基礎

全稱:django-rest framework

接口:什麼是接口、restful接口規範(協議)

CBV(基於FBV的基礎上形成)、CBV生命周期源碼—-基於restful規範下的CBV接口

請求生命周期:請求組件、解析組件、響應組件

序列化組件(序列化、反序列化簡單來說就是對象轉為字符串、字符串轉為對象,目的是為傳輸數據(傳給別的語言或者存儲))

三大認證(重中之重):認證(用戶是否合法)、權限(管理員、普通用戶)、頻率(次數過多限制)

其他組件(過濾、篩選、排序、分頁、路由)

 

1.接口

概念:聯繫兩個物質的媒介,完成信息的交互。在web程序中,聯繫前台頁面後台數據庫的媒介。

web接口組成:

url:長得像返回數據的url鏈接。如api.baidu.map/search

www.baidu.com不叫接口,叫url鏈接,訪問只能拿到主頁。api.baidu.map/search是接口,返回的是json數據)

請求參數:前台按照指定的key提供數據給後台。(必須是給定的,這樣後台才能以此提取數據再到數據庫查詢返回)

響應數據:後台與數據庫交互后,將數據反饋給前台。

因此,接口就是帶着請求參數訪問能夠得到響應數據的url鏈接。

接口 = url + 請求參數 + 響應數據

 

 

2.restful接口規範

接口規範:不同後台語言,使用同樣的接口返回同樣的數據。

如何寫接口:要寫url和響應數據。如果將請求參數也加上,就是在寫接口文檔。

 

兩大部分:

1url

1)用api關鍵字標識接口url。方式1api.baidu.com;方式2:www.baudu.com/api

2)接口數據安全性決定優先選用https協議

3)如果一個接口有多版本存在,需要在url中標識體現。如下的v1v2

api.baidu.com/v1/….  api.baidu.com/v2/….

4)操作中的數據稱為資源,在url中資源一般採用複數形式,一個接口可以概括對該資源的多種操作方式。(一個url對應一個類,類裏面可以有多個請求方法)

可以對操作隱藏,並且復用性更強(寫動作了,只能適用這一個動作,不寫其他動作都可以用)如api.baidu.com/books    api.baidu.com/books/(pk)

5)請求方式有多種,用一個url處理如何讓保證不混亂——通過不同的請求方式標識不同操作資源的方式

/books      get     獲取所有

/books post   增加一個(多個)

/books/(pk) delete 刪除一個

/books/(pk)  put 整體更新一個  #改一個用戶

/books/(pk)  patch 局部更新一個 #改一個用戶的密碼

 

6)資源往往涉及數據的各種操作方式:篩選、排序、限制

api.baidu.com/books/?search=寶馬&ordering=-price&limit=3

 

2)響應數據

1http請求的響應會有響應狀態碼,接口用來返回操作的資源數據,也有自己操作數據結果的資源狀態碼status 0代表操作資源成功,1代表操作失敗,2代表操作成功,但沒匹配結果)

注:資源狀態碼和http狀態碼不一樣,為前後台的約定

2)資源狀態碼的文字提示。

status ok “賬號有誤或者密碼有誤”

3)資源本身

results

:刪除資源成功不做任何數據返回(只返回空字符串,連狀態碼、狀態信息都不返回)

4)不能直接返回的資源(子資源、圖片、視頻等資源),返回該資源的url鏈接。

 

https://api.baidu.com/v1/books?limit=3
get|post|delete|put|patch   
{
  “status” : 0,
  “msg” : “ok”,
  “results”: [
{
  “title”: “三國”,
  “price”: 78,
  “img”: “https://.....”   
    }
  ]
}

 

3.django流程

1)項目準備:

 

1.分發路由

在項目文件夾的urls複製一份到應用文件夾中。然後在項目文件夾的urls分發路由給app:導入include,然後url(r’^api/’, include(‘api.urls’))。再在app文件夾的urls.py中分發路由給CBV

2.視圖

在應用中分發路由前,先寫類視圖

from django.http import JsonResponse
from django.views import View

class Book(View):

    def get(self, request, *args, **kwargs):
        return JsonResponse('get ok', safe=False)

    def post(self, request, *args, **kwargs):
        return JsonResponse('get ok', safe=False)   #safe默認為true,要返回字典。不是字典否則拋異常。

 

3.在應用urls下分發路由

from django.conf.urls import url
from . import views    #注意在應用中導入視圖都是.   從當前應用中導入

urlpatterns = [
    url(r'^books/', views.Book.as_view()),
]

 

4.定義模型類

1models.py中定義類

from django.db import models

class Book(models.Model):

    title = models.CharField(max_length=64)
    price = models.DecimalField(max_digits=5, decimal_places=2) #整數、小數位

    class Meta:    #嵌套類(給上級類添加功能或指定標準)
        
       db_table = 'book'    #自定義數據庫表名
        verbose_name = "book"   #給模型起個可讀的名字,默認是複數
        verbose_name_plural = verbose_name   #取消上面的複數

    def __str__(self):      #显示的內容
        return '<<%s>>' % self.title    

 

 

   (2)數據庫遷移

進入djangoshell環境中:Tools—-> run manage.py task

shell環境中生成遷移文件:makemigrations。然後遷移:migrate

 

5.生成admin

1)在amin.py中註冊並且導入模型

from django.contrib import admin
port models

admin.site.register(models.Book)

 

2)創建用戶

shell環境中:createsuper創建超級用戶,然後輸入用戶密碼(郵箱不用)

 

 

 2CBV的請求生命周期

請求如何到CBV下的getpost

a.請求過來,項目文件中路由分發給應用api的路由

b.應用分發路由走as_view函數。

views.Book.as_view()  保存一系列數據(requestargs**kwargs等)給Book對象,然後都給dispatch進行路由分發。

dispatch乾的事:判斷請求方式是否支持,然後返回(通過getattr)支持的這些請求方法(getpost等,在視圖中自定義getpost的返回值)的結果。

c.通過dispatch就執行了CBV下請求方式的結果,返回結果

 

 

4.django原生的接口、序列化

  六大基礎接口:獲取一個、獲取所有、增加一個、刪除一個、整體更新一個、局部更新一個

 十大接口:6大基礎、群增、群刪、整體群改、局部群改

 

1.在應用的urls.py下分發路由

url(r’^books/$’, views.Book.as_view()),   #必須要加$,否則後面匹配不到

url(r’^books/(?P<pk>.*)/$’, views.Book.as_view()),有名分組

在視圖函數中通過kwargs.get(pk)取到匹配的值

 

2.views.py里寫邏輯

class Book(View):

 

    def get(self, request, *args, **kwargs):

        pk = kwargs.get(‘pk’)  #獲取參數

        if not pk:  #群查接口

            #操作數據庫

            book_obj_list = models.Book.objects.all()

            #序列化過程

            book_list = []

            for obj in book_obj_list:   #將查到的對象序列化

                dic = {}

                dic[‘title’] = obj.title

                dic[‘price’] = obj.price

                book_list.append(dic)

            return JsonResponse({

                ‘status’ : 0,

                “msg” : “ok”,

                “results”: book_list,

            }, json_dumps_params={‘ensure_ascii’:False})

        else:    #單查接口

            book_dic = models.Book.objects.filter(pk=pk).values(

                ‘title’, ‘price’).first()

            if book_dic:

                return JsonResponse({

                    ‘status’: 0,

                    “msg”: “ok”,

                    “results”: book_dic,

                }, json_dumps_params={‘ensure_ascii’: False})

 

            return JsonResponse({

                ‘status’: 2,

                “msg”: “no results”,

            }, json_dumps_params={‘ensure_ascii’: False})

 

 

    def post(self, request, *args, **kwargs):

        #前台通過urlencoded方式提交數據

        try:

            book_obj = models.Book.objects.create(**request.POST.dict()) #create創建對象。將request.POST中存放的提交的關鍵詞參數轉化為字典以**方式傳進去。沒傳參數,這邊會報錯。

            if book_obj:

                return JsonResponse({

                ‘status’: 0,

                “msg”: “ok”,

                “results”: {‘title’:book_obj.title, “price”:book_obj.price}

            }, json_dumps_params={‘ensure_ascii’: False})

        except:    #健壯性

            return JsonResponse({

                ‘status’: 1,

                “msg”: “wrong params”,

            }, json_dumps_params={‘ensure_ascii’: False})

        return JsonResponse({    #可能操作數據庫失敗了

                ‘status’: 2,

                “msg”: “created failed”,

            }, json_dumps_params={‘ensure_ascii’: False})

           

  

JsonResponse返回時,中文會變成unicode,要加json_dumps_params={‘ensure_ascii’:False}選項。但在linux環境下的火狐瀏覽器,加了是亂碼。

 

filter返回queryset對象,對象里是個列表(表名:對象信息(有自定義str就是自定義的信息))。first取里第一個對象(相當於print(第一個對象)values展示對應的對象里的值

<QuerySet [<Book: <<三國演義>>>]>                   #直接.filter

<<三國演義>>                                    #.first()

<QuerySet [{‘title’: ‘三國演義‘, ‘price’: Decimal(‘56.00’)}]>  #.values(‘title’,’price’)

{‘title’: ‘三國演義‘, ‘price’: Decimal(‘56.00’)}             #.values.first()  是個字典

 

 上面序列化的工作很麻煩。drf就是為了方便序列化的。

 

postman可以完成不同方式的請求:getpostput

postman發送數據包有三種方式:form-dataurlencodedjson. 原生djangourlencoded數據提交兼容。

 

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

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

「虹之松原」原來是海岸保安林 國寶級景點靠公私協力經營

環境資訊中心特約記者 廖靜蕙報導

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

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

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

『圖論』LCA 最近公共祖先

概述篇

LCA (Least Common Ancestors) ,即最近公共祖先,是指這樣的一個問題:在一棵有根樹中,找出某兩個節點 uv 最近的公共祖先。

LCA 可分為在線算法離線算法

  • 在線算法:指程序可以以序列化的方式一個一個處理輸入,也就是說在一開始並不需要知道所有的輸入。
  • 離線算法:指一開始就需要知道問題的所有輸入數據,而在解決一個問題后立即輸出結果。

算法篇

對於該問題,很容易想到的做法是從 u、v 分別回溯到根節點,然後這兩條路徑中的第一個交點即為 u、v 的最近公共祖先,在一棵平衡二叉樹中,該算法的時間複雜度可以達到 O(logn)O(log⁡n) ,但是對於某些退化為鏈狀的樹來說,算法的時間複雜度最壞為 O(n)O(n) ,顯然無法滿足更高頻率的查詢。

本節將介紹幾種比較高效的算法來解決這一問題,常見的算法有三種:在線 DFS + ST 算法、倍增算法、離線 Tarjan 算法。

接下來我們來一一解釋這三種 /* 看似高深,其實也不簡單 */ 的算法。

在線 DFS + ST 算法

首先看到 ST 你會想到什麼呢?(腦補許久都沒有想到它會是哪個單詞的縮寫)

看過前文 『數據結構』RMQ 問題 的話你便可以明白 ST算法 的思路啦~

So ,關於 LCA 的這種在線算法也是可以建立在 RMQ 問題的基礎上咯~

我們設 LCA(T,u,v) 為在有根樹 T 中節點 u、v 的最近公共祖先, RMQ(A,i,j) 為線性序列 A 中區間 [i,j] 上的最小(大)值。

如下圖這棵有根樹:

我們令節點編號滿足父節點編號小於子節點編號(編號條件)

可以看出 LCA(T,4,5) = 2, LCA(T,2,8) = 1, LCA(T,3,9) = 3

設線性序列 A 為有根樹 T 的中序遍歷,即 A = [4,2,5,1,8,6,9,3,7]

由中序遍歷的性質我們可以知道,任意兩點 u、v 的最近公共祖先總在以該兩點所在位置為端點的區間內,且編號最小。

舉個栗子:

假設 u = 8, v = 7 ,則該兩點所確定的一段區間為 [8,6,9,3,7] ,而區間最小值為 3 ,也就是說,節點 3u、v 的最近公共祖先。

解決區間最值問題我們可以採用 RMQ 問題中的 ST 算法

但是在有些問題中給出的節點並不一定滿足我們所說的父節點編號小於子節點編號,因此我們可以利用節點間的關係建圖,然後採用前序遍歷來為每一個節點重新編號以生成線性序列 A ,於是問題又被轉化為了區間最值的查詢,和之前一樣的做法咯~

時間複雜度: n×O(logn)n×O(log⁡n) 預處理 + O(1)O(1) 查詢

想了解 RMQ 問題 的解法可以戳上面的鏈接哦~

以上部分介紹了 LCA 如何轉化為 RMQ 問題,而在實際中這兩種方案之間可以相互轉化

類比之前的做法,我們如何將一個線性序列轉化為滿足編號條件的有根樹呢?

  1. 設序列中的最小值為 AkAk ,建立優先級為 AkAk 的根節點 TkTk
  2. 將 A[1…k−1]A[1…k−1] 遞歸建樹作為 TkTk 的左子樹
  3. 將 A[k+1…n]A[k+1…n] 遞歸建樹作為 TkTk 的右子樹

讀者可以試着利用此方法將之前的線性序列 A = [4,2,5,1,8,6,9,3,7] 構造出有根樹 T ,結果一定滿足之前所說的編號條件,但卻不一定唯一。

離線 Tarjan 算法

Tarjan 算法是一種常見的用於解決 LCA 問題的離線算法,它結合了深度優先搜索與並查集,整個算法為線性處理時間。

首先來介紹一下 Tarjan 算法的基本思路:

  1. 任選一個節點為根節點,從根節點開始
  2. 遍歷該點 u 的所有子節點 v ,並標記 v 已經被訪問過
  3. 若 v 還有子節點,返回 2 ,否則下一步
  4. 合併 v 到 u 所在集合
  5. 尋找與當前點 u 有詢問關係的點 e
  6. 若 e 已經被訪問過,則可以確定 u、e 的最近公共祖先為 e 被合併到的父親節點

偽代碼:

Tarjan(u)               // merge 和 find 為並查集合併函數和查找函數
{
    for each(u,v)       // 遍歷 u 的所有子節點 v
    {
        Tarjan(v);      // 繼續往下遍歷
        merge(u,v);     // 合併 v 到 u 這一集合
        標記 v 已被訪問過;
    }
    for each(u,e)       // 遍歷所有與 u 有查詢關係的 e
    {
        if (e 被訪問過)
            u, e 的最近公共祖先為 find(e);
    }
}
C++

感覺講到這裏已經沒有其它內容了,但是一定會有好多人沒有理解怎麼辦呢?

我們假設在如下樹中模擬 Tarjan 過程(節點數量少一點可以畫更少的圖o( ̄▽ ̄)o)

存在查詢: LCA(T,3,4)、LCA(T,4,6)、LCA(T,2,1)

注意:每個節點的顏色代表它當前屬於哪一個集合,橙色線條為搜索路徑,黑色線條為合併路徑。

當前所在位置為 u = 1 ,未遍歷孩子集合 v = {2,5} ,向下遍歷。

當前所在位置為 u = 2 ,未遍歷孩子集合 v = {3,4} ,向下遍歷。

當前所在位置為 u = 3 ,未遍歷孩子集合 v = {} ,遞歸到達最底層,遍歷所有相關查詢發現存在 LCA(T,3,4) ,但是節點 4 此時標記未訪問,因此什麼也不做,該層遞歸結束。

遞歸返回,當前所在位置 u = 2 ,合併節點 3u 所在集合,標記 vis[3] = true ,此時未遍歷孩子集合 v = {4} ,向下遍歷。

當前所在位置 u = 4 ,未遍歷孩子集合 v = {} ,遍歷所有相關查詢發現存在 LCA(T,3,4) ,且 vis[3] = true ,此時得到該查詢的解為節點 3 所在集合的首領,即 LCA(T,3,4) = 2 ;又發現存在相關查詢 LCA(T,4,6) ,但是節點 6 此時標記未訪問,因此什麼也不做。該層遞歸結束。

遞歸返回,當前所在位置 u = 2 ,合併節點 4u 所在集合,標記 vis[4] = true ,未遍歷孩子集合 v = {} ,遍歷相關查詢發現存在 LCA(T,2,1) ,但是節點 1 此時標記未訪問,因此什麼也不做,該層遞歸結束。

遞歸返回,當前所在位置 u = 1 ,合併節點 2u 所在集合,標記 vis[2] = true ,未遍歷孩子集合 v = {5} ,繼續向下遍歷。

當前所在位置 u = 5 ,未遍歷孩子集合 v = {6} ,繼續向下遍歷。

當前所在位置 u = 6 ,未遍歷孩子集合 v = {} ,遍歷相關查詢發現存在 LCA(T,4,6) ,且 vis[4] = true ,因此得到該查詢的解為節點 4 所在集合的首領,即 LCA(T,4,6) = 1 ,該層遞歸結束。

遞歸返回,當前所在位置 u = 5 ,合併節點 6u 所在集合,並標記 vis[6] = true ,未遍歷孩子集合 v = {} ,無相關查詢因此該層遞歸結束。

遞歸返回,當前所在位置 u = 1 ,合併節點 5u 所在集合,並標記 vis[5] = true ,未遍歷孩子集合 v = {} ,遍歷相關查詢發現存在 LCA(T,2,1) ,此時該查詢的解便是節點 2 所在集合的首領,即 LCA(T,2,1) = 1 ,遞歸結束。

至此整個 Tarjan 算法便結束啦~

PS:不要在意最終根節點的顏色和其他節點顏色有一點點小小差距,可能是在染色的時候沒仔細看,總之就這樣咯~

PPS:所謂的首領就是、就是首領啦~

倍增算法

哇!還有一個倍增算法以後繼續補充吧!

總結篇

對於不同的 LCA 問題我們可以選擇不同的算法。

假若一棵樹存在動態更新,此時離線算法就顯得有點力不從心了,但是在其他情況下,離線算法往往效率更高(雖然不能保證得到解的順序與輸入一致,不過我們有 sort 呀)

總之,喜歡哪種風格的 code 是我們自己的意願咯~

另外, LCA 和 RMQ 問題是兩個非常基礎的問題,很多複雜問題都可以轉化為這兩類問題來解決。(當然這兩類問題之間也可以相互轉化啦~)

參考資料

OI wiki https://oi-wiki.org/graph/lca/

https://blog.csdn.net/my_sunshine26/article/details/72717112

https://wizardforcel.gitbooks.io/the-art-of-programming-by-july/content/03.03.html

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

【Spring註解驅動開發】在@Import註解中使用ImportBeanDefinitionRegistrar向容器中註冊bean

寫在前面

在前面的文章中,我們學習了如何使用@Import註解向Spring容器中導入bean,可以使用@Import註解快速向容器中導入bean,小夥伴們可以參見《【Spring註解驅動開發】使用@Import註解給容器中快速導入一個組件》。可以在@Import註解中使用ImportSelector接口導入bean,小夥伴們可以參見《【Spring註解驅動開發】在@Import註解中使用ImportSelector接口導入bean》一文。今天,我們就來說說,如何在@Import註解中使用ImportBeanDefinitionRegistrar向容器中註冊bean。

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

ImportBeanDefinitionRegistrar概述

概述

我們先來看看ImportBeanDefinitionRegistrar是個什麼鬼,點擊進入ImportBeanDefinitionRegistrar源碼,如下所示。

package org.springframework.context.annotation;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.type.AnnotationMetadata;

public interface ImportBeanDefinitionRegistrar {

	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {

		registerBeanDefinitions(importingClassMetadata, registry);
	}

	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}

}

由源碼可以看出,ImportBeanDefinitionRegistrar本質上是一個接口。在ImportBeanDefinitionRegistrar接口中,有一個registerBeanDefinitions()方法,通過registerBeanDefinitions()方法,我們可以向Spring容器中註冊bean實例。

Spring官方在動態註冊bean時,大部分套路其實是使用ImportBeanDefinitionRegistrar接口。

所有實現了該接口的類都會被ConfigurationClassPostProcessor處理,ConfigurationClassPostProcessor實現了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中動態註冊的bean是優先於依賴其的bean初始化的,也能被aop、validator等機制處理。

使用方法

ImportBeanDefinitionRegistrar需要配合@Configuration和@Import註解,@Configuration定義Java格式的Spring配置文件,@Import註解導入實現了ImportBeanDefinitionRegistrar接口的類。

ImportBeanDefinitionRegistrar實例

既然ImportBeanDefinitionRegistrar是一個接口,那我們就創建一個MyImportBeanDefinitionRegistrar類,實現ImportBeanDefinitionRegistrar接口,如下所示。

package io.mykit.spring.plugins.register.condition;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

/**
 * @author binghe
 * @version 1.0.0
 * @description ImportBeanDefinitionRegistrar的實現類
 */
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * AnnotationMetadata: 當前類的註解信息
     * BeanDefinitionRegistry:BeanDefinition註冊類
     * 通過調用BeanDefinitionRegistry接口的registerBeanDefinition()方法,可以將所有需要添加到容器中的bean注入到容器中。
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){

    }
}

可以看到,這裏,我們先創建了MyImportBeanDefinitionRegistrar類的大體框架。接下來,我們在PersonConfig2類上的@Import註解中,添加MyImportBeanDefinitionRegistrar類,如下所示。

@Configuration
@Import({Department.class, Employee.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class PersonConfig2 {

接下來,創建一個Company類,作為測試測試ImportBeanDefinitionRegistrar接口的bean,如下所示。

package io.mykit.spring.plugins.register.bean;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試ImportBeanDefinitionRegistrar接口的使用
 */
public class Company {
}

接下來,就要實現MyImportBeanDefinitionRegistrar類中的registerBeanDefinitions()方法的邏輯了,添加邏輯后的registerBeanDefinitions()方法如下所示。

    /**
     * AnnotationMetadata: 當前類的註解信息
     * BeanDefinitionRegistry:BeanDefinition註冊類
     * 通過調用BeanDefinitionRegistry接口的registerBeanDefinition()方法,可以將所有需要添加到容器中的bean注入到容器中。
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
        boolean employee = registry.containsBeanDefinition("employee");
        boolean department = registry.containsBeanDefinition("department");
        if (employee && department){
            BeanDefinition beanDefinition = new RootBeanDefinition(Company.class);
            registry.registerBeanDefinition("company", beanDefinition);
        }
    }

registerBeanDefinitions()方法的實現邏輯很簡單,就是判斷Spring容器中是否同時存在以employee命名的bean和以department命名的bean,如果同時存在以employee命名的bean和以department命名的bean,則向Spring容器中注入一個以company命名的bean。

接下來,我們就運行SpringBeanTest類中的testAnnotationConfig7()方法來進行測試,輸出結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
personConfig2
io.mykit.spring.plugins.register.bean.Department
io.mykit.spring.plugins.register.bean.Employee
io.mykit.spring.plugins.register.bean.User
io.mykit.spring.plugins.register.bean.Role
person
binghe001

可以看到,在輸出結果中,並沒有看到“company”,這是因為輸出結果中存在io.mykit.spring.plugins.register.bean.Department和io.mykit.spring.plugins.register.bean.Employee,並不存在我們代碼邏輯中的department和employee。所以,我們將registerBeanDefinitions()方法的邏輯稍微修改下,修改后的代碼如下所示。

/**
  * AnnotationMetadata: 當前類的註解信息
  * BeanDefinitionRegistry:BeanDefinition註冊類
  * 通過調用BeanDefinitionRegistry接口的registerBeanDefinition()方法,可以將所有需要添加到容器中的bean注入到容器中。
  */
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
    boolean employee = registry.containsBeanDefinition(Employee.class.getName());
    boolean department = registry.containsBeanDefinition(Department.class.getName());
    if (employee && department){
        BeanDefinition beanDefinition = new RootBeanDefinition(Company.class);
        registry.registerBeanDefinition("company", beanDefinition);
    }
}

接下來,我們再次運行SpringBeanTest類中的testAnnotationConfig7()方法來進行測試,輸出結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
personConfig2
io.mykit.spring.plugins.register.bean.Department
io.mykit.spring.plugins.register.bean.Employee
io.mykit.spring.plugins.register.bean.User
io.mykit.spring.plugins.register.bean.Role
person
binghe001
company

可以看到,此時輸出了company,說明Spring容器中已經成功註冊了以company命名的bean。

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

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

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

從字符串到常量池,一文看懂String類設計

從一道面試題開始

看到這個標題,你肯定以為我又要講這道面試題了

//  這行代碼創建了幾個對象?
String s3 = new String("1");

是的,沒錯,我確實要從這裏開始

這道題就算你沒做過也肯定看到,總所周知,它創建了兩個對象,一個位於堆上,一個位於常量池中。

這個答案粗看起來是沒有任何問題的,但是仔細思考確經不起推敲。

如果你覺得我說的不對的話,那麼可以思考下面這兩個問題

  1. 你說它創建了兩個對象,那麼這兩個對象分別是怎樣創建的呢?我們回顧下Java創建對象的方式,一共就這麼幾種

    • 使用new關鍵字創建對象
    • 使用反射創建對象(包括Class類的newInstance方法,以及Constructor類的newInstance方法)
    • 使用clone複製一個對象
    • 反序列化得到一個對象

    你說它創建了兩個對象,那你告訴我除了new出來那個對象外,另外一個對象怎麼創建出來的?

  2. 堆跟常量池到底什麼關係?不是說在JDK1.7之後(含1.7版本)常量池已經移到了堆中了嗎?如果說常量池本身就位於堆中的話,那麼這種一個對象在堆中,一個對象在常量池的說法還準確嗎?

如果你也產生過這些疑問的話,那麼請耐心看完這篇文章!要解釋上面的問題首先我們得對常量池有個準確的認知。

常量池

通常來說,我們提到的常量池分為三種

  • class文件中的常量池
  • 運行時常量池
  • 字符串常量池

對於這三種常量池,我們需要搞懂下面幾個問題?

  1. 這個常量池在哪裡?
  2. 這個常量池用來干什麼呢?
  3. 這三者有什麼關係?

接下來,我們帶着這些問題往下看

class文件中的常量池

位置在哪?

顧名思義,class文件中的常量池當然是位於class文件中,而class文件又是位於磁盤上

用來干什麼的?

在學習class文件中的常量池前,我們首選需要對class文件的結構有一定了解

Class文件是一組以8個字節為基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在文

件之中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內容幾乎全部是程序運行的必要數

據,沒有空隙存在。

​ ————《深入理解Java虛擬機》

整個class文件的組成可以用下圖來表示

對本文而言,我們只關注其中的常量池部分,常量池可以理解為class文件中資源倉庫,它是class文件結構中與其它項目關聯最多的數據類型,主要用於存放編譯器生成的各種字面量(Literal)和符號引用(Symbolic References)
字面量就是我們所說的常量概念,如文本字符串、被聲明為final的常量值等
符號引用是一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可(它與直接引用區分一下,直接引用一般是指向方法區的本地指針,相對偏移量或是一個能間接定位到目標的句柄)。一般包括下面三類常量:

  • 類和接口的全限定名
  • 字段的名稱和描述符
  • 方法的名稱和描述符

現在我們知道了class文件中常量池的作用:存放編譯器生成的各種字面量(Literal)和符號引用(Symbolic References)。很多時候知道了一個東西的概念並不能說你會了,對於程序員而言,如果你說你已經會了,那麼最好的證明是你能夠通過代碼將其描述出來,所以,接下來,我想以一種直觀的方式讓大家感受到常量池的存在。通過分析一段簡單代碼的字節碼,讓大家能更好感知常量池的作用。

talk is cheap ,show me code

我們以下面這段代碼為例,通過javap來查看class文件中的具體內容,代碼如下:

/**
 * @author 程序員DMZ
 * @Date Create in 22:59 2020/6/15
 * @公眾號 微信搜索:程序員DMZ
 */
public class Main {
    public static void main(String[] args) {
        String name = "dmz";
    }
}

進入Main.java文件所在目錄,執行命令:javac Main.java ,那麼此時會在當前目錄下生成對應的Main.class文件。再執行命令:javap -v -c Main.class,此時會得到如下的解析后的字節碼信息

public class com.dmz.jvm.Main
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
// 這裏就是常量池了
Constant pool:
   #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #2 = String             #21            // dmz
   #3 = Class              #22            // com/dmz/jvm/Main
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               LocalVariableTable
  #10 = Utf8               this
  #11 = Utf8               Lcom/dmz/jvm/Main;
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               args
  #15 = Utf8               [Ljava/lang/String;
  #16 = Utf8               name
  #17 = Utf8               Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               Main.java
  #20 = NameAndType        #5:#6          // "<init>":()V
  #21 = Utf8               dmz
  #22 = Utf8               com/dmz/jvm/Main
  #23 = Utf8               java/lang/Object
 // 下面是方法表                           
{
  public com.dmz.jvm.Main();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/dmz/jvm/Main;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         // 可以看到方法表中的指令引用了常量池中的常量,這也是為什麼說常量池是資源倉庫的原因
         // 因為它會被class文件中的其它結構引用         
         0: ldc           #2                  // String dmz
         2: astore_1
         3: return
      LineNumberTable:
        line 9: 0
        line 10: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  args   [Ljava/lang/String;
            3       1     1  name   Ljava/lang/String;
}
SourceFile: "Main.java"

在上面的字節碼中,我們暫且關注常量池中的內容即可。主要看這兩行

#2 = String             #14            // dmz
#14 = Utf8               dmz

如果要看懂這兩行代碼,我們需要對常量池中String類型常量的結構有一定了解,其結構如下:

CONSTANT_String_info tag 標誌常量類型的標籤
index 指向字符串字面量的索引

對應到我們上面的字節碼中,tag=String,index=#14,所以我們可以知道,#2是一個字面量為#14的字符串類型常量。而#14對應的字面量信息(一個Utf8類型的常量)就是dmz

常量池作為資源倉庫,最大的用處在於被class文件中的其它結構所引用,這個時候我們再將注意力放到main方法上來,對應的就是這三條指令

0: ldc           #2                  // String dmz
2: astore_1
3: return

ldc:這個指令的作用是將對應的常量的引用壓入操作數棧,在執行ldc指令時會觸發對它的符號引用進行解析,在上面例子中對應的符號引用就是#2,也就是常量池中的第二個元素(這裏就能看出方法表中就引用了常量池中的資源)

astore_1:將操作數棧底元素彈出,存儲到局部變量表中的1號元素

return:方法返回值為void,標誌方法執行完成,將方法對應棧幀從棧中彈出

下面我用畫圖的方式來畫出整個流程,主要分為四步

  1. 解析ldc指令的符號引用(#2

  2. #2對應的常量的引用壓入到操作數棧頂

  3. 將操作數棧的元素彈出並存儲到局部變量表中

  4. 執行return指令,方法執行結束,彈出棧區該方法對應的棧幀

第一步:

在解析#2這個符號引用時,會先到字符串常量池中查找是否存在對應字符串實例的引用,如果有的話,那麼直接返回這個字符串實例的引用,如果沒有的話,會創建一個字符串實例,那麼將其添加到字符串常量池中(實際上是將其引用放入到一個哈希表中),之後再返回這個字符串實例對象的引用。

到這裏也能回答我們之前提出的那個問題了,一個對象是new出來的,另外一個是在解析常量池的時候JVM自動創建的

第二步:

將第一步得到的引用壓入到操作數棧,此時這個字符串實例同時被操作數棧以及字符串常量池引用。

第三步:

操作數棧中的引用彈出,並賦值給局部變量表中的1號位置元素,到這一步其實執行完了String name = "dmz"這行代碼。此時局部變量表中儲存着一個指向堆中字符串實例的引用,並且這個字符串實例同時也被字符串常量池引用。

第四步:

這一步我就不畫圖了,就是方法執行完成,棧幀彈出,非常簡單。

在上文中,我多次提到了字符串常量池,它到底是個什麼東西呢?我們還是分為兩部分討論

  1. 位置在哪?
  2. 用來干什麼的?

字符串常量池

位置在哪?

字符串常量池比較特殊,在JDK1.7之前,其存在於永久代中,到JDK1.7及之後,已經中永久代移到了堆中。當然,如果你非要說永久代也是堆的一部分那我也沒辦法。

另外還要說明一點,經常有同學會將方法區元空間永久代(permgen space)的概念混淆。請注意

  1. 方法區JVM在內存分配時需要遵守的規範,是一個理論,具體的實現可以因人而異
  2. 永久代hotspot jdk1.8以前對方法區的實現,使用jdk1.7的老司機肯定以前經常遇到過java.lang.OutOfMemoryError: PremGen space異常。這裏的PermGen space其實指的就是方法區。不過方法區和PermGen space又有着本質的區別。前者是JVM的規範,而後者則是JVM規範的一種實現,並且只有HotSpot才有PermGen space
  3. 元空間jdk1.8對方法區的實現,jdk1.8徹底移除了永久代,其實,移除永久代的工作從JDK 1.7就開始了。JDK 1.7中,存儲在永久代的部分數據就已經轉移到Java Heap或者Native Heap。但永久代仍存在於JDK 1.7中,並沒有完全移除,譬如符號引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了Java heap;類的靜態變量(class statics)轉移到了Java heap。到jdk1.8徹底移除了永久代,將JDK7中還剩餘的永久代信息全部移到元空間,元空間相比對永久代最大的差別是,元空間使用的是本地內存(Native Memory)

用來干什麼的?

字符串常量池,顧名思義,肯定就是用來存儲字符串的嘛,準確來說存儲的是字符串實例對象的引用。我查閱了很多博客、資料,它們都會說,字符串常量池中存儲的就是字符串對象。其實我們可以類比下面這段代碼:

HashSet<Person> persons = new HashSet<Person>;

persons這個集合中,存儲的是Person對象還是Person對象對應的引用呢?

所以,請大聲跟我念三遍

字符串常量池存儲的是字符串實例對象的引用!

字符串常量池存儲的是字符串實例對象的引用!

字符串常量池存儲的是字符串實例對象的引用!

下面我們來看R大博文下評論的一段話:

簡單來說,HotSpot VM里StringTable是個哈希表,裏面存的是駐留字符串的引用(而不是駐留字符串實例自身)。也就是說某些普通的字符串實例被這個StringTable引用之後就等同被賦予了“駐留字符串”的身份。這個StringTable在每個HotSpot VM的實例里只有一份,被所有的類共享。類的運行時常量池裡的CONSTANT_String類型的常量,經過解析(resolve)之後,同樣存的是字符串的引用;解析的過程會去查詢StringTable,以保證運行時常量池所引用的字符串與StringTable所引用的是一致的。

​ ——R大博客

從上面我們可以知道

  1. 字符串常量池本質就是一個哈希表
  2. 字符串常量池中存儲的是字符串實例的引用
  3. 字符串常量池在被整個JVM共享
  4. 在解析運行時常量池中的符號引用時,會去查詢字符串常量池,確保運行時常量池中解析后的直接引用跟字符串常量池中的引用是一致的

為了更好理解上面的內容,我們需要去分析String中的一個方法—–intern()

intern方法分析

/** 
 * Returns a canonical representation for the string object. 
 * <p> 
 * A pool of strings, initially empty, is maintained privately by the 
 * class <code>String</code>. 
 * <p> 
 * When the intern method is invoked, if the pool already contains a 
 * string equal to this <code>String</code> object as determined by 
 * the {@link #equals(Object)} method, then the string from the pool is 
 * returned. Otherwise, this <code>String</code> object is added to the 
 * pool and a reference to this <code>String</code> object is returned. 
 * <p> 
 * It follows that for any two strings <code>s</code> and <code>t</code>, 
 * <code>s.intern()&nbsp;==&nbsp;t.intern()</code> is <code>true</code> 
 * if and only if <code>s.equals(t)</code> is <code>true</code>. 
 * <p> 
 * All literal strings and string-valued constant expressions are 
 * interned. String literals are defined in section 3.10.5 of the 
 * <cite>The Java&trade; Language Specification</cite>. 
 * 
 * @return  a string that has the same contents as this string, but is 
 *          guaranteed to be from a pool of unique strings. 
 */  
public native String intern();  

String#intern方法中看到,這個方法是一個 native 的方法,但註釋寫的非常明了。“如果常量池中存在當前字符串, 就會直接返回當前字符串. 如果常量池中沒有此字符串, 會將此字符串放入常量池中后, 再返回”。

關於其詳細的分析可以參考:美團:深入解析String#intern

珠玉在前,所以本文着重就分析下intern方法在JDK不同版本下的差異,首先我們要知道引起差異的原因是因為JDK1.7及之後將字符串常量池從永久代挪到了堆中。

我這裏就以美團文章中的示例代碼來進行分析,代碼如下:

public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
}

打印結果是

  • jdk6 下false false
  • jdk7 下false true

在美團的文章中已經對這個結果做了詳細的解釋,接下來我就用我的圖解方式再分析一波這個過程

jdk6 執行流程

第一步:執行 String s = new String("1"),要清楚這行代碼的執行過程,我們還是得從字節碼入手,這行代碼對應的字節碼如下:

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/String
       3: dup
       4: ldc           #3                  // String 1
       6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
       9: astore_1
      10: return

new :創建了一個類的實例(還沒有調用構造器函數),並將其引用壓入操作數棧頂

dup:複製棧頂數值並將複製值壓入棧頂,這是因為invokespecialastore_1各需要消耗一個引用

ldc:解析常量池符號引用,將實際的直接引用壓入操作數棧頂

invokespecial:彈出此時棧頂的常量引用及對象引用,執行invokespecial指令,調用構造函數

astore_1:將此時操作數棧頂的元素彈出,賦值給局部變量表中1號元素(0號元素存的是main函數的參數)

我們可以將上面整個過程分為兩個階段

  1. 解析常量
  2. 調用構造函數創建對象並返回引用

在解析常量的過程中,因為該字符串常量是第一次解析,所以會先在永久代中創建一個字符串實例對象,並將其引用添加到字符串常量池中。此時內存狀態如下:

當真正通過new方式創建對象完成后,對應的內存狀態如下,因為在分析class文件中的常量池的時候已經對棧區做了詳細的分析,所以這裏就省略一些細節了,在執行完這行代碼后,棧區存在一個引用,指向 了堆區的一個字符串實例內存狀態對應如下:

第二步:緊接着,我們調用了s的intern方法,對應代碼就是 s.intern()

當intern方法執行時,因為此時字符串常量池中已經存在了一個字面量信息跟s相同的字符串的引用,所以此時內存狀態不會發生任何改變。

第三步:執行String s2 = "1",此時因為常量池中已經存在了字面量1的對應字符串實例的引用,所以,這裏就直接返回了這個引用並且賦值給了局部變量s2。對應的內存狀態如下:

到這裏就很清晰了,s跟s2指向兩個不同的對象,所以s==s2肯定是false嘛~

如果看過美團那篇文章的同學可能會有些疑惑,我在圖中對常量池的描述跟美團文章圖中略有差異,在美團那篇文章中,直接將具體的字符串實例放到了字符串常量池中,而在我上面的圖中,字符串常量池存的永遠時引用,它的圖是這樣畫的

就我查閱的資料而言,我個人不贊同這種說法,常量池中應該保存的僅僅是引用。關於這個問題,我已經向美團的團隊進行了留言,也請大佬出來糾錯!

接着我們分析s3跟s4,對應的就是這幾行代碼:

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);

我們一行行分析,看看執行完后,內存的狀態是什麼樣的

第一步:String s3 = new String("1") + new String("1"),執行完成后,堆區多了兩個匿名對象,這個我們不用多關注,另外堆區還多了一個字面量為11的字符串實例,並且棧中存在一個引用指向這個實例

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NVeeWKoO-1592334452491)(upload\image-20200617020742618.png)]

實際上上圖中還少了一個匿名的StringBuilder的對象,這是因為當我們在進行字符串拼接時,編譯器默認會創建一個StringBuilder對象並調用其append方法來進行拼接,最後再調用其toString方法來轉換成一個字符串,StringBuildertoString方法其實就是new一個字符串

public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

這也是為什麼在圖中會說在堆上多了一個字面量為11的字符串實例的原因,因為實際上就是new出來的嘛!

第二步:s3.intern()

調用intern方法后,因為字符串常量池中目前沒有11這個字面量對應的字符串實例的應用,所以JVM會先從堆區複製一個字符串實例到永久代中,再將其引用添加到字符串常量池中,最終的內存狀態就如下所示

第三步:String s4 = "11"

這應該沒啥好說的了吧,常量池中有了,直接指向對應的字符串實例

到這裏可以發現,s3跟s4指向的根本就是兩個不同的對象,所以也返回false

jdk7 執行流程

在jdk1.7中,s跟s2的執行結果還是一樣的,這是因為 String s = new String("1")這行代碼本身就創建了兩個字符串對象,一個屬於被常量池引用的駐留字符串,而另外一個只是堆上的一個普通字符串對象。跟1.6的區別在於,1.7中的駐留字符串位於堆上,而1.6中的位於方法區中,但是本質上它們還是兩個不同的對象,在下面代碼執行完后

    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

內存狀態為:

但是對於s3跟s4確不同了,因為在jdk1.7中不會再去複製字符串實例了,在intern方法執行時在發現堆上有對應的對象之後,直接將這個對應的引用添加到字符串常量池中,所以代碼執行完,內存狀態對應如下:

看到了吧,s3跟s4指向的同一個對象,這是因為intern方法執行時,直接s3這個引用複製到了常量池,之後執行String s4= "11"的時候,直接再將常量池中的引用複製給了s4,所以s3==s4肯定為true啦。

在理解了它們之間的差異之後,我們再來思考一個問題,假設我現在將代碼改成這個樣子,那麼運行結果是什麼樣的呢?

public static void main(String[] args) {
    String s = new String("1");
    String sintern = s.intern();
    String s2 = "1";
    System.out.println(sintern == s2);

    String s3 = new String("1") + new String("1");
    String s3intern = s3.intern();
    String s4 = "11";
    System.out.println(s3intern == s4);
}

上面這段代碼運行起來結果會有差異嗎?大家可以自行思考~

在我們對字符串常量池有了一定理解之後會發現,其實通過String name = "dmz"這行代碼申明一個字符串,實際的執行邏輯就像下面這段偽代碼所示

/**
  * 這段代碼邏輯類比於
  * <code>String s = "字面量"</code>;這種方式申明一個字符串
  * 其中字面量就是在""中的值
  *
  */
public String declareString(字面量) {
    String s;
    // 這是一個偽方法,標明會根據字面量的值到字符串值中查找是否存在對應String實例的引用
    s = findInStringTable(字面量);
    // 說明字符串池中已經存在了這個引用,那麼直接返回
    if (s != null) {
        return s;
    }
    // 不存在這個引用,需要新建一個字符串實例,然後調用其intern方法將其拘留到字符串池中,
    // 最後返回這個新建字符串的引用
    s = new String(字面量);
    // 調用intern方法,將創建好的字符串放入到StringTable中,
    // 類似就是調用StringTable.add(s)這也的一個偽方法
    s.intern();
    return s;
}

按照這個邏輯,我們將我們將上面思考題中的所有字面量進行替換,會發現不管在哪個版本中結果都應該返回true。

運行時常量池

位置在哪?

位於方法區中,1.6在永久代,1.7在元空間中,永久代跟元空間都是對方法區的實現

用來干什麼?

jvm在執行某個類的時候,必須經過加載、連接、初始化,而連接又包括驗證# 位置在哪?

位於方法區中,1.6在永久代,1.7在元空間中,永久代跟元空間都是對方法區的實現

用來干什麼?

jvm在執行某個類的時候,必須經過加載、連接、初始化,而連接又包括驗證、準備、解析三個階段。而當類加載到內存中后,jvm就會將class常量池中的內容存放到運行時常量池中,由此可知,運行時常量池也是每個類都有一個。在上面我也說了,class常量池中存的是字面量和符號引用,也就是說他們存的並不是對象的實例,而是對象的符號引用值。而經過解析(resolve)之後,也就是把符號引用替換為直接引用,解析的過程會去查詢全局字符串池,也就是我們上面所說的StringTable,以保證運行時常量池所引用的字符串與全局字符串池中所引用的是一致的

所以簡單來說,運行時常量池就是用來存放class常量池中的內容的。

總結

我們將三者進行一個比較

以一道測試題結束

// 環境1.7及以上
public class Clazz {
    public static void main(String[] args) {
        String s1 = new StringBuilder().append("ja").append("va1").toString();
        String s2 = s1.intern();
        System.out.println(s1==s2);
        
        String s5 = "dmz";
        String s3 = new StringBuilder().append("d").append("mz").toString();
        String s4 = s3.intern();
        System.out.println(s3 == s4);

        String s7 = new StringBuilder().append("s").append("pring").toString();
        String s8 = s7.intern();
        String s6 = "spring";
        System.out.println(s7 == s8);
    }
}

答案是true,false,true。大家可以仔細思考為什麼,如有疑惑可以給我留言,或者進群交流!

如果本文對你有幫助的話,記得點個贊吧!也歡迎關注我的公眾號,微信搜索:程序員DMZ,或者掃描下方二維碼,跟着我一起認認真真學Java,踏踏實實做一個coder。

我叫DMZ,一個在學習路上匍匐前行的小菜鳥!

參考文章:

R大博文:請別再拿“String s = new String(“xyz”);創建了多少個String實例”來面試了吧

R大知乎回答:JVM 常量池中存儲的是對象還是引用呢?

Java中幾種常量池的區分

方法區,永久代和元空間

美團:深入解析String#intern

參考書籍:

《深入理解Java虛擬機》第二版

《深入理解Java虛擬機》第三版

《Java虛擬機規範》

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心