特斯拉將推全新電動車Model S P100D 續航里程可達530公里

據外媒報導,特斯拉將推出Model S P100D車型,新車的綜合性能更強,最大續航里程可達530公里。

Model S P90D與Model S P85D相同,都採用了前後雙電動馬達的配置,但前者進一步強化了電動馬達的馬力輸出表現,安置於前輪的電動馬達可輸出190千瓦的最大功率,後輪的電動馬達則可提供高達370千瓦的最大功率,使得Model S P90D的最大綜合輸出馬力達到驚人的560千瓦。在Model S P90D車型上,特斯拉還新增了動力更加強勁的Ludicrous競速模式,它可讓車輛百公里加速秒數縮短10%,僅需2.8秒即可完成,極速則可達到250公里/小時。

在動力系統方面,Model S P100D車型將搭載功率為100千瓦時的電池里程套件,並搭載雙電機,以及四驅系統。新車的百公里加速時間為2.7秒,最大續航里程可能超過530公里。相較於特斯拉旗下的現款最強車型Model S P90D增加了40公里的續航里程,並縮短了0.6秒的百公里加速時間。

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

【其他文章推薦】

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

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

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

※小三通海運與一般國際貿易有何不同?

※小三通快遞通關作業有哪些?

奔馳下一代汽車將棄用特斯拉電動傳動系統 採用自家技術a

戴姆勒電動汽車主管哈拉爾德•克勒格爾(Harald Kroeger)在接受德國媒體採訪時說,為了開發自有傳動系統和電池技術,奔馳已經投資了5.5億美元,下一代B級Electric Drive電動汽車不會使用特斯拉傳動系統。

奔馳這樣做合情合理。2014年年末時,奔馳基本上已經清空了特斯拉持股。在2014年的年報中特斯拉也說B級車研發合作已經基本完成。到了2015年年底,特斯拉已經銷售10萬輛電動汽車。以前特斯拉是矽谷新崛起的明星企業,一些大型汽車公司有興趣與它合作,現在汽車公司已經將特斯拉當成了競爭對手。比如豐田,在特斯拉成立之初,豐田也曾參與投資,特斯拉還為豐田電動版RAV4提供傳動系統。

那時的特斯拉還很年輕,它還沒有推出Model S轎車,如果能與大型汽車商合作對公司的發展無疑是有利的。特斯拉需要增加營收,只有有了營收,公司才有可能轉變為主流汽車製造商。另外,在公司沒有上市之前,它需要吸引早期投資者來支援自己。今天的特斯拉已經完全不同了,Model S大獲成功,Model X剛剛開始向用戶交貨。本月晚些時候Model 3也將上市,這是一款低價電動汽車。可以說現在的特斯拉已經是一家規模雖小但是卻很成熟的電動汽車製造商了。

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

【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

※小三通物流營運型態?

※快速運回,大陸空運推薦?

柯文哲訪美西 特斯拉初體驗

台北市長柯文哲近日參訪美國舊金山,並於10日拜訪全球知名的電動車大廠特斯拉汽車公司(Tesla Motors, Inc.),與特斯拉交換電動車友善城市的意見。

由Elon Musk與史丹佛碩士JB Straul聯手打造的特斯拉,是目前知名度最高的電動車製造商。特斯拉將自己定位為「科技產業」,除生產純電動汽車外,也加入自動駕駛等功能。同時,特斯拉搭載的儲能裝置採用鋰電池,其鋰電池技術也透過品牌「Tesla Energy」開始提供Powerwall、Powerpack家用與商用儲能系統。

柯文哲在美西時間10日下午參訪特斯拉,並試乘價值8 萬美金的特斯拉Model X 經典車款。台北市政府表示,柯文哲安排本次參訪的目的,是為了向特斯拉請益如何打造電動車友善城市,並與對方交流意見。

在台北市,目前能見度最高的電動車為電動機車Gogoro。Gogoro致力於打造智慧城市,透過電池交換系統來提供車輛行駛時所需的電力,同時在北、北、基、桃、竹都設有電池交換站,提高民眾採購Gogoro的意願。

(照片來源:台北市政府)

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

【其他文章推薦】

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

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

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

※台灣寄大陸海運貨物規則及重量限制?

※大陸寄台灣海運費用試算一覽表

※南投搬家前需注意的眉眉角角,別等搬了再說!

寶馬未來百年發力電動車 將在中國實現“國產”

德國慕尼克報導寶馬集團於日前舉辦百年慶典之際,不僅宣佈企業正式進入下一個百年,同時寶馬集團董事長科魯格先生還表示:“我們在多種技術上進行投資,並持續在電動化領域的投入。”根據寶馬集團的規劃,未來每一款產品都會推出插電式混合動力車型。

對於寶馬在華新能源佈局,寶馬集團大中華區總裁兼首席執行官康思遠先生于近日在慕尼克的寶馬總部與車王雜誌溝通時透露:“未來我們將推出更多插電式混合動力車型,寶馬在華的瀋陽工廠正在建設高壓電池中心,是我們為長期發展所做出的投資。”這也將成為國內首個在華建設動力電池的豪華車製造商,並為後續的寶馬插電式混合動力車型提供電池。目前,寶馬已在華投產首款插電式混合動力車型530Le,後續有望再基於2系旅行車、新3系以及全新X1打造的3款國產插電混合動力車。

根據寶馬的新車規劃,全新一代X1將在2016年中實現投產,這款SUV將推出標準軸距版和長軸距版,其中長軸距版將推出三排七座的車型。此外,全新X1還將推出一款插電混動版車型,網通社從國家工商行政管理總局商標局獲悉,寶馬股份公司已在華註冊了“xDrive25Le”商標。其中xDrive25Le中“Le”是指加長版插電式混合動力車,與5系插電混動版530Le的含義相同。新車將搭載由1.5升三缸渦輪增壓汽油發動機和一台後置電動機組成混動系統,總功率輸出可達142千瓦,預計綜合油耗將保持2L左右的水準。

今年3月21日,創新寶馬2系旅行車將實現在華投產上市,基於新車型所打造的插電混動版本也有望投產。現階段,寶馬2系旅行車的插電混動版(225xe)已在海外推出,並搭載的混動系統由1.5T發動機和一款動力輸出可達65千瓦和165牛米電動機組成,當發動機和電動機同時做功時,這套插電式混合動力系統的綜合最大功率為165千瓦,峰值扭矩達到385牛米,百公里綜合油耗僅在2L左右。

除2系旅行車和全新X1之外,目前在華投產的3系轎車也有望推出插電混合動力版車型。此前寶馬已在國際車展上發佈了3系的插電式混合動力車型330e。新車在動力上採用的2.0T+電動機的插電混動系統,最大綜合功率可達到185千瓦,峰值扭矩則為420牛米。百公里綜合油耗僅為2L,與現款3系同搭載2.0T發動機的320i車型6.7L/100km相比,油耗下降了約70%。

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

【其他文章推薦】

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

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

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

※大陸寄台灣空運注意事項

※大陸海運台灣交貨時間多久?

英國2017年將在高速公路上測試無人汽車

據路透社報導,英國政府宣佈,2017年將首次在高速公路上測試無人駕駛汽車,其目標是到2020年允許無人駕駛汽車行駛在城市街道上。

英國財政部稱,下週三,英國財政大臣喬治•奧斯本(George Osborne)公佈年度預算報告時,將宣佈在高速公路測試無人駕駛汽車的相關計畫,並表示英國政府將提出議案,清除影響無人駕駛汽車技術部署的法規壁壘。

根據英國政府的資料,在全球範圍內,無人駕駛汽車市場的規模近期內將達到9000億英鎊(約合1.29萬億美元),但需要克服一系列法律障礙,包括明確誰為無人駕駛汽車發生的車禍負責等。

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

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

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

※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

系統分析師-軟件水平考試(高級)-理論-系統規劃

系統分析師-軟件水平考試(高級)-理論-系統規劃

前言

系統規劃是什麼東東呢?

通俗點說,就是為了實現企業的戰略目標,分析企業現狀(技術外部)與系統狀況(技術內部),從而提出新的技術要求,對現有系統進行規劃設計等變化。

  1. 目標是實現企業戰略目標;
  2. 分析企業現狀(了解情況了,才能下手唄。站在技術角度,企業現狀就分技術和非技術);
  3. 根據掌握的資料,對接下來的系統開發做出規劃(技術規劃,那是CTO的工作,這裏就直接系統規劃);
  4. 落實規劃,開發或改造系統,從而完成對企業戰略目標的支持。

期間會涉及對一些項目或系統(一個公司會擁有多個項目與多個系統,用於支撐公司產業線)的評估,如機會選擇,可行性分析,效益分析(分析師比架構師多了這些思量與考察,而高項則更為具體)。

XMIND

(圖片絕對清楚。如果看不清楚,請下載,或者在新頁面中打開圖片)

總結

XMIND重難點的解讀

步驟

為了幫助大家更好地理解這部分內容,我們舉個栗子。

這樣說吧,阿里巴巴定下了淘寶雙十一戰略目標。你作為CTO,就要站在公司高層,開始規劃阿里的系統,讓它為雙十一這一戰略目標服務。

具體的實施過程應該是這樣的:

  1. 首先,根據雙十一這一戰略目標,你得分析阿里現在技術環境(可以做出怎樣的改變,業務上是否可以進行一定的妥協等等),並了解系統當前的狀況(是否可以承受住雙十一壓力。承受不了的話,瓶頸在哪裡等等)。無論做什麼,收集信息,了解情況都是首要的。
  2. 其次,站在技術的角度,你需要確定公司信息系統該達到怎樣的目標(更大一些說,應該是技術的目標標準)。比如雙十一這樣的戰略目標,我確定公司的系統應該達到十萬的訂單處理速度(2019年,阿里的訂單創建峰值已經達到了五十萬左右),以及一系列類似的系統要求。
  3. 然後,根據前面制定的一系列系統要求。開始規劃系統的拆分,將目標系統進行邏輯的拆分。但是如果已經有了系統基礎,那麼就需要在原有的系統上,進行調整。如阿里雲平台必須保證在雙十一時提供足夠強大的基礎設施支撐(可以在必要時,支撐大當量的橫向擴展)。又如支付寶需要確保在雙十一零點(流量衝擊的峰值)保證可以支撐百萬計的支付請求,並且妥善處理超出的部分。這樣就完成了目標的落地方案生成。
  4. 接下來,需要確定工作的優先級別,以及開發順序。由於總的開發資源是有限的,並且不同業務之間存在各種錯綜複雜的關係,所以,需要CTO站在公司高層角度,進行這個層次的方案安排。如由於阿里雲是其它業務的支撐基礎,是實現目標的必要因素(甚至都沒有太多業務妥協的空間),那麼我們可以將它作為優先級第一的工作,並最大程度地傾斜企業資源等。其它工作以此類推。從複雜的角度,可以了解項目組合管理,以及企業戰略管理等(但是分析師考試不會這麼複雜的,這裏只是舉個栗子)。
  5. 接着,我需要對這些方案進行可行性分析。畢竟這些方案可能是我根據自己的經驗,以及周邊個別的意見,花了一天的時間整合出來的。其中可能存在一些不合理,不科學的部分,而我並不了解(個人的力量與認知畢竟有限。也許我對軟件十分清楚,但是在硬件上提出了超脫物理規律的不可能方案,而我想當然地認為可以)。所以,需要進行可行性分析。甚至通過論證會議,進行具體的論證,從而確定方案是否可行,是否需要調整。
  6. 最後,根據前面這些簡要的方案與意見,通過自己與下屬不斷溝通,從而形成一份具體的系統設計的方案-系統設計任務書。作為接下來實施系統建設工作的具體依據。

可行性分析

這裏着重提一下這個部分,這個部分充分體現了系統的業務特性。

從考試角度說,這個部分雖然不是一個章節,但是每次考試都會考幾分。

從實際角度來說,可能一些感興趣的比較清楚,阿里的p7-p8,騰訊的t3-t4,晉陞都是有一定困難的。晉陞條件中有一條,需要對業務有足夠的了解。就如同國外對程序員的分級看法,也對程序員的產品思維有着非常重的要求。大廠要求程序員對業務有一定的了解,一方面是確定經歷真實性,另一方面就是觀察面試者的業務思維,產品思維。可能p7,只是要求根據業務場景,實現業務方案,並適當根據技術,提出對業務的看法(如調整業務,提出新的業務特性等)。到了p8-p9就是希望你能在一個大的目標下,提出自己的產品理念。在項目經理的產品模型或產品想法的基礎上,站在技術角度,給出新的產品想法,去完善產品模型,提高產品的層次。

以上看法,不保證完全正確,畢竟不同人的解讀是不同的。但是還是值得參考一下的(日後我晉陞p8,p9,考慮回來更新一下)。

那麼可行性分析可以帶來產品思維嘛?

當然,,,不能。

但是,可行性分析,絕對是一個絕佳的入門機會。可以幫助開發人員,從一個新的角度去看待自己開發的系統。並且這個入門的門檻很低,開發人員可以很好的理解,入門。後續的道路,就需要各位自己走了。畢竟完事開頭難,開始解決了,後面走下去就行了。當然後續有更多積累了,我會分享我對產品,業務的看法。

至於其它部分,都比較簡單,就不在此深入談論了。

學習必要性

考試的必要性,我只說一句,分值可觀。

現實的意義,我總結了三點比較重要的:

  • 提升高度。通過這部分的學習,可以為你晉陞公司技術高層埋下一顆種子。起碼你和Boss扯皮的時候,不再是只有技術名詞了。
  • 改善眼界。通過這部分的學習,可以改變你對信息系統的純技術看法。有時候,人的改變,只是需要一個開始。
  • 心中有數。通過這部分的學習,可以令你明白現有項目的一些基本商業特性。起碼你知道你的項目能走多遠,是不是該跑路了。囧

這個章節就這些內容,如果有什麼不清楚的,可以@我。如果有正在準備考試或已經通過考試的,或有交流需求的,可以@我,加群。

希望這篇博客對大家有所幫助。

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

文件上傳

文件上傳是 Web 開發常見需求,上傳文件需要用到文件輸入框,如果給文件輸入框添加一個 multiple 屬性則可以一次選擇多個文件(不支持的瀏覽器會自動忽略這個屬性)

<input multiple type="file">

點擊這個輸入框就可以打開瀏覽文件對話框選擇文件了,一般一個輸入框上傳一個文件就行,要上傳多個文件也可以用多個輸入框來處理,這樣做是為了兼容那些不支持 multiple 屬性的瀏覽器,同時用戶一般也不會選擇多個文件

基本上傳方式

當把文件輸入框放入表單中,提交表單的時候即可將選中的文件一起提交上傳到服務器,需要注意的是由於提交的表單中包含文件,因此要修改一下錶單元素的 enctype 屬性為 multipart/form-data

<form action="#" enctype="multipart/form-data" method="post">
  <input name="file" type="file">
  <button type="submit">Upload</button>
</form>

這樣上傳方式是傳統的同步上傳,上傳的文件如果很大,往往需要等待很久,上傳完成后頁面還會重新加載,並且必須等待上傳完成后才能繼續操作

早期的瀏覽器並不支持異步上傳,不過可以使用 iframe 來模擬,在頁面中隱藏一個 <iframe> 元素,指定一個 name 值,同時將 <form> 元素的 target 屬性值指定為 <iframe> 元素的 name 屬性的值,將兩者關聯起來

<form action="#" enctype="multipart/form-data" method="post" target="upload-frame">
  <input name="file" type="file">
  <button type="submit">Upload</button>
</form>
<iframe id="upload-frame" name="upload-frame" src="about:blank" style="display: none;"></iframe>

這樣在提交表單上傳的時候,頁面就不會重新加載了,取而代之的是 iframe 重新加載了,不過 iframe 原本就是隱藏的,即使重新加載也不會感知到

訪問文件

File API 提供了訪問文件的能力,通過輸入框的 files 屬性訪問,這會得到一個 FileList,這是一個集合,如果只選擇了一個文件,那麼集合中的第一個元素就是這個文件

var input = document.querySelector('input[type="file"]')
var file = input.files[0]

console.log(file.name) // 文件名稱
console.log(file.size) // 文件大小
console.log(file.type) // 文件類型

支持 File API 的瀏覽器可以參考

Ajax 上傳

由於可以通過 File API 直接訪問文件內容,再結合 XMLHttpRequest 對象直接將文件上傳,將其作為參數傳給 XMLHttpRequest 對象的 send 方法即可

var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.send(file)

不過一些原因不建議直接這樣傳遞文件,而是使用 FormData 對象來包裝需要上傳的文件,FormData 是一個構造函數,使用的時候先 new 一個實例,然後通過實例的 append 方法向其中添加數據,直接把需要上傳的文件添加進去

var formData = new FormData()
formData.append('file', file, file.name) // 第 3 個參數是文件名稱
formData.append('username', 'Mary') // 還可以添加額外的參數

甚至也可以直接把表單元素作為實例化參數,這樣整個表單中的數據就全部包含進去了

var formData = new FormData(document.querySelector('form'))

數據準備好后,就是上傳了,同樣是作為參數傳給 XMLHttpRequest 對象的 send 方法

var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.send(formData)

監測上傳進度

XMLHttpRequest 對象還提供了一個 progress 事件,基於這個事件可以知道上傳進度如何

var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.upload.onprogress = progressHandler // 這個函數接下來定義

上傳的 progress 事件由 xhr.upload 對象觸發,在事件處理程序中使用這個事件對象的 loaded(已上傳字節數) 和 total(總數) 屬性來計算上傳的進度

function progressHandler(e) {
  var percent = Math.round((e.loaded / e.total) * 100)
}

上面的計算會得到一個表示完成百分比的数字,不過這兩個值也不一定總會有,保險一點先判斷一下事件對象的 lengthComputable 屬性

function progressHandler(e) {
  if (e.lengthComputable) {
    var percent = Math.round((e.loaded / e.total) * 100)
  }
}

支持 Ajax 上傳的瀏覽器可以參考

分割上傳

使用文件對象的 slice 方法可以分割文件,給該方法傳遞兩個參數,一個起始位置和一個結束位置,這會返回一個新的 Blob 對象,包含原文件從起始位置到結束位置的那一部分(文件 File 對象其實也是 Blob 對象,這可以通過 file instanceof Blob 確定,Blob 是 File 的父類)

var blob = file.slice(0, 1024) // 文件從字節位置 0 到字節位置 1024 那 1KB

將文件分割成幾個 Blob 對象分別上傳就能實現將大文件分割上傳

function upload(file) {
  let formData = new FormData()
  formData.append('file', file)
  let xhr = new XMLHttpRequest()
  xhr.open('POST', '/upload/url', true)
  xhr.send(formData)
}

var blob = file.slice(0, 1024)
upload(blob) // 上傳第一部分

var blob2 = file.slice(1024, 2048)
upload(blob2) // 上傳第二部分

// 上傳剩餘部分

通常用一個循環來處理更方便

var pos = 0 // 起始位置
var size = 1024 // 塊的大小

while (pos < file.size) {
  let blob = file.slice(pos, pos + size) // 結束位置 = 起始位置 + 塊大小

  upload(blob)
  pos += size // 下次從結束位置開始繼續分割
}

服務器接收到分塊文件進行重新組裝的代碼就不在這裏展示了

使用這種方式上傳文件會一次性發送多個 HTTP 請求,那麼如何處理這種多個請求同時發送的情況呢?方法有很多,可以用 Promise 來處理,讓每次上傳都返回一個 promise 對象,然後用 Promise.all 方法來合併處理,Promise.all 方法接受一個數組作為參數,因此將每次上傳返回的 promise 對象放在一個數組中

var promises = []

while (pos < file.size) {
  let blob = file.slice(pos, pos + size)

  promises.push(upload(blob)) // upload 應該返回一個 promise
  pos += size
}

同時改造一下 upload 函數使其返回一個 promise

function upload(file) {
  return new Promise((resolve, reject) => {
    let formData = new FormData()
    formData.append('file', file)
    let xhr = new XMLHttpRequest()
    xhr.open('POST', '/upload/url', true)
    xhr.onload = () => resolve(xhr.responseText)
    xhr.onerror = () => reject(xhr.statusText)
    xhr.send(formData)
  })
}

當一切完成后

Promise.all(promises).then((response) => {
  console.log('Upload success!')
}).catch((err) => {
  console.log(err)
})

支持文件分割的瀏覽器可以參考

判斷一下文件對象是否有該方法就能知道瀏覽器是否支持該方法,對於早期的部分版本瀏覽器需要加上對應的瀏覽器廠商前綴

var slice = file.slice || file.webkitSlice || file.mozSlice

if (slice) {
  let blob = slice.call(file, 0, 1024) // call
  upload(blob)
} else {
  upload(file) // 不支持分割就只能直接上傳整個文件了,或者提示文件過大
}

拖拽上傳

通過拖拽 API 可以實現拖拽文件上傳,默認情況下,拖拽一個文件到瀏覽器中,瀏覽器會嘗試打開這個文件,要使用拖拽功能需要阻止這個默認行為

document.addEventListener('dragover', function(e) {
  e.preventDefault()
  e.stopPropagation()
})

任意指定一個元素來作為釋放拖拽的區域,給一個元素綁定 drop 事件

var element = document.querySelector('label')
element.addEventListener('drop', function(e) {
  e.preventDefault()
  e.stopPropagation()

  // ...
})

通過該事件對象的 dataTransfer 屬性獲取文件,然後上傳即可

var file = e.dataTransfer.files[0]
upload(file) // upload 函數前面已經定義

選擇類型

給文件輸入框添加 accept 屬性即可指定選擇文件的類型,比如要選擇 png 格式的圖片,則指定其值為 image/png,如果要允許選擇所有類型的圖片,就是 image/*

<input accept="image/*" type="file">

添加 capture 屬性可以調用設備機能,比如 capture="camera" 可以調用相機拍照,不過這並不是一個標準屬性,不同設備實現方式也不一樣,需要注意

<input accept="image/*" capture="camera" type="file">

經測 iOS 設備添加該屬性后只能拍照而不能從相冊選擇文件了,所以判斷一下

if (iOS) { // iOS 用 navigator.userAgent 判斷
  input.removeAttribute('capture')
}

不支持的瀏覽器會自動忽略這些屬性

自定義樣式

文件輸入框在各個瀏覽器中呈現的樣子都不大相同,而且給 input 定義樣式也不是那麼方便,如果有需要應用自定義樣式,有一個技巧,可以用一個 label 關聯到這個文件輸入框,當點擊這個 label 元素的時候就會觸發文件輸入框的點擊,打開瀏覽文件的對話框,相當於點擊了文件輸入框一樣的效果

<label for="file-input"></label>
<input id="file-input" style="clip: rect(0,0,0,0); position: absolute;" type="file">

這時就可以將原本的文件輸入框隱藏了,然後給 label 元素任意地應用樣式,畢竟要給 label 元素應用樣式比 input 方便得多

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

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

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

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

※專營大陸快遞台灣服務

※台灣快遞大陸的貨運公司有哪些呢?

【集合系列】- 深入淺出分析LinkedHashMap

一、摘要

在集合系列的第一章,咱們了解到,Map的實現類有HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties等等。

本文主要從數據結構和算法層面,探討LinkedHashMap的實現。

二、簡介

LinkedHashMap可以認為是HashMap+LinkedList,它既使用HashMap操作數據結構,又使用LinkedList維護插入元素的先後順序,內部採用雙向鏈表(doubly-linked list)的形式將所有元素( entry )連接起來。

LinkedHashMap繼承了HashMap,允許放入key為null的元素,也允許插入value為null的元素。從名字上可以看出該容器是LinkedList和HashMap的混合體,也就是說它同時滿足HashMap和LinkedList的某些特性,可將LinkedHashMap看作採用Linked list增強的HashMap。

打開 LinkedHashMap 源碼,可以看到主要三個核心屬性:

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>{

    /**雙向鏈表的頭節點*/
    transient LinkedHashMap.Entry<K,V> head;

    /**雙向鏈表的尾節點*/
    transient LinkedHashMap.Entry<K,V> tail;

    /**
      * 1、如果accessOrder為true的話,則會把訪問過的元素放在鏈表後面,放置順序是訪問的順序
      * 2、如果accessOrder為false的話,則按插入順序來遍歷
      */
      final boolean accessOrder;
}

LinkedHashMap 在初始化階段,默認按插入順序來遍歷

public LinkedHashMap() {
        super();
        accessOrder = false;
}

LinkedHashMap 採用的 Hash 算法和 HashMap 相同,不同的是,它重新定義了數組中保存的元素Entry,該Entry除了保存當前對象的引用外,還保存了其上一個元素before和下一個元素after的引用,從而在哈希表的基礎上又構成了雙向鏈接列表。

源碼如下:

static class Entry<K,V> extends HashMap.Node<K,V> {
        //before指的是鏈表前驅節點,after指的是鏈表后驅節點
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
}

可以直觀的看出,雙向鏈表頭部插入的數據為鏈表的入口,迭代器遍歷方向是從鏈表的頭部開始到鏈表尾部結束。

除了可以保迭代歷順序,這種結構還有一個好處:迭代LinkedHashMap時不需要像HashMap那樣遍歷整個table,而只需要直接遍歷header指向的雙向鏈表即可,也就是說LinkedHashMap的迭代時間就只跟entry的個數相關,而跟table的大小無關。

三、常用方法介紹

3.1、get方法

get方法根據指定的key值返回對應的value。該方法跟HashMap.get()方法的流程幾乎完全一樣,默認按照插入順序遍歷。

public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
}

如果accessOrder為true的話,會把訪問過的元素放在鏈表後面,放置順序是訪問的順序

void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
}

測試用例:

public static void main(String[] args) {
        //accessOrder默認為false
        Map<String, String> accessOrderFalse = new LinkedHashMap<>();
        accessOrderFalse.put("1","1");
        accessOrderFalse.put("2","2");
        accessOrderFalse.put("3","3");
        accessOrderFalse.put("4","4");
        System.out.println("acessOrderFalse:"+accessOrderFalse.toString());
        
        //accessOrder設置為true
        Map<String, String> accessOrderTrue = new LinkedHashMap<>(16, 0.75f, true);
        accessOrderTrue.put("1","1");
        accessOrderTrue.put("2","2");
        accessOrderTrue.put("3","3");
        accessOrderTrue.put("4","4");
        accessOrderTrue.get("2");//獲取鍵2
        accessOrderTrue.get("3");//獲取鍵3
        System.out.println("accessOrderTrue:"+accessOrderTrue.toString());
}

輸出結果:

acessOrderFalse:{1=1, 2=2, 3=3, 4=4}
accessOrderTrue:{1=1, 4=4, 2=2, 3=3}

3.2、put方法

put(K key, V value)方法是將指定的key, value對添加到map里。該方法首先會調用HashMap的插入方法,同樣對map做一次查找,看是否包含該元素,如果已經包含則直接返回,查找過程類似於get()方法;如果沒有找到,將元素插入集合。

/**HashMap 中實現*/
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
}

LinkedHashMap 中覆寫的方法

// LinkedHashMap 中覆寫
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    // 將 Entry 接在雙向鏈表的尾部
    linkNodeLast(p);
    return p;
}

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    // last 為 null,表明鏈表還未建立
    if (last == null)
        head = p;
    else {
        // 將新節點 p 接在鏈表尾部
        p.before = last;
        last.after = p;
    }
}

3.3、remove方法

remove(Object key)的作用是刪除key值對應的entry,該方法實現邏輯主要以HashMap為主,首先找到key值對應的entry,然後刪除該entry(修改鏈表的相應引用),查找過程跟get()方法類似,最後會調用 LinkedHashMap 中覆寫的方法,將其刪除!

/**HashMap 中實現*/
public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof TreeNode) {...}
            else {
                // 遍歷單鏈表,尋找要刪除的節點,並賦值給 node 變量
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            if (node instanceof TreeNode) {...}
            // 將要刪除的節點從單鏈表中移除
            else if (node == p)
                tab[index] = node.next;
            else
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);    // 調用刪除回調方法進行後續操作
            return node;
        }
    }
    return null;
}

LinkedHashMap 中覆寫的 afterNodeRemoval 方法

void afterNodeRemoval(Node<K,V> e) { // unlink
    LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    // 將 p 節點的前驅后後繼引用置空
    p.before = p.after = null;
    // b 為 null,表明 p 是頭節點
    if (b == null)
        head = a;
    else
        b.after = a;
    // a 為 null,表明 p 是尾節點
    if (a == null)
        tail = b;
    else
        a.before = b;
}

四、總結

LinkedHashMap 繼承自 HashMap,所有大部分功能特性基本相同,二者唯一的區別是 LinkedHashMap 在HashMap的基礎上,採用雙向鏈表(doubly-linked list)的形式將所有 entry 連接起來,這樣是為保證元素的迭代順序跟插入順序相同。

主體部分跟HashMap完全一樣,多了header指向雙向鏈表的頭部,tail指向雙向鏈表的尾部,默認雙向鏈表的迭代順序就是entry的插入順序。

五、參考

1、JDK1.7&JDK1.8 源碼
2、

作者:炸雞可樂
出處:

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

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

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※台灣海運大陸貨務運送流程

※兩岸物流進出口一站式服務

【從今天開始好好學數據結構03】鏈表

目錄

今天我們來聊聊“鏈表(Linked list)”這個數據結構。

在我們上一章中棧與隊列底層都是採用順序存儲的這種方式的,而今天要聊的鏈表則是採用鏈式存儲,鏈表可以說是繼數組之後第二種使用得最廣泛的通用數據結構了,可見其重要性!

相比,鏈表是一種稍微複雜一點的數據結構。對於初學者來說,掌握起來也要比數組稍難一些。這兩個非常基礎、非常常用的數據結構,我們常常將會放到一塊兒來比較。所以我們先來看,這兩者有什麼區別。數組需要一塊連續的內存空間來存儲,對內存的要求比較高。而鏈表恰恰相反,它並不需要一塊連續的內存空間,它通過“指針”將一組零散的內存塊串聯起來使用,鏈表結構五花八門,今天我重點給你介紹三種最常見的鏈表結構,它們分別是:單鏈表、雙向鏈表和循環鏈表。

鏈表通過指針將一組零散的內存塊串聯在一起。其中,我們把內存塊稱為鏈表的“結點”。為了將所有的結點串起來,每個鏈表的結點除了存儲數據之外,還需要記錄鏈上的下一個結點的地址。而尾結點特殊的地方是:指針不是指向下一個結點,而是指向一個空地址NULL,表示這是鏈表上最後一個結點。

@

單鏈表

package demo2;

//一個節點
public class Node {

    //節點內容
    int data;
    //下一個節點
    Node next;
    
    public Node(int data) {
        this.data=data;
    }
    
    //為節點追回節點
    public Node append(Node node) {
        //當前節點
        Node currentNode = this;
        //循環向後找
        while(true) {
            //取出下一個節點
            Node nextNode = currentNode.next;
            //如果下一個節點為null,當前節點已經是最後一個節點
            if(nextNode==null) {
                break;
            }
            //賦給當前節點
            currentNode = nextNode;
        }
        //把需要追回的節點追加為找到的當前節點的下一個節點
        currentNode.next=node;
        return this;
    }
    
    //插入一個節點做為當前節點的下一個節點
    public void after(Node node) {
        //取出下一個節點,作為下下一個節點
        Node nextNext = next;
        //把新節點作為當前節點的下一個節點
        this.next=node;
        //把下下一個節點設置為新節點的下一個節點
        node.next=nextNext;
    }
    
    //显示所有節點信息
    public void show() {
        Node currentNode = this;
        while(true) {
            System.out.print(currentNode.data+" ");
            //取出下一個節點
            currentNode=currentNode.next;
            //如果是最後一個節點
            if(currentNode==null) {
                break;
            }
        }
        System.out.println();
    }
    
    //刪除下一個節點
    public void removeNext() {
        //取出下下一個節點
        Node newNext = next.next;
        //把下下一個節點設置為當前節點的下一個節點。
        this.next=newNext;
    }
    
    //獲取下一個節點
    public Node next() {
        return this.next;
    }
    
    //獲取節點中的數據
    public int getData() {
        return this.data;
    }
    
    //當前節點是否是最後一個節點
    public boolean isLast() {
        return next==null;
    }
    
}

單鏈表測試類

package demo2.test;

import demo2.Node;

public class TestNode {
    
    public static void main(String[] args) {
        //創建節點
        Node n1 = new Node(1);
        Node n2 = new Node(2);
        Node n3 = new Node(3);
        //追加節點
        n1.append(n2).append(n3).append(new Node(4));
        //取出下一個節點的數據
//      System.out.println(n1.next().next().next().getData());
        //判斷節點是否為最後一個節點
//      System.out.println(n1.isLast());
//      System.out.println(n1.next().next().next().isLast());
        
        //显示所有節點內容
        n1.show();
        //刪除一個節點
//      n1.next().removeNext();
        //显示所有節點內容
//      n1.show();
        //插入一個新節點
        Node node = new Node(5);
        n1.next().after(node);
        n1.show();
    }

}

鏈表要想隨機訪問第k個元素,就沒有數組那麼高效了。因為鏈表中的數據並非連續存儲的,所以無法像數組那樣,根據首地址和下標,通過尋址公式就能直接計算出對應的內存地址,而是需要根據指針一個結點一個結點地依次遍歷,直到找到相應的結點。

你可以把鏈表想象成一個隊伍,隊伍中的每個人都只知道自己後面的人是誰,所以當我們希望知道排在第k位的人是誰的時候,我們就需要從第一個人開始,一個一個地往下數。所以,鏈表隨機訪問的性能沒有數組好,需要O(n)的時間複雜度。

雙向鏈表

接下來我們再來看一個稍微複雜的,在實際的軟件開發中,也更加常用的鏈表結構:雙向鏈表。單向鏈表只有一個方向,結點只有一個後繼指針next指向後面的結點。而雙向鏈表,顧名思義,它支持兩個方向,每個結點不止有一個後繼指針next指向後面的結點,還有一個前驅指針prev指向前面的結點。

public class DoubleNode {
    //上一個節點
    DoubleNode pre=this;
    //下一個節點
    DoubleNode next=this;
    //節點數據
    int data;
    
    public DoubleNode(int data) {
        this.data=data;
    }
    
    //增節點
    public void after(DoubleNode node) {
        //原來的下一個節點
        DoubleNode nextNext = next;
        //把新節點做為當前節點的下一個節點
        this.next=node;
        //把當前節點做新節點的前一個節點
        node.pre=this;
        //讓原來的下一個節點作新節點的下一個節點
        node.next=nextNext;
        //讓原來的下一個節點的上一個節點為新節點
        nextNext.pre=node;
    }
    
    //下一個節點
    public DoubleNode next() {
        return this.next;
    }
    
    //上一個節點
    public DoubleNode pre() {
        return this.pre;
    }
    
    //獲取數據
    public int getData() {
        return this.data;
    }
    
}

雙向鏈表測試

import demo2.DoubleNode;

public class TestDoubleNode {

    public static void main(String[] args) {
        //創建節點
        DoubleNode n1 = new DoubleNode(1);
        DoubleNode n2 = new DoubleNode(2);
        DoubleNode n3 = new DoubleNode(3);
        //追加節點
        n1.after(n2);
        n2.after(n3);
        //查看上一個,自己,下一個節點的內容
        System.out.println(n2.pre().getData());
        System.out.println(n2.getData());
        System.out.println(n2.next().getData());
        System.out.println(n3.next().getData());
        System.out.println(n1.pre().getData());
        
    }
    
}

單鏈表VS雙向鏈表

如果我們希望在鏈表的某個指定結點前面插入一個結點或者刪除操作,雙向鏈表比單鏈表有很大的優勢。雙向鏈表可以在O(1)時間複雜度搞定,而單向鏈表需要O(n)的時間複雜度,除了插入、刪除操作有優勢之外,對於一個有序鏈表,雙向鏈表的按值查詢的效率也要比單鏈表高一些。因為,我們可以記錄上次查找的位置p,每次查詢時,根據要查找的值與p的大小關係,決定是往前還是往後查找,所以平均只需要查找一半的數據。

現在,你有沒有覺得雙向鏈表要比單鏈表更加高效呢?這就是為什麼在實際的軟件開發中,雙向鏈表儘管比較費內存,但還是比單鏈表的應用更加廣泛的原因。如果你熟悉Java語言,你肯定用過LinkedHashMap這個容器。如果你深入研究LinkedHashMap的實現原理,就會發現其中就用到了雙向鏈表這種數據結構。實際上,這裡有一個更加重要的知識點需要你掌握,那就是用空間換時間的設計思想。當內存空間充足的時候,如果我們更加追求代碼的執行速度,我們就可以選擇空間複雜度相對較高、但時間複雜度相對很低的算法或者數據結構。相反,如果內存比較緊缺,比如代碼跑在手機或者單片機上,這個時候,就要反過來用時間換空間的設計思路。

循環鏈表

循環鏈表是一種特殊的單鏈表。實際上,循環鏈表也很簡單。它跟單鏈表唯一的區別就在尾結點。我們知道,單鏈表的尾結點指針指向空地址,表示這就是最後的結點了。而循環鏈表的尾結點指針是指向鏈表的頭結點。和單鏈表相比,循環鏈表的優點是從鏈尾到鏈頭比較方便。當要處理的數據具有環型結構特點時,就特別適合採用循環鏈表。比如著名的約瑟夫問題。儘管用單鏈表也可以實現,但是用循環鏈表實現的話,代碼就會簡潔很多。

package demo2;

//一個節點
public class LoopNode {

    //節點內容
    int data;
    //下一個節點
    LoopNode next=this;
    
    public LoopNode(int data) {
        this.data=data;
    }
    
    //插入一個節點做為當前節點的下一個節點
    public void after(LoopNode node) {
        //取出下一個節點,作為下下一個節點
        LoopNode nextNext = next;
        //把新節點作為當前節點的下一個節點
        this.next=node;
        //把下下一個節點設置為新節點的下一個節點
        node.next=nextNext;
    }
    
    //刪除下一個節點
    public void removeNext() {
        //取出下下一個節點
        LoopNode newNext = next.next;
        //把下下一個節點設置為當前節點的下一個節點。
        this.next=newNext;
    }
    
    //獲取下一個節點
    public LoopNode next() {
        return this.next;
    }
    
    //獲取節點中的數據
    public int getData() {
        return this.data;
    }
    
}

循環鏈表測試

package demo2.test;

import demo2.LoopNode;

public class TestLoopNode {

    public static void main(String[] args) {
        LoopNode n1 = new LoopNode(1);
        LoopNode n2 = new LoopNode(2);
        LoopNode n3 = new LoopNode(3);
        LoopNode n4 = new LoopNode(4);
        //增加節點
        n1.after(n2);
        n2.after(n3);
        n3.after(n4);
        System.out.println(n1.next().getData());
        System.out.println(n2.next().getData());
        System.out.println(n3.next().getData());
        System.out.println(n4.next().getData());
    }

}

最後,我們再對比一下數組,數組的缺點是大小固定,一經聲明就要佔用整塊連續內存空間。如果聲明的數組過大,系統可能沒有足夠的連續內存空間分配給它,導致“內存不足(out of memory)”。如果聲明的數組過小,則可能出現不夠用的情況。這時只能再申請一個更大的內存空間,把原數組拷貝進去,非常費時。鏈表本身沒有大小的限制,天然地支持動態擴容,我覺得這也是它與數組最大的區別。

你可能會說,我們Java中的ArrayList容器,也可以支持動態擴容啊?事實上當我們往支持動態擴容的數組中插入一個數據時,如果數組中沒有空閑空間了,就會申請一個更大的空間,將數據拷貝過去,而數據拷貝的操作是非常耗時的。

我舉一個稍微極端的例子。如果我們用ArrayList存儲了了1GB大小的數據,這個時候已經沒有空閑空間了,當我們再插入數據的時候,ArrayList會申請一個1.5GB大小的存儲空間,並且把原來那1GB的數據拷貝到新申請的空間上。聽起來是不是就很耗時?

除此之外,如果你的代碼對內存的使用非常苛刻,那數組就更適合你。因為鏈表中的每個結點都需要消耗額外的存儲空間去存儲一份指向下一個結點的指針,所以內存消耗會翻倍。而且,對鏈表進行頻繁的插入、刪除操作,還會導致頻繁的內存申請和釋放,容易造成內存碎片,如果是Java語言,就有可能會導致頻繁的GC(Garbage Collection,垃圾回收)。

所以,在我們實際的開發中,針對不同類型的項目,要根據具體情況,權衡究竟是選擇數組還是鏈表!

如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回復!

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔…

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

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

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

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

※小三通海運與一般國際貿易有何不同?

※小三通快遞通關作業有哪些?

不止面試—jvm類加載面試題詳解

面試題

帶着問題學習是最高效的,本次我們將嘗試回答以下問題:

  1. 什麼是類的加載?
  2. 哪些情況會觸發類的加載?
  3. 講一下JVM加載一個類的過程
  4. 什麼時候會為變量分配內存?
  5. JVM的類加載機制是什麼?
  6. 雙親委派機制可以打破嗎?為什麼

答案放在文章的最後,來不及看原理也可以直接跳到最後直接看答案。

深入原理

類的生命周期

類的生命周期相信大家已經耳熟能詳,就像下面這樣:

不過這東西總是背了就忘,忘了又背,就像馬什麼梅一樣,對吧?

其實理解之後,基本上就不會再忘了。

加載

加載主要做三件事:

  1. 找到類文件(通過類的全限定名來獲取定義此類的二進制字節流)
  2. 放入方法區(將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構)
  3. 開個入口(生成一個代表此類的java.lang.Class對象,作為訪問方法區這些數據結構的入口)

總的來講,這一步就是通過類加載器把類讀入內存。需要注意的是,第三步雖然生成了對象,但並不在堆里,而是在方法區里。

連接

連接分為三步,一般面試都比較喜歡問準備這一步。

校驗

顧名思義,檢查Class文件的字節流中包含的信息是否符合當前虛擬機的要求。

準備

這一步中將為靜態變量和靜態常量分配內存,並賦值。

需要注意的是,靜態變量只會給默認值。比如下面這個:

public static int value = 123;

此時賦給value的值是0,不是123。

靜態常量(static final修飾的)則會直接賦值。比如下面這個:

public static final int value = 123;

此時賦給value的值是123。

解析

解析階段就是jvm將常量池的符號引用替換為直接引用。

恩……啥是常量池?啥是符號引用?啥是直接引用?

常量池我們放在jvm內存結構里說。先來說下什麼是符號引用和直接引用。

符號引用和直接引用

假設有一個Worker類,包含了一個Car類的run()方法,像下面這樣:

class Worker{
    ......
    public void gotoWork(){
        car.run(); //這段代碼在Worker類中的二進製表示為符號引用        
    }
    ......
}

在解析階段之前,Worker類並不知道car.run()這個方法內存的什麼地方,於是只能用一個字符串來表示這個方法。該字符串包含了足夠的信息,比如類的信息,方法名,方法參數等,以供實際使用時可以找到相應的位置。

這個字符串就被稱為符號引用。

在解析階段,jvm根據字符串的內容找到內存區域中相應的地址,然後把符號引用替換成直接指向目標的指針、句柄、偏移量等,這之後就可以直接使用了。

這些直接指向目標的指針、句柄、偏移量就被成為直接引用。

初始化

類的初始化的主要工作是為靜態變量賦程序設定的初值。

還記得上面的靜態變量嗎:

public static int value = 123;

經過這一步,value的值終於是123了。

總結如下圖:

類初始化的條件

Java虛擬機規範中嚴格規定了有且只有五種情況必須對類進行初始化:

  1. 使用new字節碼指令創建類的實例,或者使用getstatic、putstatic讀取或設置一個靜態字段的值(放入常量池中的常量除外),或者調用一個靜態方法的時候,對應類必須進行過初始化。
  2. 通過java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則要首先進行初始化。
  3. 當初始化一個類的時候,如果發現其父類沒有進行過初始化,則首先觸發父類初始化。
  4. 當虛擬機啟動時,用戶需要指定一個主類(包含main()方法的類),虛擬機會首先初始化這個類。
  5. 使用jdk1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,並且這個方法句柄對應的類沒有進行初始化,則需要先觸發其初始化。

除了以上這五種情況,其他任何情況都不會觸發類的初始化。

比如下面這幾種情況就不會觸發類初始化:

  1. 通過子類調用父類的靜態字段。此時父類符合情況一,而子類不符合任何情況。所以只有父類被初始化。
  2. 通過數組來引用類,不會觸發類的初始化。因為new的是數組,而不是類。
  3. 調用類的靜態常量不會觸發類的初始化,因為靜態常量在編譯階段就會被存入調用類的常量池中,不會引用到定義常量的類。

類加載機制

類加載器

在上面咱們曾經說到,加載階段需要“通過一個類的全限定名來獲取描述此類的二進制字節流”。這件事情就是類加載器在做。

jvm自帶三種類加載器,分別是:

  1. 啟動類加載器。
  2. 擴展類加載器。
  3. 應用程序類加載器

他們的繼承關係如下圖:

雙親委派

雙親委派機制工作過程如下:

  1. 當前ClassLoader首先從自己已經加載的類中查詢是否此類已經加載,如果已經加載則直接返回原來已經加載的類。每個類加載器都有自己的加載緩存,當一個類被加載了以後就會放入緩存,等下次加載的時候就可以直接返回了。

  2.  當前classLoader的緩存中沒有找到被加載的類的時候,委託父類加載器去加載,父類加載器採用同樣的策略,首先查看自己的緩存,然後委託父類的父類去加載,一直到bootstrp ClassLoader.

  3.  當所有的父類加載器都沒有加載的時候,再由當前的類加載器加載,並將其放入它自己的緩存中,以便下次有加載請求的時候直接返回。

為啥要搞這麼複雜?自己處理不好嗎?

雙親委派的優點如下:

  1. 避免重複加載。當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。
  2. 為了安全。避免核心類,比如String被替換。

打破雙親委派

“雙親委派”機制只是Java推薦的機制,並不是強制的機制。

比如JDBC就打破了雙親委派機制。它通過Thread.currentThread().getContextClassLoader()得到線程上下文加載器來加載Driver實現類,從而打破了雙親委派機制。

至於為什麼,以後再說吧。

答案

現在,我們可以回答文章開頭提出的問題了。盡量在理解的基礎上回答,不需要死記硬背。

  1. 什麼是類的加載?

    JVM把通過類名獲得類的二進制流之後,把類放入方法區,並創建入口對象的過程被稱為類的加載。經過加載,類就被放到內存里了。

  2. 哪些情況會觸發類的初始化?

    類在5種情況下會被初始化:

    第一,假如這個類是入口類,他會被初始化。

    第二,使用new創建對象,或者調用類的靜態變量,類會被初始化。不過靜態常量不算。

    第三,通過反射獲取類,類會被初始化

    第四,如果子類被初始化,他的父類也會被初始化。

    第五,使用jdk1.7的動態語言支持時,調用到靜態句柄,也會被初始化。

  3. 講一下JVM加載一個類的過程

    同問題1。不過這裏也可以問下面試官是不是想問類的生命周期。如果是問類的生命周期,可以回答有”加載、連接、初始化、使用、卸載“五個階段,連接又可以分為”校驗、準備、解析“三個階段。

  4. 什麼時候會為變量分配內存?

    在準備階段為靜態變量分配內存。

  5. JVM的類加載機制是什麼?

    雙親委派機制,類加載器會先讓自己的父類來加載,父類無法加載的話,才會自己來加載。

  6. 雙親委派機制可以打破嗎?為什麼

    可以打破,比如JDBC使用線程上下文加載器打破了雙親委派機制。原因是JDBC只提供了接口,並沒有提供實現。這個問題可以再看下引用文獻的內容。

引用文獻

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

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

※小三通物流營運型態?

※快速運回,大陸空運推薦?