電腦組裝之硬件選擇,電腦組裝知識

作者:凌逆戰

地址:

電腦主要配件:主板、CPU、顯卡、显示器、電源、機箱、內存條、硬盤。CPU、顯卡、內存條、硬盤是插在主板上的,電源用來給主板上的部件進行供電,CPU,主板,顯卡,內存條、硬盤、電源這幾個放在機箱中就構成了我們通常所說的主機。摩爾定律,硬件的性能每隔18~20個月就會提升一倍。

主板

主板主要接口:CPU插槽;內存條插槽;SATA硬盤接口;M.2接口(用於M.2接口的固態硬盤);背板I/O接口;PCLE-X16插槽(用來接顯卡);PCLE-X1插槽(用來接聲卡、網卡等);供電插槽

主板性能指標:芯片組、供電項數目(供電項越多,平分下來每一路的電流就會減小了,減少主板發熱 超頻時更加穩定)、做工、擴展能力(是否支持USB3.0或者USB3.1,是否支持超頻)。

對於主板最重要的就是其穩定能力,其次才是芯片組

因為芯片組有Intel和AMD,所以主板可以根據芯片組分類,英特爾和AMD這兩家公司每次發布新的CPU的時候,都會先各大主板廠家發布適配自己CPU的芯片組,意思就是說:“我這邊CPU已經做出來了,你們主板廠得按照我的這個標準,做出來的主板才能用我的這個CPU”

如何區分intel主板和AMD主板:

  • intel主板會有亮閃閃的金屬扣,且主板的安裝位置是一個一個針腳,均勻分佈,因為intel處理器是觸點式接口
  • AMD主板沒有金屬扣,且主板的安裝位置是一個一個小凹槽,因為AMD處理器是針腳式接口

Intel芯片組主板

芯片組是主板的核心芯片,選對芯片組,主板和CPU才能兼容。

型號字母

目前英特爾的芯片組有4個等級,H、B、Z、X分別適配不同的用戶,下面只是一個大概的規律,不能以偏概全,比如:H370>B360

H:中低端、入門,不支持超頻。價格在300元左右。

B:中低端、主流,這類主板幾乎不能超頻(有特殊幾個主板能通過人工破解來進行超頻),而且僅支持最高DDR4 2666GHz的內存條頻率,所以如果你買的CPU是不帶K的如 i3-8100;i5-8500這類不支持超頻的CPU,或者本身沒有超頻需求,B系主板是非常划算和有性價比的選擇。價格在500元左右。

Z:高端,這類主板天生支持超頻功能(需要CPU支持,英特爾CPU後面帶K的都支持超頻,如i3-8350K;I5-8600K;I7-8700K),同時芯片組支持更多原生的擴展卡槽的接口,而且這類主板通常也支持非常高的內存頻率,價格也會貴一些,一般在1000元以上。

X:特殊、頂級,這類主板有2066個針腳,也就是只能用英特爾後面帶X的CPU。如i9-7960X、i7-7800X。當然這類主板和CPU價格都高的嚇人,市場很小眾。

H、B、Z芯片組的主板/CPU接口都是 LGA 1151的,X系列的主板/CPU接口是 LGA 2066

字母後面的数字

100系列支持英特爾6代CPU,如B150芯片組支持i3-6100、i5-6500、i7 6700等。(intel的100系列主板刷新bios是可以上7代cpu的)

200系列支持英特爾6、7代CPU,如:B250芯片組支持i3-7100、i5-7500、i7-7700k等。

300系列支持英特爾8、9代CPU,如:B360芯片組支持i3-8100、i5-8500、i7-8700k、i7-9700k、i9-9900k等。

至於後面個位和十位數上的数字,越高代表主板在同等級中越高端。

AMD芯片組主板

芯片組是主板的核心芯片,選對芯片組,主板和CPU才能兼容。

型號字母

AMD平台的主板芯片組,有A、B、X三個檔次

A:低端,這類芯片組的主板不能超頻,普通用戶使用也足夠了。價格大多在500元以下。

B:中端,這類芯片組的主板也能超頻,高性能的接口數量要少一些,價格在500元左右,是普通裝機用戶的性價比選擇。

X:高端,芯片組支持更多可以擴展的接口,可以超頻。

AMD芯片組的主板CPU接口大都是AM4的,像高端的X399CPU接口是TR4的。

字母後面的数字

300系列支持 一代銳龍,需要更新BIOS才能支持二代銳龍

400系列支持 一代、二代銳龍

銳龍二代或一代的用戶買300 400系列芯片組的主板都可以

主板尺寸

常見的主板板型分為:E-ATX(加強型)、ATX(標準型)、M-ATX(緊湊型)、MINI-ITX(迷你型)。

 

常見的主板板型對比

E-ATX型主板:高性能主板,一般會有8跟內存插槽,芯片組也多為X等級的,也就是說適用於英特爾以X結尾的CPU,如i9-7960X。價格非常高

ATX型主板:用的最多的主板,俗稱“大板”。這類主板由於體型稍大,需要搭配中塔以上大機箱,做工用料較好,擴展接口比較豐富,不過價位略貴

M-ATX型主板:比ATX要短了一些,俗稱“小板”,也叫緊湊型主板,其結構為方形。M-ATX主板主要用於小機箱電腦中,如今裝機非常主流的主板板型。

MINI-ITX型主板:俗稱迷你型主板,結構是方形,主板尺寸小,適合一些迷你小機箱電腦,這種主板通常作為小巧的HTPC電腦,因此這類主板大部分都內置了Wifi模塊。

主板小板和大板的區別是什麼?主板小板和大板優缺點分析:

1、同芯片的主板大板和小板性能沒有任何提升,主板尺寸並不會影響到電腦的整體性能;

2、大板相比小板的擴展接口也許會更加豐富,比如大板標配4個內存插槽,而小板有可能是2個或則4個,大板的PCI-E顯卡插槽擁有兩個,而小板只有一個。此外,PCI插槽方面,大板也更豐富一些,USB方面,大板通常也多出兩個;

3、大主板做工用料相對充足,不過價位上也會更加貴一些;

4、大板需要中塔以上大機箱支持,而小主板既能夠兼容大機箱,還能夠兼容一些迷你機箱,價位上也相對可能實惠一些;

  對於買主板是選擇小板還是大板,關於這個問題,關鍵看個人需求。通常來說,組裝入門級電腦,選用小巧的M-ATX機箱,那麼肯定需要選用M-ATX主板或者迷你主板,對於注重散熱並定位中高端或者高端,選用大機箱的裝機用戶,建議搭配ATX大主板,其擴展、做工更加有優勢,一般用戶,建議選擇M-ATX小主板,價格相對實惠一些,擴展接口也基本足夠使用了,屬於大眾型主板。

關於品牌

華碩:三大板廠之一,主板的BIOS界面做的超一流,簡單易懂,好用,而且功能很多。高端主板非常強,中低端主板與其他牌子拉不開差距。ROG玩家國度系列主板用料豪華,超頻能力非常強,是最高端主板的象徵。

技嘉:三大板廠之一,技嘉的主板一般比較耐用。AROUS系列主板定位高端,類ROG玩家國度。

微星:三大板廠之一,微星的板子跟華碩技嘉都差不多,BIOS比華碩稍差一些,價格也低一點點。

華擎:華擎是華碩的子公司,是華碩面對中低端市場與二三線品牌競爭的品牌,在低價位中用料算是不錯的,性價比高,價格比三大廠要低一兩百塊,被稱為價格屠夫。

映泰:映泰也是台灣的主板大廠,主板相對來說稍微低端一些,穩定性可以,價格也很低。

七彩虹:七彩虹的品牌宣傳做的好,線下電腦城賣的好,但線上銷量有點凄慘,不建議購買。

高端板子最好選擇華碩的,低中端主板最好選擇技嘉、華擎的。

CPU

CPU有幾個重要的參數:架構、主頻(頻率)、核心數、線程數、緩存、最大睿頻。

關鍵參數

主頻:CPU的工作頻率,我們可以直接理解為運算速度,主頻越高,計算能力越強,現在CPU主頻都以GHz為單位。

最大睿頻:睿頻是指當啟動一個運行程序后,處理器會自動加速到合適的頻率,而原來的運行速度會提升 10%~20% 以保證程序流暢運行的一種技術。

核心數與線程數:核心數就相當於有多少只手,線程數就相當於你能同時干幾樣活,核心數*線程數=馬路寬度,頻率等於車速

架構:架構對性能的影響最大。一般來說,每一代CPU的架構都是一樣的,

緩存:位於CPU與內存之間的臨時存儲器,它的容量比內存小但交換速度快。有一級緩存和二級緩存,一級緩存的容量基本在4KB~64KB,二級緩存的容量則分為128KB/256KB/512KB/1MB/2MB等,一級緩衝容量相差不大,二級緩存容量則是提高CPU內存的關鍵。

如果玩遊戲,就需要選主頻高的,簡單粗暴的進行計算。如果做圖形渲染,建議多核心多線程的

CPU有兩家廠商在做Intel和AMD,處理器有兩種接口接口類型,AMD的CPU是針腳式,Intel的CPU是觸點式,由於intelCPU採用了觸點式所以intel主板就必須有針腳,而AMD則是與之相反,CPU使用針腳,主板採用觸點式。雖然方式不同,但是從性能上完全沒有區別,主要AMD的處理器針腳特別怕彎,彎了或者斷了處理器基本就要涼涼了。而intel處理器就不會這樣,不過intel主板的針腳也極易容易彎折。現在AMD也推出了自己的觸點式處理器——線程撕裂者,以後AMD處理器也會朝着觸點式發展。

Intel CPU:穩定性強,功耗低(發熱量少,常規散熱系統),兼容性強

AMD CPU:爆發力強,多線程(適合畫圖)、功耗高(發熱量大,散熱系統需要規劃)

Intel CPU和AMD CPU在相同性能下,價格往往AMD會更加實惠,換句話說相同價位的CPU,AMD的性能普遍更強,說雖然這麼說,但是還是要看個人需求,畢竟這兩款CPU的針對性不同,當然對於AMD的高端CPU散熱量和Intel差不多,不用很擔心AMD處理器散熱問題。

Intel CPU後面的数字

  以i5 3450U為例

第一位数字 是i 後面的数字代表的是家族名稱。

第二位数字 “3”代表它是第幾代架構的產品,後面的三位数字是CPU型號,一般越高越好,

第三位数字 “4”代表的是處理器等級。

第四位和第五位数字 “50”代表處理器的頻率。如果後面還有@ 3.0GHz  @ 3.0GHz,前一個是主頻后一個是睿頻

Intel CPU後面的字母

K代表不鎖倍頻的處理器;也就是可以超頻;

M代表標準電壓cpu,可以拆卸的;

U代表低電壓節能的,可以拆卸的;

X代表高性能,可拆卸的;

Q代表至高性能級別;

H是高電壓的,是焊接的,不能拆卸;

Y代表超低電壓的,除了省電,沒別的優點的了,是不能拆卸的;

F代表不帶內置顯卡(核顯)

兩個字母的,屬於上面這些特性的組合

相同性能的CPU,Intel的CPU價格要比AMD貴,且Intel的CPU功耗比AMD處理器功耗低。如果手頭預算不多,建議選擇AMD的CPU,而手頭寬裕的話,建議選擇Intel的CPU。相比而言AMD 的CPU性價比較高。

AMD CPU後面的数字

  以R5 1500X為例

第一位数字 是R 後面的数字代表的是家族名稱。

第二位数字 “1”代表它是第幾代架構的產品,

後面的三位数字是CPU型號,一般越高越好,

X代表支持完整的XFR技術(帶X的超頻能力更強,而且超頻是全自動智能的)。

CPU天梯圖

想要看更詳細的CPU性能天梯圖請移步:

中關村CPU性能天梯圖:

注意事項:選購CPU、主板的時候要特別注意接口時候匹配

CPU的主要廠商

  • Intel:主要有賽揚(Celeron)、奔騰(Pentinum)、酷睿2(Core2)、酷睿i(Core i)這三個系列,賽揚系列以低端產品為主,奔騰系列以低端和低中端產品為主,Core2以低端和中低端產品為主,Core i系列以中端和高端產品為主,酷睿i3 i5 i7 是英特爾主流的處理器家族。總的來說,性能強弱為賽揚<奔騰<酷睿2<酷睿 i。但是不是絕對的,還要看該系列的產品是第幾代的,比如酷睿2系列的CPU不一定就絕對地比奔騰系列的CPU性能強,從天梯排行中就可以看出來,Pentinum E5700>Core2 duo E4300.
  • AMD:主要有銳龍(Ryzen,性價比)、AMD FX(推土機,高端)、APU(以四核為主)、速龍(Athlon,性價比)、閃龍(Sempron),這幾個系列,閃龍系列主要以低端產品為主,速龍以低端和低中端產品為主,弈龍主要以中端和高端產品為主,A系列(A系列集成了顯卡芯片)主要以低中端和中低端產品為主,FX系列以中高端和高端產品為主。總的來說,性能強弱為:閃龍<速龍<A系列<弈龍<FX。但是這個也不是絕對的。比如弈龍1代的很多產品性能就比不上速龍2代的產品。AMD現在只有銳龍系列的CPU值得買

顯卡 

顯卡的性能指標:架構、流處理器個數、GPU頻率、核心頻率、顯存帶寬、顯存大小(按重要程度排序)

架構:越新越好

流處理器:簡稱SP,也叫渲染管,是顯卡最重要的參數,流處理器的數量直接影響顯卡的性能,流處理器越多,顯卡畫圖能力越強,速度越快(同一代的顯卡比較流處理器才有意義)。

GPU頻率:GPU頻率越高,性能越強,發熱也越大,功耗越高;頻率低,性能弱,發熱也越小。

顯存帶寬:顯存帶寬=顯存位寬×顯存頻率,顯存位寬就相當於公路路寬,顯存頻率就像汽車的速度,所以,顯卡位寬和顯存頻率對顯卡的性能影響很大。

顯存容量:顯存能提供臨時的存儲功能。很多奸商會把顯存當做顯卡的賣點來忽悠小白,這裏要說的是,大顯存有用,但不是那麼的重要。舉個例子:顯存是停車場,如果停車場的馬路不夠寬(位寬 bit),汽車的速度也不夠快(顯存頻率MHz),那麼這個停車場的吞吐量就很小,修一個超大的停車場純屬浪費資源。

顯卡品牌:N卡和A卡

Nvida Geforce——N卡

N卡有GT、GTS、GTX這幾個系列。

GT是普通系列,而GTS和GTX是中高端系列的,如果後面跟了 Ti 表示是該型號的的加強版。

在選擇N卡來說,一般看第一位数字和第二位数字,

第一位数字 第幾代顯卡

第二位数字 表示是該系列的低端、中端還是高端。1,2,3的話表示是該系列的低端產品;4,5,6表示是中端產品,7,8,9表示是高端產品,比如GT610就是第六代的低端顯卡,還有一點就是產品低一代的話,而第二個数字高一點的話,兩個顯卡的性能基本很接近的,比如GT640和GT550,GT640是第六代的顯卡,GT550是第五代的顯卡,而GT550第二位数字是5,GT640第二位数字是4,通常來說,像這樣的情況下,兩個顯卡的性能是很接近的。

第三位数字 “0”表示常規顯卡,“5”表示升級版

N卡優缺點

  • 設計側重點:3D性能和速度,
  • 性能運算:執行效率高,但運行能力較低
  • 視頻處理:色彩稍淡,需要手動調節分辨率

ATI——A卡

ATI(已經被AMD收購),A卡;N卡有鐳、HD、R、X系列。

第一位数字 表示是第幾代,

第二位数字 表示該系列的低端、中端還是高端。4,5的話表示是低端;6,7表示是中端;8,9表示是高端。

第三位数字 “0”表示常規顯卡,“5”表示升級版

A卡優缺點

  • 設計側重點:2D平面畫質
  • 性能運算:運算能力強大,但執行效率不高,對於複雜的任務適應性不強,需要軟件的支持
  • 視頻處理:自動調節分辨率,色彩還原度高

娛樂一下

  相同性能情況下N卡價格比A卡貴,A卡的性價比相對來說要高一點。現在來說,N卡和A卡區別不大,普通用戶根本用不出來區別。但是A卡較N卡性價比高是真的。有時候我們看到一個顯卡相同的型號,但是卻買不同的價格,其主要原因是因為顯卡的核心頻率、散熱設計等不同。

關於品牌

目前較大的顯卡品牌有:華碩、技嘉、微星、藍寶石、索泰、映眾、七彩虹、影馳等。

華碩:三大顯卡廠之一,顯卡用料好、散熱猛、售後好、價格高。ROG玩家國度系列在性能、散熱、外觀等各個方面都幾乎完美,但是就是貴。

技嘉:三大顯卡廠之一,顯卡做工用料都很好。旗下有AORUS系列,定位類似ROG玩家國度,價格和玩家國度也差不多。

微星:三大顯卡廠之一,是三大廠中性價比最高的。微星紅龍系列顯卡性能和用料還是很強的,而且價格要便宜一些。

藍寶石:是AMD最忠實的合作夥伴,A卡最強廠家。

索泰:索泰和映眾都是香港柏能旗下的品牌,索泰被稱為“堆料狂魔”,顯卡性能極強,核心頻率調的很高,性能甚至超越三大廠,價格還便宜,但是高頻帶來的散熱問題較大,可能會比較吵。

映眾:映眾跟索泰的做法剛好相反,映眾在顯卡的核心頻率上沒索泰那麼激進,為了散熱和靜音將顯卡頻率調低了一點點,犧牲了小部分性能。映眾冰龍系列散熱極佳,風扇非常靜音,價格也較低。

影馳:影馳的顯卡其實也是非常不錯的,名人堂系列顯卡用料足,外觀也很好看,但是價格非常非常高,性價比很低,感覺不如買三大廠的旗艦型號。

七彩虹:七彩虹也被稱為“凄慘紅”,七彩虹在十年前的名聲並不好,但是現在七彩虹的igame系列顯卡做的還可以,價格和性能都中規中矩。如果遇上打折力度較大,也是可以選擇的。

核心顯卡、主板集成顯卡和獨立顯卡的區別

核心顯卡:處理器集成顯卡就是指集成在cpu內部的顯卡

集成顯卡:集成在主板北橋中的顯卡,目前處理器核心顯卡性能已經領先於主板集成的顯卡,並且將顯卡集成在CPU中比集成在主板中更有優勢。新的主板廠商已經不會在主板上集成顯卡了。

獨立顯卡:我們自己加的獨立的显示芯片,採用PCI接口插槽。

注意:下圖左邊NVIDIA卡的支持光追改成RTX了,不支持光追的還是GTX

想要看更詳細的顯卡性能天梯圖請移步:

中關村顯卡性能天梯圖:

內存條

  DDR內存,目前分為4代。每到新一代的內存的開發,都是為了解決上一代內存速度極限的問題,DDR2到了1066就不能再高了,而DDR3是從1066起步的,DDR3內存條基本上到了2400的頻率,就上不去了。而DDR4代,從2400起步,同之前的內存是一樣的。所以,這個其實是根本區別。再高的頻率,意味着更大的帶寬,就可以更好的適應新的CPU的要求。

DDR3 支持頻率有6種:800Mhz(DDR2和DDR3換代時候的產品)、1066Mhz(DDR2和DDR3換代時候的產品)、1333Mhz(常見)、1600Mhz(常見)、1866Mhz(高端電腦配備)、2400Mhz(高端電腦配備)

DDR4 支持頻率有6種:2133MHZ(常見)、2400MHz(常見)、2666MHz、2800MHz、3000MHz、3200MHZ、3300MHZ,

內存最高頻率 < 主板最大支持頻率

  內存是CPU與硬盤之間的傳輸的中間設備,以DDR4為例,內存條的讀寫速度相比於硬盤快很多很多很多。

內存的主要參數:頻率、時延、顆粒、容量、下行速度(按重要程度排序)

頻率和時序:我們經常看到的2133MHz、2400MHz、3200MHz就是內存條的頻率,它可以看成是內存條數據的傳輸速度,超高頻率的內存條固然能給遊戲帶來一點性能提升,但是需要更高端的主板和CPU的支持,普通用戶選擇2400MHz的也已經足夠用了,頻率是內存條最重要的參數。

  還有一個重要參數就是時延,一般用CL表示,時延就是尋址所需的時鐘周期。同一頻率的內存條時延越低越好。

現在普通的DDR4代內存條一般為頻率2400MHz,時序CL15-17左右。但是一些使用極品顆粒的超頻內存條如三星的B-die顆粒就可以輕鬆做到頻率3200MHz,而且時序只有CL12。這類極品內存條可以做到保證時序不超標的情況下,超頻上4000MHz以上。

幾乎所有的DDR4代的內存條默認的頻率只有2133MHz,所以即使你買的是高頻內存條,也需要在主板BIOS設置中打開XMP(自動超頻)或手動設置超頻后才能達到商家所給出的頻率,而且,很多主板並不支持超過2666MHz以上的頻率,所以即使你的內存條是4000MHz的神條,也會自動降頻到2666MHz使用,這個需要用戶去看主板上的說明。

內存條顆粒:顆粒就是內存條的存儲數據的東西,現在主流的顆粒生產商就是 三星、海力士、鎂光這三家。由於顆粒在生產時候會有質量參差不齊的情況,所以一些成色極品的顆粒會被挑選出來做成高端超頻內存條,而一些成色普通但合格的顆粒會被拿去做成普通內存條。至於怎麼看顆粒的好壞,我們可以從內存條的頻率和時序來做一個購買前的初步判斷。

內存條容量:容量越大,存儲的數據越多。每打開一個軟件,這些軟件的數據都會被保存到內存條中,如果內存條被塞滿,我們繼續打開其他軟件的時候,CPU就只能從速度超慢的硬盤調取數據了

單通道與雙通道

CPU與內存條之間需要交換數據,就有上行數據(內存條數據傳入CPU)和下行數據(CPU數據傳入內存條)  ,如果只有一根內存條,那麼上行數據和下行數據的傳輸就需要佔用一條通道,爭搶通道的使用權限和帶寬。如果有兩根內存條組雙通道,那麼上行數據和下行數據的傳輸就可以分別在兩根內存條之間分別傳輸,互不影響。

  一般來說,兩根相同規格的內存條插在主板對應的位置上就可以組成雙通道了(看B站UP主說的)。

雙通道能夠給計算機帶來5%左右的性能提升,雙通道最大受益者是集顯/核顯,因為CPU要同時負責程序數據和显示數據的處理,需要的數據流量更大,所以雙通道帶來的雙倍帶寬才能滿足這麼大的數據流量的需求。英特核顯可以提高20%性能,amd核顯可以提升30%-50%。

內存條選擇注意事項:

  1. 對於普通用戶,選擇內存條的時候,一般來說4g就可以了,最多8g,16g的內存條看需求,如果自己也不知道自己需要多少容量的,可以先買一根8GB的使用,發現不夠可以再買一根8GB組雙通道。
  2. 如果有兩根或多根不同頻率的內存條同時使用,會以頻率最低的來統一頻率。比如有一根2133MHz、一根2400MHz、一根3200MHz的內存條同時使用的話,所有內存條都是按照2133MHz來使用。所以一般組雙通道建議購買兩根相同規格的內存條插在主板對應的位置上。所以如果是升級內存條的用戶,一定要看看已有的是多少頻率的,不要盲目購買高頻內存條。
  3. 有4根內存插槽的主板,你把內存條插滿它依然是雙通道的
  4. 關於內存條PCB板的層數,層數越多,電路板內部的電路走線層數增加,每一層的電路走線就不會那麼擁擠了,這樣會有更好的電氣性能,使得超頻更加穩定。
  5. AMD銳龍平台用戶可以選擇芝奇、英睿達等牌子的內存條,可以兼容。
  6. 關於品牌:各個主流品牌之間內存條價格差距不大,普通用戶建議在 芝奇、英睿達、海盜船、影馳、金士頓、威剛、阿斯加特、十銓、宇瞻等這幾個品牌中對比挑選一款頻率、時序、價格都不錯的內存條,然後認準官方自營旗艦店、官方旗艦店,因為內存條官方都會終身質保。

硬盤

  我們都知道木桶的短板效應,就電腦的速度來說,CPU緩存、顯卡緩存、甚至是內存條都是以至少十倍百倍以上的速度差距遠遠的超過了所有的机械硬盤的,這就一定會給電腦的性能帶來一些瓶頸。固態硬盤的存儲速度是机械硬盤的5-10倍左右,多少能彌補一些硬盤在速度上的短板,所以如果你覺得你的舊電腦有些卡,反應慢,換一塊固態硬盤絕對能給你的舊電腦帶來新的生命。

机械硬盤

  机械硬盤是利用磁性來記錄信息數據,原理類似與小時候聽歌用的磁帶,如果我們需要找到某個數據,磁盤就會轉動到記錄這個信息的部位,然後由磁頭感應磁性來讀取數據。

注意事項:硬盤建議選擇西部數據和希捷的,我個人傾向於西部數據,相比而言穩定一點,硬盤的轉速有7200轉和5400轉的,最好選擇7200轉的,西部數據硬盤有三種:黑盤,藍盤和綠盤,在買硬盤的時候注意千萬不要買綠盤,最好選擇藍盤或者黑盤(價格有點貴)。好像現在又出了紅盤,具體不是很清楚。

固態硬盤

  固態硬盤(SSD)是利用電流來記錄信息數據,原理類似與MP3,如果我們要找到某個數據,直接去找到存放數據的區域,就可以直接讀取了。

  

 

固態硬盤的性能參數:固態硬盤的顆粒、主控、緩存、3D NAND堆棧技術、接口\總線\協議、(按重要程度排序)

顆粒

閃存顆粒是固態硬盤用來存儲數據的東西,分為SLC、MLC、TLC三種,是挑選固態最重要的參數。

SLC:每個存儲單元存儲1bit的數據。這種存儲方式穩定性最強,讀寫速度很快,而且不會出錯,壽命長,但是成本高。

MLC:每個存儲單元存儲2bit的數據,速度會比SLC慢一點,穩定性較強,壽命還好,但是價格貴。

TLC:每個存儲單元存儲3bit的數據,效率低、速度慢、還容易出錯、壽命相對於上面兩種短一些,,說雖這麼說,但是現在使用最多的還是TLC,重度使用5年是沒有問題的,且價格相對便宜。

現在世界上能自主生產顆粒的廠家有:intel、三星、閃迪、東芝、鎂光(英睿達)、海力士。所有正規的固態硬盤使用的都是這幾家的檢驗合格的原廠顆粒。如使用自家顆粒的intel、三星、閃迪、鎂光(英睿達)、東芝等;還有雖然自己不會生產顆粒,但是使用從原廠購買顆粒的浦科特、海盜船、建興等。

主控

  主控很重要,就相當於顆粒的管理員。比較好的主控品牌有:馬牌(Marvell)、SandForce、三星、intel、東芝等

緩存

  和前面一樣,緩存就是CPU和固態硬盤之間的緩衝區,方便傳輸,緩存越大越好

總線

  總線有SATA總線、PCI-E總線(PCIE×1、PCIE×2、PCIE×4、PCIE×8、PCIE×16 数字越大,速度越快)目前固態硬盤用的都是PCIEx2和PCIEx4的總線,顯卡是PCIEx16總線

傳輸協議

  傳輸協議有:IDE(老的机械硬盤)、AHCI(机械硬盤)、SATA(目前主流硬盤協議)和NVMe(高速度低延遲)。越後面傳輸速度越快,擁有NVME協議的固態硬盤速度比無NVME協議的固態硬盤快很多。

接口

目前固態硬盤接口有:SATA、mSATA、M.2、PCI-E

SATA接口:屬於老式的接口,分SATA 3GB和SATA6 GB,我們的机械硬盤使用的也是這種接口。這種接口速度稍慢,延遲稍高,最大速度不會超過600MB/s,

mSATA接口:這種接口不多,一般用在筆記本上

M.2接口有兩種:B key和M key。

B key M.2接口:又稱“SOCKET 2”,豁口在左邊,比較老,支持PCI-Ex2總線和SATA總線,速度比較慢

M key M.2接口:又稱“SOCKET 3”,豁口在右邊,目前主流,支持PCI-Ex4總線,速度比較快

B&M型接口的固態硬盤兼容性好,兩種M.2的插槽都能用,性能和B key差不多。

PCI-E接口:一般這個插槽是給顯卡用的,但也可以用它來插固態硬盤,這個長得跟顯卡一樣的固態硬盤也是 PCI-E ×4的接口,支持PCI-E ×4的總線。但是現在的主板大多數是沒有PCIE-4的插槽的。所以一般都是接在顯卡的插槽里使用的。

4KB隨機讀寫:固態硬盤雖然順序讀寫速度超快,但是那是只有在讀寫一整個大文件(如一部電影)時才能體會到它的優勢,而影響我們日常使用的是硬盤的4KB 隨機讀寫速度

排名

固態硬盤推薦:

  • 120GB裝個系統,裝幾個日常軟件、裝一個大型遊戲就差不多滿了,建議首選240GB容量。
  • 新配電腦的用戶建議選擇M.2接口的硬盤,因為這是未來的主流。老電腦升級的用戶要檢查主板是否有M.2插槽。
  • 4k讀寫性能才是影響日常體驗的重中之重,購買前需要重點關注。 

電源

  電源的選擇是最容易被我們忽略,又非常重要的一項,我們的核心部件如:CPU、固態硬盤、內存條都是由額定電壓的。如果如果供電不穩定忽高忽低,很容易造成元器件的損壞與老化。理想情況下,供電12V就是12V,但是市面上幾乎沒有一款真正能做到0偏差,intel CPU對電壓的要求是偏差不超過$\pm5%$,電源的好壞和品牌沒有絕對的關係,即便是品牌好的牌子如海盜船,也依然有差的電源,如:VS系列。

功率的選擇

直流輸出中+12V的,是給CPU和顯卡供電的電壓和電流。最好買單路12V的,因為雙路12V的會限制CPU和顯卡的使用功率,單路的直接給他們,誰需求多誰就取得多。買電源的時候,+12V的直流電源功率大於顯卡和CPU的額定功率就不會出問題。

80Plus認證

80Plus認證是一個電源轉換率標準,轉化率越高,也就越省電。就是說,如果你的電源額定功率是500W的白牌電源(滿載轉化率為80%),那麼當你的電源滿載時,你家電錶實際用電為:500÷80%=625W

注意:80plus認證僅僅是轉化率標準,不能直接反應電源的好壞,所以這個指標在購買時僅作為參考就行了。

電源用料:日系電容>台系電容>國產電容(大概排名,不絕對)

模組選擇

非模組:非模組電源價格便宜,但是全部線材固定,多餘線頭不能拆卸,理線稍微麻煩一些。

半模組:比非模組貴一點,重要線材固定,部分線材可拆卸,可以去除多餘線材。

全模組:價格最貴,全部線材可拆卸,美觀,方便理線。

電源尺寸

一般來說選擇ATX的電源就夠了,SFX尺寸的電源一般用在MINI型機箱上面。

關於品牌

振華(super flower):台企,振華電源有戰碟、金蝶、金蝶GX、Leadex這幾個系列,建議從金蝶開始買起,

海韻:台企,海韻的電源清一色日系電容,用料很不錯。

酷冷至尊:台企,全日系電容,全模組,性價比高,穩定性很好。

美商海盜船:美企,海盜船的電源一定要買高端的,也就是800元起步,低端的VS系列價格高,配置低,臭名在外;

EVGA:美企,EVGA的電源500元以下的都不建議買,性價比太低了。高端的如G、P、T系列都是全日系電容,主流的LLC半橋+ DC-DC 結構。質保7-10年,性價比稍低。

訊景(XFX):訊景低端的XT2電源(價格在300左右)不建議買,因為雖然價格不低,但是電源方案很落後,高端點的XTR、XTS系列價格在500元以上,方案主流,可以購買,但性價比稍低。

台達:台企,台達是世界最大的電源生產商,主要是做服務器電源和高端專業的電源解決方案,在零售商做的不是很多。有幾款在售的電源如NX系列和VX靜音王系列價格便宜,全日系電容用料很不錯,很耐用。

航嘉:深圳企業,國內老牌電源廠商,型號多,價格從100-1000都有。航嘉的電源是國內做工非常好的,低端電源也比國內雜牌好的很多,安全有保障,300元以內還是推薦買的,超過300的還是建議去買酷媽和振華。

鑫谷:東莞企業,國內老牌電源廠商,比較推薦的是GP 白金版,使用日系和台系電容,而且是白金牌認證,電壓波形很穩,價格也很便宜,性價比很高。

注意事項:

  1. 不要不捨得為電源花錢,多花100塊錢有時就能拯救你好幾塊兒机械硬盤。
  2. 功率選擇不要看廠家標多少瓦,主要看+12V,把顯卡和CPU滿載功耗加起來還有幾十瓦余量就夠了。
  3. 80PLUS金銀銅牌認證僅代表電源轉化效率,高轉化率更加省電,但是不能完全評判電源的好壞。
  4. 建議購買使用 LLC半橋+ DC-DC方案,使用日系、台系電容的電源。
  5. 盡量購買大品牌的電源,雖然貴一點點,安全有保障,不至於翻車燒壞主板。

:航嘉電源功率計算器 輸入你要買的主板、CPU、顯卡型號 它就會自動計算出你電源的待機功率和最大功耗,然後推薦出適合你的電源型號

散熱器

  如果電腦散熱不良,CPU的溫度過高,CPU為了保護自己不被燒壞,首先會自動降低頻率來減少發熱,這會導致電腦性能下降,其次降頻之後如果溫度還是過高,CPU就會自動觸發電腦死機來保護自己。所以保證良好的散熱還是很有必要的。

散熱器的工作原理

傳熱底座與CPU緊密接觸,通過導熱裝置,將CPU產生的熱量傳導至散熱鰭片,然後由風扇吹走鰭片上的熱量。

導熱裝置有三種

①純銅(純鋁)導熱:這種方式導熱效率比較低,但是結構簡單,價格便宜,很多原裝散熱器都是這種方式。

②導熱銅管:這是現在最常用的方式,它的銅管是中空的,裏面注有一種導熱液,溫度升高時銅管底部的液體蒸發吸收熱量,將熱量傳遞給散熱鰭片后溫度降低凝結成液體,流回銅管底部,如此循環,導熱效率很高。所以現在的大部分散熱器都是這種方式。

③水冷散熱:嚴格來說它並不是水,是一種導熱率很高的液體。它是通過水將CPU的熱量帶走,然後高溫的水在通過曲曲折折的冷排(結構跟家裡的暖氣片差不多)的時候被風扇吹走熱量,變為涼水再次循環。

影響散熱效果的因素(風冷)

熱量傳遞的效率

熱量的傳遞效率是散熱的關鍵,影響熱量傳遞效率的因素有以下4點。

熱管的數量以及粗細

熱管根數越多越好,一般2根湊合,4根夠用,6根及以上就是高端散熱器了;熱管越粗越好(大部分為6mm,也有8mm的)。

傳熱底座的工藝:

①熱管直觸:這種方案的底座非常普遍,一般的百元及以下散熱器都是這種的。這種方案為了保證與CPU接觸面的平整,會把銅管拍扁、打磨,這使得本來就很薄的銅管更薄了,時間久了就會出現凹凸不平的現象,影響導熱效率。正規大廠都會把銅管打磨的非常平整,這樣就與CPU的接觸面積更大,導熱效率高。一些山寨廠家的銅管凹凸不平,導致有些銅管工作時根本接觸不到CPU,所以再多銅管也只是花架子。

②銅底焊接(鏡面打磨):這種方案的底座價格稍微貴一些,因為把傳熱底座直接做成鏡面,接觸面積更高,導熱效果也更好。所以中高端的風冷散熱器都是用這種方案。

③均熱板:這是一種很少很少見到的方案,原理類似於熱管,也是通過液體遇熱蒸發,然後遇冷液化來傳遞熱量,這種方案導熱均勻效率高,但是成本很高,所以很少見。

導熱硅脂

  由於製造工藝問題,散熱器底座與CPU之間不可能有完全平整的接觸面(即使你看上去很平整,但是在放大鏡下是能看到凹凸不平的),所以就需要塗一層導熱係數較高的硅脂來填補這些凹凸不平的地方幫助導熱。硅脂的導熱係數比銅低很多,所以只要均勻的塗上薄薄的一層就好了,如果塗太厚,反倒影響散熱了。一般好的硅脂的導熱係數在5-8之間,也有非常昂貴的導熱係數在10-15。

散熱鰭片與熱管交接處的工藝

熱管是穿插在鰭片之間的,要把熱量傳遞到鰭片上,所以他們交接地方的處理工藝也會影響導熱性,目前的處理工藝有兩種

①迴流焊:顧名思義就是將兩者焊接到一起。這種方案成本較高,但是導熱性能好,而且很牢固,不容易出現鰭片鬆動的現象。

②穿fin:也叫“穿片”工藝。顧名思義就是在鰭片上開孔,然後藉助外力將導熱銅管插進鰭片里。這種工藝成本較低,雖然簡單,但是要想做好卻並不容易,因為要考慮接觸不良、鰭片鬆動等問題(如果你隨手一撥,鰭片就在熱管上滑動,導熱效果可想而知了)。

鰭片與空氣的接觸面積大小

鰭片承擔著散熱的重任,它的任務是將熱管送來的熱量散發到空氣中,所以鰭片必須盡可能多的與空氣進行接觸,有些廠家會細心的設計一些凸點來盡可能大的增加鰭片的表面積。

風量

風量表示每分鐘風扇能送出風的總體積,一般用CFM表示。風量越大,散熱也就越好。關於風扇的參數還有:轉速、風壓、扇恭弘=叶 恭弘尺寸、噪音等。現在大多數風扇都支持PWM智能調速,我們需要關注的也就是風量、噪音等。

風冷散熱器的類型

風冷散熱器有三種類型:被動式散熱(無風扇設計)、塔式、下壓式。

1、被動式散熱:它其實就是一個無風扇版的散熱器,全靠空氣流通帶走鰭片上的熱量。

  • 優點:完全沒有噪音。
  • 缺點:散熱性能差,適合發熱量特別小的平台(我們的手機幾乎都是被動式散熱,甚至不如被動式散熱)。

2、下壓式散熱:這種散熱器風扇是朝下吹的,所以在兼顧CPU散熱的同時也可以惠顧到主板和內存條散熱。但是散熱效果稍差,而且會擾亂機箱風道,所以適合發熱小的平台,同時由於體積小不佔空間,所以是小機箱的福音。

3、塔式散熱:這種散熱器高高聳立如高塔一般,故名塔式散熱。這種散熱器單向吹風,不會擾亂風道,而且鰭片和風扇可以做的比較大,因此散熱性能最好。但是不能兼顧主板和內存散熱,因此需要機箱上的風扇輔助才行。

關於品牌

貓頭鷹:來自奧地利的品牌,貓頭鷹主要以靜音風扇久負盛名。貓頭鷹的散熱器最主要的特點是靜音,而且設計絕對沒有光污染,堅持使用迴流焊工藝,但是價格昂貴。如果只是追求靜音,可以買稍微便宜的型號,但如果還想要一個不錯的散熱效果,那就只能放血了。

大鐮刀:日本品牌,鐮刀的散熱器做工優秀,設計很人性化,扭曲式銅管設計可以避開內存條,所以不擋內存條。

利民:中國台灣品牌,利民的散熱器主要還是面對高端的超頻用戶,

九州風神:國內品牌,九州風神最出名的就是玄冰400和大塔霜了。但是99元的玄冰400不推薦,因為特別難安裝。229元的6熱管的大塔霜性價比非常高,但是可能會出現擋一根內存的情況。

酷冷至尊:台灣品牌,129元的T400,4熱管,安裝方便,比玄冰400要好,適合絕大多數人的裝機需求。

ID-COOLING:深圳品牌,產品設計很棒,而且性價比高。主力產品是下壓式散熱器,ITX機箱用戶可以選擇199元的IS-VC45,採用均熱板設計,厚度僅為45mm;159元的is-60,採用6熱管設計,也是一款性價比很高的產品。

超頻三:深圳品牌,超頻三的散熱器性價比很高,最出名的就是紅海mini了,不到40元的售價,2熱管設計,不玩遊戲的話足夠了。東海X5和東海X6也都是很有性價比的選擇。

總結:

  1. 雖然熱管數量很重要,但是吸熱底座做工必須要足夠好才行,如果做工不平整,再多的熱管都沒用,因此散熱器拼的更多是做工。
  2. 普通的i3、i5後面不帶K的CPU和銳龍不帶X的CPU,如i3 8100、i5 8500、銳龍1500 1600等選擇4熱管的散熱器就足夠了,比如99元的大鐮刀STB120或雙風扇的STB120 plus都能輕鬆應對。
  3. i3 i5 i7後面帶K的CPU和銳龍帶X的CPU,如i3-8350k、i5 8600k、i7 8700K、銳龍1600X等選擇6熱管就可以了,如大鐮刀的千石船、九州風神的大塔霜、利民的TS140P等。
  4. 選擇散熱器的時候一定要注意:①機箱限高和散熱器的大小;②擋不擋內存條;③支不支持你的主板平台(大部分散熱器都支持英特爾和AMD多平台)。
  5. 硅脂也很重要,盡量選擇導熱係數在5以上的硅脂。這東西很便宜,確是散熱很重要的一環,均勻的塗上薄薄的一層就可以了。
  6. 不要迷信水冷。風冷才是最安全,最具性價比的選擇。

显示器

不要以為显示器不重要,显示器總結影響用戶體驗呀,

主要參數:色域(能显示的色彩範圍)、色深(bit)(色彩的精細程度)、色差(色彩還原的準確性)、對比度(對比度越高,越清晰)、分辨率(表示圖像的清晰程度)、刷新率Hz(表示显示器1秒能刷新多少幀)、灰階響應時間(畫面延遲)

显示器接口

DVI:只支持視頻

DVI接口有兩個標準,25針和29針,如下圖所示。直觀來說,這兩種接口沒有區別。DVI接口傳輸的是数字信號,可以傳輸大分辨率的視頻信號。DVI連接計算機顯卡和显示器時不用發生轉換,所以信號沒有損失。

VGA:只支持視頻

針數為15的視頻接口,主要用於老式的電腦輸出。VGA輸出和傳遞的是模擬信號。大家都知道計算機顯卡產生的是数字信號,显示器使用的也是数字信號。所以使用VGA的視頻接口相當於是經歷了一個數模轉換和一次模數轉換。信號損失,显示較為模糊

HDMI接口 支持音視頻

HDMI既能傳輸高清圖形畫面信號,也能夠傳輸音頻信號,一般來說家裡會接電視,而且抗干擾強。如數碼相機的體積小,需要小的接口,可以使用micro HDMI。

 

DP接口 支持音視頻

DP即DisplayPort,是一種高清数字显示接口標準,可以連接電腦和显示器,也可以連接連接電腦和家庭影院。DP接口可以理解是HDMI的加強版,在音頻和視頻傳輸方面更加強悍。目前情況下,DP與HDMI在性能上沒有多大區別。如果你使用3840*2160分辨率(4K),HDMI由於帶寬不足,最大隻能傳送30幀,DP就沒有問題。

 

VGA和DVI互轉:模擬信號和数字信號的轉換,視頻信號損失,造成失真。最好不要這樣轉換。

DVI和HDMI互轉:都是数字信號,轉換不會發生是真。可以轉換。但是從HDMI轉換成DVI時會自動捨去音頻信號。

液晶面板

TN面板

TN面板的優點是:液晶分子偏轉速度非常快,所以灰階響應時間很短。缺點是:色域窄,色彩差,畫面色彩蒼白,可視角度很小,有條件的可以用手機屏幕對比一下老式的便宜的筆記本電腦屏幕。這種屏幕本來快被市場淘汰了,但是隨着電競的火熱,TN面板藉著刷新率高,灰階響應時間短的優點又重新回到市場,散發第二春。

IPS面板

IPS面板的優點:色彩显示效果好,可視角度大,色彩准。缺點是:容易漏光,黑色不夠純正。這類显示器由於色彩好,可視角度大,所以也是現在應用最廣的显示器面板。

VA面板

VA面板有兩種:MVA面板和PVA面板,PVA是三星改良的MVA面板。這類面板算是TN面板和IPS的折中方案,優點是色彩準確,對比度高,可視角度較大,漏光少,黑色純正。缺點是響應時間比IPS還要長。

PLS面板

PLS面板是三星獨家研製的面板,類似IPS面板。

帶魚屏

帶魚屏指屏幕比列為21:9或以上的显示器,特點是非常的長,跟帶魚一樣,所以被調侃為“帶魚屏”。這種屏幕由於較長,所以一屏能显示更多的內容。

優點:

1.多開網頁或者軟件、遊戲時,同屏能显示更多的內容,因此很適合用來工作。

2.如果能找到21:9的電影片源,看電影會非常爽。(這種片源很少)

3.支持市面主流網絡和單機遊戲。LOL和絕地求生、cs go等都能有效擴寬左右視野。

缺點:

1.由於直播、電視劇、綜藝節目等片源大多是傳統的16:9的,所以看這些內容,屏幕兩邊會有很寬的黑邊。

2.由於分辨率高,所以玩遊戲時對顯卡要求更高。

3.雖然視野變寬了,但是左右看時需要左右扭頭,累脖子,適合單機遊戲娛樂,不適合電競。

所以,對於有多開需求的用戶來說,帶魚屏或許是個能提高效率的選擇。

總結:

  1. 144hz/1ms的電競显示器對CF、CS GO、絕地求生、守望先鋒等PFS射擊遊戲來說,效果區別非常大,是那種用了之後眼睛就再也受不了60hz显示器的那種,當然前提是你的顯卡得支持這麼高的幀數。
  2. 一兩千塊錢的144hz/1ms電競显示器都是TN面板的屏幕,色彩會很差,四五千的會好一些,但色彩依然比不過2千塊的IPS。
  3. IPS屏幕色彩很好,可視角度也很大,但漏光是IPS的通病,你買到的显示器漏光嚴重與否,很大程度看運氣。而且輕微漏光日常使用看不出區別,所以不用太糾結。
  4. 普通玩家選擇IPS或者三星VA面板的显示器就夠了,不是PFS射擊類遊戲玩家不用盲目追求144hz/1ms,效果不明顯。
  5. 帶魚屏細長,能同時显示多個窗口,對於多開多任務需求的用戶來說幫助很大。遊戲時雖然能提供更寬的視野,但是需要左右扭頭,累脖子,適合單機遊戲娛樂,不適合電競。

機箱

散熱器的選擇也應該挑有品牌保障的產品,切勿貪圖便宜,品牌產品不僅有售後保障,而且在設計時為了品牌發展也會下較大的成本,而非品牌產品則更加註重於散熱器的外觀,實際的散熱性能會因為壓縮成本而大打折扣。

搭配選擇

家用:對配置要求不高,看電影、上上網之類的,選擇入門級CPU和顯卡即可

打遊戲:看遊戲此配置情況選擇顯卡,遊戲對顯卡要求特別高,同時對CPU也有一定的要求。

辦公:辦公時常需要多線程切換,因此選擇一款多線程處理能力好的CPU,

特殊領域:比如工程繪圖或者圖形渲染,深度學習等等,建議CPU和顯卡都買比較好的

參考來自中關村:

推薦兩個個自助裝機的網站和,用戶可以根據自己需要選擇配置,而且會標出價格,配完之後可以在線諮詢客服,更方面用戶進行選擇。

 

 

如果因為資金原因,那麼哪些電腦配件可以選擇二手的,哪些不能呢?

可以買二手的電腦硬件部分

處理器(CPU):處理器做工非常精細,一般都是在機箱內部,多層維護,輕易是不會損壞的,除非是人為把針腳搞壞或者是其它損壞,如果CPU正常點亮的話,買二手的話,是可以的。

內存條:內存條也是如此,做工都是非常精細的,也是在機箱內部,不是人為損壞的話,除非你用力掰扯損壞或者是其它損壞。一般情況下正常用,買二手的都是沒有問題的。

機箱:機箱對電腦的整體性能不大,只要能保證完整,可以用,那麼機箱可以考慮買二手的。

显示器:显示器新的和舊的差別不太大,显示器保證正常,買二手的對於使用者來說完全沒有問題。

不可以買二手的電腦硬件部分

顯卡(GPU):顯卡的性能和使用的程度是密切相關的,尤其是別人挖礦的顯卡,長時間負載,元器件極易容易老化,影響較大,所以顯卡最好買新的。

主板:主板上的電容 ,接口,線路較多,很容易損壞,舊的很容易老化有問題,建議買新的。

硬盤:無論是机械硬盤還是固態硬盤,它們都有一定的使用壽命與做擦寫次數的問題,建議大家硬盤要買新的。

電源:電源是維持電腦穩定工作的必備之一,二手電源會有一定的老化損耗,電壓容易不穩定,建議買新的而且要買額定功率大的電源,用來保證電腦正常工作。

散熱器:二手的很多都是沒有經過保養的,二手散熱器經過長時間的使用會有磨損,使用時有明顯的噪音,散熱對電腦散熱方面也是一個不可忽視的部分,所以建議大家散熱,也要買新的。散熱器的選擇也應該挑有品牌保障的產品,切勿貪圖便宜,品牌產品不僅有售後保障,而且在設計時為了品牌發展也會下較大的成本,而非品牌產品則更加註重於散熱器的外觀,實際的散熱性能會因為壓縮成本而大打折扣。

參考

硬件知識

嗶哩嗶哩 古宇胡:

嗶哩嗶哩 顯卡吧DIY電腦團:

B站UP主“”的專欄(強烈建議大家可以關注他,很多內容轉載他的文章,對本文幫助很大)

J.Feng 博客園博客——

組裝電腦

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

【其他文章推薦】

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

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

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

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

SpringBoot 源碼解析 (二)—– Spring Boot精髓:啟動流程源碼分析

本文從源代碼的角度來看看Spring Boot的啟動過程到底是怎麼樣的,為何以往紛繁複雜的配置到如今可以這麼簡便。

入口類

@SpringBootApplication public class HelloWorldMainApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldMainApplication.class, args);
    }
    
}

@SpringBootApplication我們上一篇文章中大概的講過了,有興趣的可以看看我第一篇關於SpringBoot的文章,本篇文章主要關注SpringApplication.run(HelloWorldMainApplication.class, args);,我們跟進去看看

// 調用靜態類,參數對應的就是HelloWorldMainApplication.class以及main方法中的args
public static ConfigurableApplicationContext run(Class<?> primarySource,String... args) {
    return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return (new SpringApplication(sources)).run(args);
}

它實際上會構造一個SpringApplication的實例,並把我們的啟動類HelloWorldMainApplication.class作為參數傳進去,然後運行它的run方法

SpringApplication構造器

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //把HelloWorldMainApplication.class設置為屬性存儲起來
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //設置應用類型是Standard還是Web
    this.webApplicationType = deduceWebApplicationType(); //設置初始化器(Initializer),最後會調用這些初始化器
    setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //設置監聽器(Listener)
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass();
}

先將HelloWorldMainApplication.class存儲在this.primarySources屬性中

設置應用類型

private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

// 相關常量
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.servlet.DispatcherServlet";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };

這裏主要是通過類加載器判斷REACTIVE相關的Class是否存在,如果不存在,則web環境即為SERVLET類型。這裏設置好web環境類型,在後面會根據類型初始化對應環境。大家還記得我們第一篇文章中引入的依賴嗎?

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

spring-boot-starter-web 的pom又會引入Tomcat和spring-webmvc,如下

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.0.5.RELEASE</version>
  <scope>compile</scope>
</dependency>

 我們來看看spring-webmvc這個jar包

很明顯spring-webmvc中存在DispatcherServlet這個類,也就是我們以前SpringMvc的核心Servlet,通過類加載能加載DispatcherServlet這個類,那麼我們的應用類型自然就是WebApplicationType.SERVLET

public enum WebApplicationType {
    NONE,
  SERVLET,
    REACTIVE;

    private WebApplicationType() {
    }
}

設置初始化器(Initializer)

//設置初始化器(Initializer),最後會調用這些初始化器
setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));

我們先來看看getSpringFactoriesInstances( ApplicationContextInitializer.class)

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

// 這裏的入參type就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // 使用Set保存names來避免重複元素
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 根據names來進行實例化
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); // 對實例進行排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

這裏面首先會根據入參type讀取所有的names(是一個String集合),然後根據這個集合來完成對應的實例化操作:

// 入參就是ApplicationContextInitializer.class
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
  String factoryClassName = factoryClass.getName();

  try {
      //從類路徑的META-INF/spring.factories中加載所有默認的自動配置類
      Enumeration<URL> urls = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
      ArrayList result = new ArrayList();

      while(urls.hasMoreElements()) {
          URL url = (URL)urls.nextElement();
          Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
          //獲取ApplicationContextInitializer.class的所有值
          String factoryClassNames = properties.getProperty(factoryClassName);
          result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
      }

      return result;
  } catch (IOException var8) {
      throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
  }
}

這個方法會嘗試從類路徑的META-INF/spring.factories處讀取相應配置文件,然後進行遍歷,讀取配置文件中Key為:org.springframework.context.ApplicationContextInitializer的value。以spring-boot-autoconfigure這個包為例,它的META-INF/spring.factories部分定義如下所示:

org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

這兩個類名會被讀取出來,然後放入到Set<String>集合中,準備開始下面的實例化操作:

// parameterTypes: 上一步得到的names集合
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set<String> names) {
    List<T> instances = new ArrayList<T>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            //確認被加載類是ApplicationContextInitializer的子類
 Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            //反射實例化對象
            T instance = (T) BeanUtils.instantiateClass(constructor, args); //加入List集合中
 instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException(
                    "Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

確認被加載的類確實是org.springframework.context.ApplicationContextInitializer的子類,然後就是得到構造器進行初始化,最後放入到實例列表中。

因此,所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,這個接口是這樣定義的:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

    void initialize(C applicationContext);

}

在Spring上下文被刷新之前進行初始化的操作。典型地比如在Web應用中,註冊Property Sources或者是激活Profiles。Property Sources比較好理解,就是配置文件。Profiles是Spring為了在不同環境下(如DEV,TEST,PRODUCTION等),加載不同的配置項而抽象出來的一個實體。

設置監聽器(Listener)

下面開始設置監聽器:

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

我們還是跟進代碼看看getSpringFactoriesInstances

// 這裏的入參type是:org.springframework.context.ApplicationListener.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<String>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

可以發現,這個加載相應的類名,然後完成實例化的過程和上面在設置初始化器時如出一轍,同樣,還是以spring-boot-autoconfigure這個包中的spring.factories為例,看看相應的Key-Value:

org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

這10個監聽器會貫穿springBoot整個生命周期。至此,對於SpringApplication實例的初始化過程就結束了。

SpringApplication.run方法

完成了SpringApplication實例化,下面開始調用run方法:

public ConfigurableApplicationContext run(String... args) {
    // 計時工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

    configureHeadlessProperty();

    // 第一步:獲取並啟動監聽器
    SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // 第二步:根據SpringApplicationRunListeners以及參數來準備環境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
        configureIgnoreBeanInfo(environment);

        // 準備Banner打印器 - 就是啟動Spring Boot的時候打印在console上的ASCII藝術字體
        Banner printedBanner = printBanner(environment);

        // 第三步:創建Spring容器
        context = createApplicationContext();

        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);

        // 第四步:Spring容器前置處理
 prepareContext(context, environment, listeners, applicationArguments,printedBanner); // 第五步:刷新容器
 refreshContext(context);
    
// 第六步:Spring容器後置處理 afterRefresh(context, applicationArguments);     // 第七步:發出結束執行的事件 listeners.started(context); // 第八步:執行Runners this.callRunners(context, applicationArguments); stopWatch.stop(); // 返回容器 return context; } catch (Throwable ex) { handleRunFailure(context, listeners, exceptionReporters, ex); throw new IllegalStateException(ex); } }
  • 第一步:獲取並啟動監聽器
  • 第二步:根據SpringApplicationRunListeners以及參數來準備環境
  • 第三步:創建Spring容器
  • 第四步:Spring容器前置處理
  • 第五步:刷新容器
  • 第六步:Spring容器後置處理
  • 第七步:發出結束執行的事件
  • 第八步:執行Runners

 下面具體分析。

第一步:獲取並啟動監聽器

獲取監聽器

跟進getRunListeners方法:

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

這裏仍然利用了getSpringFactoriesInstances方法來獲取實例,大家可以看看前面的這個方法分析,從META-INF/spring.factories中讀取Key為org.springframework.boot.SpringApplicationRunListener的Values:

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

getSpringFactoriesInstances中反射獲取實例時會觸發EventPublishingRunListener的構造函數,我們來看看EventPublishingRunListener的構造函數:

 1 public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
 2     private final SpringApplication application;
 3     private final String[] args;
 4     //廣播器
 5     private final SimpleApplicationEventMulticaster initialMulticaster;  6 
 7     public EventPublishingRunListener(SpringApplication application, String[] args) {
 8         this.application = application;
 9         this.args = args;
10         this.initialMulticaster = new SimpleApplicationEventMulticaster();
11         Iterator var3 = application.getListeners().iterator(); 12 
13         while(var3.hasNext()) {
14             ApplicationListener<?> listener = (ApplicationListener)var3.next();
15             //將上面設置到SpringApplication的十一個監聽器全部添加到SimpleApplicationEventMulticaster這個廣播器中
16             this.initialMulticaster.addApplicationListener(listener); 17         }
18 
19     }
20     //略...
21 }

我們看到EventPublishingRunListener裏面有一個廣播器,EventPublishingRunListener 的構造方法將SpringApplication的十一個監聽器全部添加到SimpleApplicationEventMulticaster這個廣播器中,我們來看看是如何添加到廣播器:

 1 public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
 2     //廣播器的父類中存放保存監聽器的內部內
 3     private final AbstractApplicationEventMulticaster.ListenerRetriever defaultRetriever = new AbstractApplicationEventMulticaster.ListenerRetriever(false);
 4 
 5     @Override
 6     public void addApplicationListener(ApplicationListener<?> listener) {
 7         synchronized (this.retrievalMutex) {
 8             Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
 9             if (singletonTarget instanceof ApplicationListener) {
10                 this.defaultRetriever.applicationListeners.remove(singletonTarget);
11             }
12             //內部類對象
13             this.defaultRetriever.applicationListeners.add(listener); 14             this.retrieverCache.clear();
15         }
16     }
17 
18     private class ListenerRetriever {
19         //保存所有的監聽器
20         public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet(); 21         public final Set<String> applicationListenerBeans = new LinkedHashSet();
22         private final boolean preFiltered;
23 
24         public ListenerRetriever(boolean preFiltered) {
25             this.preFiltered = preFiltered;
26         }
27 
28         public Collection<ApplicationListener<?>> getApplicationListeners() {
29             LinkedList<ApplicationListener<?>> allListeners = new LinkedList();
30             Iterator var2 = this.applicationListeners.iterator();
31 
32             while(var2.hasNext()) {
33                 ApplicationListener<?> listener = (ApplicationListener)var2.next();
34                 allListeners.add(listener);
35             }
36 
37             if (!this.applicationListenerBeans.isEmpty()) {
38                 BeanFactory beanFactory = AbstractApplicationEventMulticaster.this.getBeanFactory();
39                 Iterator var8 = this.applicationListenerBeans.iterator();
40 
41                 while(var8.hasNext()) {
42                     String listenerBeanName = (String)var8.next();
43 
44                     try {
45                         ApplicationListener<?> listenerx = (ApplicationListener)beanFactory.getBean(listenerBeanName, ApplicationListener.class);
46                         if (this.preFiltered || !allListeners.contains(listenerx)) {
47                             allListeners.add(listenerx);
48                         }
49                     } catch (NoSuchBeanDefinitionException var6) {
50                         ;
51                     }
52                 }
53             }
54 
55             AnnotationAwareOrderComparator.sort(allListeners);
56             return allListeners;
57         }
58     }
59     //略...
60 }

上述方法定義在SimpleApplicationEventMulticaster父類AbstractApplicationEventMulticaster中。關鍵代碼為this.defaultRetriever.applicationListeners.add(listener);,這是一個內部類,用來保存所有的監聽器。也就是在這一步,將spring.factories中的監聽器傳遞到SimpleApplicationEventMulticaster中。我們現在知道EventPublishingRunListener中有一個廣播器SimpleApplicationEventMulticaster,SimpleApplicationEventMulticaster廣播器中又存放所有的監聽器。

啟動監聽器

我們上面一步通過getRunListeners方法獲取的監聽器為EventPublishingRunListener,從名字可以看出是啟動事件發布監聽器,主要用來發布啟動事件。

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    private final SpringApplication application;
    private final String[] args;
    private final SimpleApplicationEventMulticaster initialMulticaster;

我們先來看看SpringApplicationRunListener這個接口

package org.springframework.boot;
public interface SpringApplicationRunListener {

    // 在run()方法開始執行時,該方法就立即被調用,可用於在初始化最早期時做一些工作
    void starting();
    // 當environment構建完成,ApplicationContext創建之前,該方法被調用
    void environmentPrepared(ConfigurableEnvironment environment);
    // 當ApplicationContext構建完成時,該方法被調用
    void contextPrepared(ConfigurableApplicationContext context);
    // 在ApplicationContext完成加載,但沒有被刷新前,該方法被調用
    void contextLoaded(ConfigurableApplicationContext context);
    // 在ApplicationContext刷新並啟動后,CommandLineRunners和ApplicationRunner未被調用前,該方法被調用
    void started(ConfigurableApplicationContext context);
    // 在run()方法執行完成前該方法被調用
    void running(ConfigurableApplicationContext context);
    // 當應用運行出錯時該方法被調用
    void failed(ConfigurableApplicationContext context, Throwable exception);
}

SpringApplicationRunListener接口在Spring Boot 啟動初始化的過程中各種狀態時執行,我們也可以添加自己的監聽器,在SpringBoot初始化時監聽事件執行自定義邏輯,我們先來看看SpringBoot啟動時第一個啟動事件listeners.starting():

@Override
public void starting() {
    //關鍵代碼,先創建application啟動事件`ApplicationStartingEvent`
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

這裏先創建了一個啟動事件ApplicationStartingEvent,我們繼續跟進SimpleApplicationEventMulticaster,有個核心方法:

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    //通過事件類型ApplicationStartingEvent獲取對應的監聽器
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { //獲取線程池,如果為空則同步處理。這裏線程池為空,還未沒初始化。
        Executor executor = getTaskExecutor();
        if (executor != null) {
            //異步發送事件
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            //同步發送事件
 invokeListener(listener, event);
        }
    }
}

這裡會根據事件類型ApplicationStartingEvent獲取對應的監聽器,在容器啟動之後執行響應的動作,有如下4種監聽器:

我們選擇springBoot 的日誌監聽器來進行講解,核心代碼如下:

@Override
public void onApplicationEvent(ApplicationEvent event) {
    //在springboot啟動的時候
    if (event instanceof ApplicationStartedEvent) {
    onApplicationStartedEvent((ApplicationStartedEvent) event); } //springboot的Environment環境準備完成的時候
    else if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent(
                (ApplicationEnvironmentPreparedEvent) event);
    }
    //在springboot容器的環境設置完成以後
    else if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent((ApplicationPreparedEvent) event);
    }
    //容器關閉的時候
    else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
            .getApplicationContext().getParent() == null) {
        onContextClosedEvent();
    }
    //容器啟動失敗的時候
    else if (event instanceof ApplicationFailedEvent) {
        onApplicationFailedEvent();
    }
}

因為我們的事件類型為ApplicationEvent,所以會執行onApplicationStartedEvent((ApplicationStartedEvent) event);。springBoot會在運行過程中的不同階段,發送各種事件,來執行對應監聽器的對應方法。

第二步:環境構建

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

跟進去該方法:

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    //獲取對應的ConfigurableEnvironment
    ConfigurableEnvironment environment = getOrCreateEnvironment(); //配置
 configureEnvironment(environment, applicationArguments.getSourceArgs()); //發布環境已準備事件,這是第二次發布事件
 listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    ConfigurationPropertySources.attach(environment);
    return environment;
}

來看一下getOrCreateEnvironment()方法,前面已經提到,environment已經被設置了servlet類型,所以這裏創建的是環境對象是StandardServletEnvironment。

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    if (this.webApplicationType == WebApplicationType.SERVLET) { return new StandardServletEnvironment(); } return new StandardEnvironment();
}

接下來看一下listeners.environmentPrepared(environment);,上面已經提到了,這裡是第二次發布事件。什麼事件呢?來看一下根據事件類型獲取到的監聽器:

主要來看一下ConfigFileApplicationListener,該監聽器非常核心,主要用來處理項目配置。項目中的 properties 和yml文件都是其內部類所加載。具體來看一下:

首先還是會去讀spring.factories 文件,List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();獲取的處理類有以下四種:

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

在執行完上述三個監聽器流程后,ConfigFileApplicationListener會執行該類本身的邏輯。由其內部類Loader加載項目制定路徑下的配置文件:

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";

至此,項目的變量配置已全部加載完畢,來一起看一下:

這裏一共6個配置文件,取值順序由上到下。也就是說前面的配置變量會覆蓋後面同名的配置變量。項目配置變量的時候需要注意這點。

第三步:創建容器

context = createApplicationContext();

繼續跟進該方法:

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

這裏創建容器的類型 還是根據webApplicationType進行判斷的,該類型為SERVLET類型,所以會通過反射裝載對應的字節碼,也就是AnnotationConfigServletWebServerApplicationContext

第四步:Spring容器前置處理

這一步主要是在容器刷新之前的準備動作。包含一個非常關鍵的操作:將啟動類注入容器,為後續開啟自動化配置奠定基礎。

prepareContext(context, environment, listeners, applicationArguments,printedBanner);

繼續跟進該方法:

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    //設置容器環境,包括各種變量
 context.setEnvironment(environment); //執行容器後置處理
    postProcessApplicationContext(context);
    //執行容器中的ApplicationContextInitializer(包括 spring.factories和自定義的實例)
 applyInitializers(context);   //發送容器已經準備好的事件,通知各監聽器
 listeners.contextPrepared(context); //註冊啟動參數bean,這裏將容器指定的參數封裝成bean,注入容器
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    //設置banner
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }
    //獲取我們的啟動類指定的參數,可以是多個
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    //加載我們的啟動類,將啟動類注入容器
    load(context, sources.toArray(new Object[0])); //發布容器已加載事件。
    listeners.contextLoaded(context);
}

調用初始化器

protected void applyInitializers(ConfigurableApplicationContext context) {
    // 1. 從SpringApplication類中的initializers集合獲取所有的ApplicationContextInitializer
    for (ApplicationContextInitializer initializer : getInitializers()) { // 2. 循環調用ApplicationContextInitializer中的initialize方法
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
       initializer.initialize(context);
    }
}

這裏終於用到了在創建SpringApplication實例時設置的初始化器了,依次對它們進行遍歷,並調用initialize方法。我們也可以自定義初始化器,並實現initialize方法,然後放入META-INF/spring.factories配置文件中Key為:org.springframework.context.ApplicationContextInitializer的value中,這裏我們自定義的初始化器就會被調用,是我們項目初始化的一種方式

加載啟動指定類(重點)

大家先回到文章最開始看看,在創建SpringApplication實例時,先將HelloWorldMainApplication.class存儲在this.primarySources屬性中,現在就是用到這個屬性的時候了,我們來看看getAllSources()

public Set<Object> getAllSources() {
    Set<Object> allSources = new LinkedHashSet();
    if (!CollectionUtils.isEmpty(this.primarySources)) {
        //獲取primarySources屬性,也就是之前存儲的HelloWorldMainApplication.class
        allSources.addAll(this.primarySources);
    }

    if (!CollectionUtils.isEmpty(this.sources)) {
        allSources.addAll(this.sources);
    }

    return Collections.unmodifiableSet(allSources);
}

很明顯,獲取了this.primarySources屬性,也就是我們的啟動類HelloWorldMainApplication.class,我們接着看load(context, sources.toArray(new Object[0]));

protected void load(ApplicationContext context, Object[] sources) {
    BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
    if (this.beanNameGenerator != null) {
        loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
        loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
        loader.setEnvironment(this.environment);
    }
    loader.load();
}

private int load(Class<?> source) {
    if (isGroovyPresent()
            && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
        // Any GroovyLoaders added in beans{} DSL can contribute beans here
        GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
                GroovyBeanDefinitionSource.class);
        load(loader);
    }
    if (isComponent(source)) {
        //以註解的方式,將啟動類bean信息存入beanDefinitionMap,也就是將HelloWorldMainApplication.class存入了beanDefinitionMap
        this.annotatedReader.register(source); return 1;
    }
    return 0;
}

啟動類HelloWorldMainApplication.class被加載到 beanDefinitionMap中,後續該啟動類將作為開啟自動化配置的入口,後面一篇文章我會詳細的分析,啟動類是如何加載,以及自動化配置開啟的詳細流程。

通知監聽器,容器已準備就緒

listeners.contextLoaded(context);

主還是針對一些日誌等監聽器的響應處理。

第五步:刷新容器

執行到這裏,springBoot相關的處理工作已經結束,接下的工作就交給了spring。我們來看看refreshContext(context);

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    //調用創建的容器applicationContext中的refresh()方法
    ((AbstractApplicationContext)applicationContext).refresh();
}
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        /**
         * 刷新上下文環境
         */
        prepareRefresh();

        /**
         * 初始化BeanFactory,解析XML,相當於之前的XmlBeanFactory的操作,
         */
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        /**
         * 為上下文準備BeanFactory,即對BeanFactory的各種功能進行填充,如常用的註解@Autowired @Qualifier等
         * 添加ApplicationContextAwareProcessor處理器
         * 在依賴注入忽略實現*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
         * 註冊依賴,如一個bean的屬性中含有ApplicationEventPublisher(beanFactory),則會將beanFactory的實例注入進去
         */
        prepareBeanFactory(beanFactory);

        try {
            /**
             * 提供子類覆蓋的額外處理,即子類處理自定義的BeanFactoryPostProcess
             */
            postProcessBeanFactory(beanFactory);

            /**
             * 激活各種BeanFactory處理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
             * 執行對應的postProcessBeanDefinitionRegistry方法 和  postProcessBeanFactory方法
             */
            invokeBeanFactoryPostProcessors(beanFactory);

            /**
             * 註冊攔截Bean創建的Bean處理器,即註冊BeanPostProcessor,不是BeanFactoryPostProcessor,注意兩者的區別
             * 注意,這裏僅僅是註冊,並不會執行對應的方法,將在bean的實例化時執行對應的方法
             */
            registerBeanPostProcessors(beanFactory);

            /**
             * 初始化上下文中的資源文件,如國際化文件的處理等
             */
            initMessageSource();

            /**
             * 初始化上下文事件廣播器,並放入applicatioEventMulticaster,如ApplicationEventPublisher
             */
            initApplicationEventMulticaster();

            /**
             * 給子類擴展初始化其他Bean
             */
            onRefresh();

            /**
             * 在所有bean中查找listener bean,然後註冊到廣播器中
             */
            registerListeners();

            /**
             * 設置轉換器
             * 註冊一個默認的屬性值解析器
             * 凍結所有的bean定義,說明註冊的bean定義將不能被修改或進一步的處理
             * 初始化剩餘的非惰性的bean,即初始化非延遲加載的bean
             */
            finishBeanFactoryInitialization(beanFactory);

            /**
             * 通過spring的事件發布機制發布ContextRefreshedEvent事件,以保證對應的監聽器做進一步的處理
             * 即對那種在spring啟動后需要處理的一些類,這些類實現了ApplicationListener<ContextRefreshedEvent>,
             * 這裏就是要觸發這些類的執行(執行onApplicationEvent方法)
             * 另外,spring的內置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
             * 完成初始化,通知生命周期處理器lifeCycleProcessor刷新過程,同時發出ContextRefreshEvent通知其他人
             */
            finishRefresh();
        }

        finally {
    
            resetCommonCaches();
        }
    }
}

refresh方法在spring整個源碼體系中舉足輕重,是實現 ioc 和 aop的關鍵。我之前也有文章分析過這個過程,大家可以去看看

第六步:Spring容器後置處理

protected void afterRefresh(ConfigurableApplicationContext context,
        ApplicationArguments args) {
}

擴展接口,設計模式中的模板方法,默認為空實現。如果有自定義需求,可以重寫該方法。比如打印一些啟動結束log,或者一些其它後置處理。

第七步:發出結束執行的事件

public void started(ConfigurableApplicationContext context) {
    //這裏就是獲取的EventPublishingRunListener
    Iterator var2 = this.listeners.iterator();

    while(var2.hasNext()) {
        SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
        //執行EventPublishingRunListener的started方法
 listener.started(context);
    }
}

public void started(ConfigurableApplicationContext context) {
    //創建ApplicationStartedEvent事件,並且發布事件 //我們看到是執行的ConfigurableApplicationContext這個容器的publishEvent方法,和前面的starting是不同的
    context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
}

獲取EventPublishingRunListener監聽器,並執行其started方法,並且將創建的Spring容器傳進去了,創建一個ApplicationStartedEvent事件,並執行ConfigurableApplicationContext 的publishEvent方法,也就是說這裡是在Spring容器中發布事件,並不是在SpringApplication中發布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的11個監聽器發布啟動事件。

第八步:執行Runners

我們再來看看最後一步callRunners(context, applicationArguments);

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<Object>();
    //獲取容器中所有的ApplicationRunner的Bean實例
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); //獲取容器中所有的CommandLineRunner的Bean實例
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<Object>(runners)) {
        if (runner instanceof ApplicationRunner) {
            //執行ApplicationRunner的run方法
 callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            //執行CommandLineRunner的run方法
 callRunner((CommandLineRunner) runner, args);
        }
    }
}

如果是ApplicationRunner的話,則執行如下代碼:

private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
    try {
      runner.run(args);
    } catch (Exception var4) {
        throw new IllegalStateException("Failed to execute ApplicationRunner", var4);
    }
}

如果是CommandLineRunner的話,則執行如下代碼:

private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
    try {
        runner.run(args.getSourceArgs());
    } catch (Exception var4) {
        throw new IllegalStateException("Failed to execute CommandLineRunner", var4);
    }
}

我們也可以自定義一些ApplicationRunner或者CommandLineRunner,實現其run方法,並注入到Spring容器中,在SpringBoot啟動完成后,會執行所有的runner的run方法

至此,SpringApplication大概分析了一遍,還有很多細節和核心留在下面文章中講。

 

 

 

 

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

【其他文章推薦】

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

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

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

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

美西岸漁場重新開放 拖網漁船罕見獲環團肯定

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

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

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

繼 YouBike 之後 台北市擬推電動機車租借

台北市政府擬比照 YouBike 模式,推動智慧電動車 U CAR,台北市長柯文哲昨拋出推行電動機車(U Moto)政策,預計在信義計畫區試辦。北市交通局長鍾慧諭說,目前已在全市規劃 20 多個公有停車場空間,作為電動機車充電站,且不侷限信義區,而是各區全面拓點,最快明年上路。   柯文哲說,前天跟產業界討論後得出結論,YouBike 實施之後,可進一步推動電動摩托車,至於 U CAR 則需有足夠經濟規模,將等幾千戶公共住宅成立時再處理。 鍾慧諭表示,共享運具是交通局的重要政策方向,由政府帶頭推動 U Moto,一來改善停車位供給不足問題,二來扶植產業,創造新的服務形態。   鍾慧諭說,U Moto 營運模式與 YouBike 類似,以悠遊卡登記租借,但 YouBike 是政府帶頭做,U Moto 電動摩托車則由民間企業驅動,市府角色為扶植、監理者,目前已與GOGORO、台灣城市動力公司接洽合作,由市府提供公有停車場、路邊停車格作為電池充電站,已規劃出廿多個據點,分布北市各區。

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

【其他文章推薦】

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

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

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

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

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

導入高通無線快充技術!BMW i8 充滿電僅需一小時

  隨著新一季電動方程式 (Formula E) 賽車比賽 10 月將在北京開跑,身為主要贊助廠商之一的 Qualcomm 高通也在賽前活動中發表了將應用在賽季中的新無線充電技術,透過 Halo DD 技術的強化,讓原本需要 2 小時才能完全充滿電的 BMW i8 SafetyCar 安全車,充電時間減少至 1 小時內即可完全充飽!   Qualcomm 所推出的這款 Halo DD 進階版車用無線充電系統,利用鋪設在地面上的磁感應線圈對電動車進行無線充電,不過新版的 Halo DD 透過磁感應線圈的最佳化,將原本 3.6 kW 的規格提升到 7.2 kW,雖然仍比不上汽車加油般在數分鐘內就可加滿,但在技術上已經是相當大的進步,倘若這項技術普及到一般環境中,只要在停車場、路邊停車格都鋪設了此種磁感應充電線圈,相信對電動車的普及將會更有幫助。

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

【其他文章推薦】

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

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

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

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

歐美電動車市場不同調,歐洲衝鋒美國疲軟

  美國在特斯拉領軍下,一度成為電動車的先鋒國家,不過風水輪流轉,美國電動車銷售開始牛皮化,反倒是歐洲電動車銷售吹起衝鋒號,據雷諾汽車零排放計畫( Renault Z.E.)統計,2015 年上半年歐洲電動車銷售較 2014 年同期大增 55%。   其中,英國的增幅最大,2015 年上半年銷售成長 80%,大約售出 5,000 輛電動車,不過銷售量最多的國家仍然是挪威,2015 年上半年銷售約 15,000 輛電動車,成長 47%。   而根據歐盟汽車業資料(European Automotive Industry Data),2015 年 1 到 4 月,西歐消費者總共購買了 26,808 輛電動車,24,578  輛油電混合車,總計 51,386 輛,而據美國汽車網站 HybridCars.com 統計,美國消費者在同期間購買 21,403 輛電動車,10,684 輛油電混合車。西歐電動車與油電混合車的銷售量超過美國 6 成,使得歐洲市場首度成為電動車市場的領頭羊,過去電動車市場由美國領銜,2014 年,美國電動車與油電混合車銷售超過 11.8 萬輛、歐洲市場則銷售 9.8 萬輛、中國市場銷售 74,763 輛。   美國市場之所以失去領先地位,最主要原因可能在於油價的落差,由於油價持續下跌,美國零售油價已經降至每(美制)加侖 2.6 美元,而歐洲的零售油價下降卻不如美國,例如英國油價換算為美制加侖時,相當於每加侖約 6.77 到 7 美元,油價越高,消費者轉換為電動車的意願也越高,這使得歐洲的電動車市場買氣強於美國;另一方面,美國雪佛蘭(Chevrolet)即將推 出 2016 年款 Volt,消費者等待新款,也成為消費遲滯的原因之一。   歐洲市場勝過美國的另一個可能原因,是歐洲的車款選擇較多,如三菱 Outlander 油電混合車,據 EV Obsession 網站統計,是 2015 年以來歐洲銷售最好的一輛電動與油電混合車,但是只在日本、歐洲、澳洲銷售,美國上市時間因故延後到 2015 年第四季,Outlander 油電混合車的缺席,多少對美國市場的總體銷售量有所影響。   歐洲市場的蓬勃發展讓更多車廠考慮推出電動車,如英國老牌跑車品牌奧斯頓‧馬丁(Aston Martin)就打算在 2 年內推出 Rapide 的電動車版本,售價可能在 20 萬 到 25 萬美元之間,目前 Rapide 的電動車版本已經有測試原型車,Rapide 電動車上市數年後,DBX 也將推出電動車。奧斯頓‧馬丁推出電動車的另一個考量是碳排放規範問題,為了平衡大馬力跑車的高碳排放,勢必要推出無碳排放的電動車來平衡,以免遭到包括中國在內的各國相關規範限制。各車廠為了類似原因,更積極推出電動車款,也將進一步推動歐洲電動車市場更加蓬勃發展。     本文全文授權轉載自《科技新報》─〈〉  

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

【其他文章推薦】

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

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

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

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

java基礎階段幾個必會面試題

1.說出你對面向對象的理解

       在我理解,面向對象是向現實世界模型的自然延伸,這是一種“萬物皆對象”的編程思想。在現實生活中的任何物體都可以歸為一類事物,而每一個個體都是一類事物的實例。面向對象的編程是以對象為中心,以消息為驅動,所以程序=對象+消息。
       面向對象有三大特性,封裝、繼承和多態。
       封裝就是將一類事物的屬性和行為抽象成一個類,使其屬性私有化,行為公開化,提高了數據的隱秘性的同時,使代碼模塊化。這樣做使得代碼的復用性更高。
       繼承則是進一步將一類事物共有的屬性和行為抽象成一個父類,而每一個子類是一個特殊的父類–有父類的行為和屬性,也有自己特有的行為和屬性。這樣做擴展了已存在的代碼塊,進一步提高了代碼的復用性。
       如果說封裝和繼承是為了使代碼重用,那麼多態則是為了實現接口重用。多態的一大作用就是為了解耦–為了解除父子類繼承的耦合度。如果說繼承中父子類的關係式IS-A的關係,那麼接口和實現類之之間的關係式HAS-A。簡單來說,多態就是允許父類引用(或接口)指向子類(或實現類)對象。很多的設計模式都是基於面向對象的多態性設計的。

2.JVM的內存區及其GC算法

參考:

元空間:jdk1.8取消了持久代新增了元空間,並將方法區放在元空間中

3.集合框架下的各種接口和實現類有哪些,分別有啥特點

參考:

4.string類有啥特點,有哪些常用的API

1.String類對象的相等判斷使用equals()方法完成,“==”實現的是地址數值的比較
2.字符串內容一旦聲明則不可改變,String類對象內容的改變是依靠引用關係的變更實現的。
3.String類有兩種實例化方式,使用直接賦值可以不產生垃圾空間,並且可以自動入池,不要使用構造方法賦值。

一些常見API:

 indexOf():檢索字符串中某個字符或某段字符的下標。

lastIndexOf():和indexOf類似,不過是查找最後一個出現的位置。

str.lastIndexOf(str,index):從下標index往前查找最後一個出現的位置

substring():返回一個字符串的子字符串

charAt(index):返回下標對應的字符

trim():去掉字符串前後的空格

startsWith()/endsWith():檢測字符串是否已制定字符串開頭或結尾,返回值是boolean

split()/根據括號內的字符串分離字符串,返回值是一個字符串數組

….

5.stringBuilder和stringBuffer的區別?

運行速度:StringBuilder >StringBuffer >String
線程安全:StringBuilder是線程不安全的,而StringBuffer是線程安全的
String:適用於少量的字符串操作的情況
StringBuilder:適用於單線程下在字符緩衝區進行大量操作的情況
StringBuffer:適用多線程下在字符緩衝區進行大量操作的情況

為什麼StringBuilder是不安全的?

下面是StringBuilder 的append方法源碼

char[] value;
int count;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}

對於count + =len;不是一個原子操作 兩個線程同時執行假設都是 計數器為 10 執行完后 就會變成11 而不是12

什麼是原子操作:

 簡單的例子:
       轉賬,A轉給B100,因為停電,導致A轉出了100,B卻沒收到100,所以要把100回滾給A。
原子操作就是多線程下各線程同時執行失敗且同時成功,在兩個線程下,由於count繼承於父類AbstractStringBuilder,當
其中一個線程對coun執行+len后,另一線程取到的count值仍為原來的count值,故+len后和上一個線程得到的結果一樣,
故線程不安全
而stringBuffer中源碼:

@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

當一個線程訪問append後會立即上鎖,從而另一個線程無法訪問append方法,故是線程安全的
在多線程下,stringBuffer下各線程需要頻繁的加鎖解鎖操作,從而需要運行更長的時間,雖然stringBuilder不需要加鎖解鎖,
但由於線程不安全性,更適用於單線程。

6.線程創建的3種方式,線程阻塞的API有哪些及其之間的區別?

Runnable,Thread,通過 Callable 和 Future 創建線程三種方式。

1. 繼承Thread類來創建一個線程, 並實現run方法(線程需要完成的功能); 構建子類對象,start()啟動線程
2. 實現Runnable接口來創建一個線程, 實現Runnable,實現run()方法; 將Runnable接口類的對象作為參數傳遞給Thread類對象, 並調用start()方法;
3. 實現Callable接口來創建一個線程, 先定義一個Callable的實現類, 重寫call()方法, call()有返回值; 兩種執行方式:
1). 藉助FutureTask執行, 創建Callable實現類的對象, 並作為參數傳遞給FutureTask, FutureTask作為參數傳遞給Thread類的對象, 並執行start()方法;
2). 藉助線程池來執行, 先創建線程池, 然後調用線程池的submit方法, 並將Callable實現列作為參數傳入

方法二的好處:
1. 可以將一個Runnable實現類傳遞給多個線程對象, 適合用多個相同程序代碼的編程處理同一個資源
2. Thread類創建線程是採用繼承的方式, 而Java中只能單繼承, 如果某個子類的需要創建線程只能採用實現Runnable接口或者實現Callable接口的方式.

方法三的好處:
1. 有返回值
2. call()可以拋出異常
3. 運行Callable任務可以得到一個Future兌現,表示異步計算的結果. 它提供了檢測計算是否完成的方法(isDone())以等待計算的完成,並檢索計算的結果.

線程阻塞api:

sleep()方法;:該方法允許指定以ms為單位的一段時間作為參數, 它使得線程在指定的時間內進入阻塞狀態,不能得到CPU時間, 指定時間已過,線程重新進入可執行狀態.
suspend()和resume()方法:配套使用, suspend()使得線程進入阻塞狀態,且不會自動恢復, 必須將其對應的resume()調用, 才可以使線程進入可執行狀態.
yield();:使得線程放棄當前分得的CPU時間, 但是不使線程阻塞, 即線程仍然處於可執行狀態;
wait()和notify()方法:配套使用,若wait()有參數,相當於sleep(但可以通過notify強行喚醒), wait()沒有參數,相當於suspend(), 需要通過notify喚醒

sleep(0)和sleep(1)和不要sleep的區別:

sleep(0),如果線程調度器的可運行隊列中有大於或等於當前線程優先級的就緒線程存在,操作系統會將當前線程從處理器上移除,調度其他優先級高的就緒線程運行;如果可運行隊列中的沒有就緒線程或所有就緒線程的優先級均低於當前線程優先級,那麼當前線程會繼續執行,就像沒有調用 Sleep(0)一樣。
Sleep(1),會引發線程上下文切換:調用線程會從線程調度器的可運行隊列中被移除一段時間,這個時間段約等於 timeout 所指定的時間長度。為什麼說約等於呢?是因為睡眠時間單位為毫秒,這與系統的時間精度有關。通常情況下,系統的時間精度為 10 ms,那麼指定任意少於 10 ms但大於 0 ms 的睡眠時間,均會向上求值為 10 ms。

7.抽象類和接口的區別?有了抽象類為啥還要接口?

1.一類可以實現多個接口但只能繼承自一個抽象類,從抽象類派生出的子類同樣可以實現接口,從而,我們能得出一個結論:接口是為Java實現多繼承而存在的
2.抽象類中可以存在非抽象的方法,可接口不能存在非抽象的方法,並且接口裡面的方法只是一個聲明,必須用 public abstract來修飾,沒有具體的實現
3.抽象方法中的成員變量可以被不同的修飾符修飾,而接口中的成員變量默認都是靜態常量
4.抽象類是對對象進行的抽象,而接口是一種行為規範,這一點是比較重要的.
(所以為什麼有了接口還要有抽象類)

8.冒泡排序,選擇排序,快速排序(了解)

冒泡排序:什麼是冒泡?比如說水底隨機產生一些氣泡,一起往上冒泡,越輕的氣泡往上冒的越快
具體:12 34 10 78 67
如果從小到大排序:先將67和78比較,67比78小,依次往前比較,小的放前面,打的放後面,以此為一輪排序,然後再將新的數組重複上述過程,共需要n輪排序(n為元素個數);

選擇排序:從一個數組裡選出最小的元素放在數組第一位並交換位置,然後再將去掉第一位的數組找出最小元素並放在這個新數組第一位,
重複此操作。
12 34 10 78 67
第一輪:10| 34 12 78 67
第二輪:10 12| 34 78 67
第三輪:10 12 34| 78 67
第四輪:10 12 34 67| 78
排序結束

快速排序:基於基數排序。先取任意一基數,一般為數組第一個元素(由於當第一個元素為最小值(最大值)時會使排序出現錯誤,故有時候也取中間的元素),然後將比基數小的數作為一個數組,比基數大的數作為一個數組,再將新的兩個數組分別遞歸排序。
通過基數分成兩個數組的過程:12 34 10 78 67 8 假設數組為arr
取一基數temp=12 取low=0(數組第一位),high=5(數組最後一位)
第一輪:第一步:先從后往前比較:arr[high]=8<12=temp,結束這一步操作,high與low不變。如果這裏arr[high]>12,則令high-1得到新的high將arr[high]與temp比較,依此下去直到arr[high]<temp,這種情況high發生改變,low不變。
第二步:再從前往後將arr[low]與temp比較,原理與第一步相同,因為arr[1]>temp,此時low=1,結束這一步操作。
第三步:交換arr[low]與arr[high]的值
第一輪結果:12 8 10 78 67 34(low=1,high=5)
第二輪:與第一輪一樣,第一步,從arr[high]往前,直到arr[2]=10<12,此時high=2,結束這一步
第二步,從arr[low]往後,12,8,10都不大於12,到這裏的時候,因為low=2=high,故比較,得到索引index=low=high=2
第二輪結果:12 8 10 78 67 34
因為index得到了值3,將arr[index]作為分界點將最後一輪結果數組[12 8 10 78 67 34]分為兩個數組[12 8 10]和[78 67 34]
將新的到的兩個數組重複進行上述操作
[12 8 10]->因為12為最大值,故取中間值8->[8]和[10 12]->[8]、[10]、[12]
[78 67 34]->取67,->[34]、[67 78]->[34]、[67]、[78]->[8]、[10]、[12]、[34]、[67]、[78]
(拓展:希爾排序、插入排序)

9.什麼是死鎖?如何避免死鎖

死鎖的定義:所謂死鎖是指多個線程因競爭資源而造成的一種僵局(互相等待),若無外力作用,這些進程都將無法向前推進。

產生原因:
1) 系統資源的競爭
通常系統中擁有的不可剝奪資源,其數量不足以滿足多個進程運行的需要,使得進程在 運行過程中,會因爭奪資源而陷入僵局,如磁帶機、打印機等。只有對不可剝奪資源的競爭 才可能產生死鎖,對可剝奪資源的競爭是不會引起死鎖的。
2) 進程推進順序非法
進程在運行過程中,請求和釋放資源的順序不當,也同樣會導致死鎖。例如,併發進程 P1、P2分別保持了資源R1、R2,而進程P1申請資源R2,進程P2申請資源R1時,兩者都 會因為所需資源被佔用而阻塞。

四個產生死鎖的條件:
互斥條件:進程要求對所分配的資源(如打印機)進行排他性控制,即在一段時間內某 資源僅為一個進程所佔有。此時若有其他進程請求該資源,則請求進程只能等待。
不剝奪條件:進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,即只能 由獲得該資源的進程自己來釋放(只能是主動釋放)。
請求和保持條件:進程已經保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其他進程佔有,此時請求進程被阻塞,但對自己已獲得的資源保持不放。
循環等待條件:存在一種進程資源的循環等待鏈,鏈中每一個進程已獲得的資源同時被 鏈中下一個進程所請求。即存在一個處於等待狀態的進程集合{Pl, P2, …, pn},其中Pi等 待的資源被P(i+1)佔有(i=0, 1, …, n-1),Pn等待的資源被P0佔有。

避免死鎖:
1.加鎖順序(線程按照一定的順序加鎖)
2.加鎖時限(線程嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己佔有的鎖)
3.死鎖檢測
參考:

 

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

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

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

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

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

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

什麼是回調,回調在編程中的含義

回調函數的最初需求背景

回調函數我能想到的最古老的場景就是系統編程會用到。

編程分為兩類:

  • 系統編程(system programming)
  • 應用編程(application programming)

什麼是系統編程:
  所謂系統編程,簡單來說,就是編寫各種各樣的功能庫。比如Windows裏面的win32、gdi32庫,win32就能調用主機硬件和系統層的功能,gdi32能用來繪製圖形相關。這些庫就等着那些做應用的人來調用就行。

什麼是應用編程:
  而應用編程就是利用已經寫好的各種系統功能庫、語言功能庫來編寫具某種業務功能用的程序,就是應用。比如一個基礎的爬蟲程序,可以利用python語言和requests庫來完成,一個基礎的Web站點可以利用Java語言和Java Servlet庫來完成。

系統編程和回調的關係

  系統程序員會給自己寫的庫留下一些接口,即API,以供應用程序員使用。所以在抽象層的圖示里,庫位於應用的底下。當程序跑起來時,一般情況下,應用程序會時常通過API調用庫里所預先備好的函數。但是有些庫函數卻要求應用先傳給它一個函數,好在合適的時候調用,以完成目標任務。這個被傳入的、后又被調用的函數就稱為回調函數。

如果你看文字看得比較懵,那麼你看我畫的圖(下面是圖1):

(圖1)

理解回調前,先理解同步調用

同步調用是以一種阻塞式調用,簡單來說就是從上往下,按照順序去執行。 而回調就是一種非同步調用式順序。

  同步式調用的具體案例,可以聯想到古代的烽火台。古代長城的烽火傳遞的機制就和同步調用差不多,現在我們假設每個烽火只能看到相鄰的烽火狀態,每個烽火的狀態只有亮(點火狀態)和暗(不點火狀態)。

  現在有A、B、C、D四個烽火台,A首先點亮,B看到A的烽火亮了,立馬去點火,花了2秒點亮。但是這時候負責C烽火的人在睡覺,可是這時候所有人都在等待C點亮,終於C睡了2個小時候看到了B點亮,然後去點亮。D由於長期沒有點亮,導致烽火出現問題,因此整個過程都在等待D的完成。(由此也引發一些思考,同步調用有時也容易掉鏈子,如果上一步掉鏈子了,下一步之後的操作都完蛋了。)

同步調用的案例代碼:

print("start.")
print(123)
print(456)

a = 7
if a > 6:
    print(789)

print(91011)
print("end.")

回調需要解決的問題

  常見的系統都會開發出很多庫,庫裏面有很多函數。而有些函數,需要調用者根據自己的需求來寫入要調用的函數。因為這個在編寫庫的時候沒法預測,只能由調用者輸入,所以就需要回調機制。

  回調機制是用來完善同步調用機制的一種方式,用來完善同步調用機制的還有異步調用機制。(後面會寫文章介紹這種更重要的異步)

回調函數怎麼解決實際問題的案例

回調就是通過如下方式來解決上面說的問題。

  • 函數能變成參數
  • 靈活、自定義的方式調用

函數變參數案例

def doubel(x):
    return 2*x

def quadruple(x):
    return 4*x

# mind function
def getAddNumber(k, getEventNumber):
    return 1 + getEventNumber(k)

def main():
    k=1
    i=getAddNumber(k,double)
    print(i)
    i=getAddNumber(k,quadruple)
    print(i)

# call main
main()

輸出結果:

3
5

靈活、自定義的方式調用(酒店叫醒旅客)案例

這個案例真是回調的靈魂所在了,假設你是酒店的前台小姐姐,你不可能知道今晚入住的旅客需不需要明天要不要叫醒服務、需要什麼樣的叫醒服務。

def call_you_phone(times):
    """
    叫醒方式: 給你打電話
    :param times: 打幾次電話
    :return: None
    """
    print('已經給旅客撥打了電話的次數:', str(times))

def knock_you_door(times):
    """
    叫醒方式: 去敲你房間門
    :param times: 敲幾次門
    :return: None
    """
    print('已經給旅客敲門的次數:', str(times))

def no_service(times):
    """
    叫醒方式: 無叫醒服務. (默認旅客是選無叫醒服務)
    :param times: 敲幾次門
    :return: None
    """
    print('顧客選擇無服務.不要打擾他的好夢。')

def front_desk(times, function_name=no_service()):
    """
    這個相當於酒店的前台,你去酒店之後,你要啥叫醒方式都得在前台說
    這裡是實現回調函數的核心,相當於一个中轉中心。
    :param times:次數
    :param function_name:回調函數名
    :return:調用的函數結果
    """
    return function_name(times)

if __name__ == '__main__':
    front_desk(100, call_you_phone)  # 意味着給你打100次電話,把你叫醒

輸出:

已經給旅客撥打了電話的次數:100

實際應用(Python的requests庫自帶的事件鈎子)

這個案例就很好解決原本程序是同步機制執行的,但是通過鈎子事件,就可以優先去執行一些先行步驟。而這個鈎子事件的原理就是函數回調。

import requests

def env_hooks(response, *args, **kwargs):
    print(response.headers['Content-Type'])

def main():
    result = requests.get("https://api.github.com", hooks=dict(response=env_hooks))
    print(result.text)

if __name__ == '__main__':
    main()

輸出:

application/json; charset=utf-8
{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"...省略"}

課後思考題

看完了上面的案例,請你回答如下幾個問題

1.回調在那些場景下使用?
2.回調必須以函數為參數嗎?
3.回調和異步的差異在哪裡?

把你的思考評論在評論區裏面,我會抽空給你回復的。

參考文獻

參考:
參考:

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

【其他文章推薦】

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

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

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

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

.NET Core 對龍芯的支持情況和對 .NET Core 開發嵌入式的思考

目錄

.NET Core 對龍芯的支持情況和對 .NET Core 開發嵌入式的思考

一,遺憾的嘗試

前些天看到了張隊公眾推送的《》,聯想到上一周與朋友在龍芯搗鼓 .NET Core,就想寫一下關於 .NET Core 在龍芯下的資料。

Jexus Web Server 能夠在龍芯服務器上跑,但是 ASP.NET 呢?.NET Core 呢?安裝什麼版本的 Mono ?Jexus 作者的文章表達有點模糊呀~

上一周與朋友在龍芯上面為了部署 .NET 項目,頗費心機。朋友公司中標政府項目,開發好 .NET Core 做的項目后,才發現要部署的服務器是龍芯的,.NET Core 無法在上面運行。

服務器有什麼有 Mono 4.x,可以創建簡單的 Proparm.cs ,編譯出程序,使用 mono xx.exe 運行,可是把項目放進去編譯不出來~想編譯安裝 Mono 6.x 也不行,中間有些過程報錯。

.NET Core 自然不用想了,完全無法編譯,通過 Google 查詢資料,要重寫 C++ 部分(移植),才能在 龍芯 下編譯出 CoreCLR。

官方 CoreCLR 源碼庫,可以看到一些腳本和編譯工具鏈。

RISV-C 是精簡指令集,MIPS 是指 基於 RISC-V 的 CPU 架構,龍芯服務器使用 MIPS 架構。

最終,無法部署 .NET 軟件,朋友公司改用 Java 開發。。。

之前筆者為了在 Armel 的 CPU 下運行 .NET Core ,花了很多時間手動編譯 .NET Core,最終還是失敗。我將編譯過程詳細寫了一篇文章,地址《》。

二,.NET Core在嵌入式下的幾點不足

18年7月張隊來我校組織了大灣區 .NET 交流會,從那時起開始學習 .NET ,19 年三月月份進入敢為實習轉正至此。

使用 .NET Core 開發半年的時間里,在嵌入式開發中,我個人總結當前 .NET Core 在嵌入式領域有幾個問題/建議。

1,不支持前幾年的CPU

.NET Core 無法在樹莓派 Zero上運行(Arm v6);

無法在華為海思A9芯片上運行(Armel Armv7);

這兩種芯片雖說是幾年前出的芯片,但是 .NET Core 大張旗鼓的說要搞 IoT,卻不兼容舊一些的 CPU,目前很多舊式設備依然會在未來一段時間內是主流的存在 。

微軟官方也說了:

Note: .NET Core 2.1 is supported on Raspberry Pi 2+. It isn’t supported on the Pi Zero or other devices that use an ARMv6 chip. .NET Core requires ARMv7 or ARMv8 chips, like the ARM Cortex-A53.

Arm 方面的支持還是不夠廣。

2,測試的硬件設備較少

官方對嵌入式設備的測試,主要在 樹莓派 2 / 3,還有很多開發板沒有測試~

3,支持兼容的系統版本較少

.NET Core 支持很多 Linux 系統,但是對應這些系統的支持,都是以最新版本的系統為主,例如 .NET Core 3.0 在Ubuntu 上是支持 16.x、18.x,14.x 和 17.x 被無情的拋棄了。

.NET Core 3.0 支持的系統如下:

4,體積依然太大

對於嵌入式開發來說, .NET Core 的體積依然太大,.NET Core 3.0 也拯救不了。。。哪怕只有一行 Hello World,也要 70MB+ 以上。

5,依賴庫比較傷腦筋

經常會出現 ICU、libssl、gcc 等依賴庫版本不一致或沒有安裝這些庫時的報錯信息,石頭哥曾經被這些問題搞得掉頭髮。

三,.NET Core 龍芯移植的進展和資料

根據大佬們的移植,在 11 月 9 號時,已經實現了 在龍芯上面運行 .NET Core 的 Hello World 實例,

The code base was upgraded to 3.0. Hello World and serveral tests in coreclr can run on MIPS64 now. 

這是對於 CoreCLR 的移植,還有很多問題等待大神解決。

對於 .NET Core 在 MIPS 上的移植討論,可以到 Issue 查看

不過,微軟官方目前沒有移植計劃,只能靠社區去完成了。

Microsoft currently has no plans or work in progress to support MIPS. Of course, we would be willing to accept external contributions towards that goal as appropriate. Note that it is, certainly, a significant amount of work to port .NET Core to a new platform.

還有另一個大神的作品

This project will focus on translating .NET IL for non-supported .NET targets. Portibility is a huge focus.

  • .NET Standard compatibility
  • Native C performance
  • C89: modern, legacy and embedded platforms (x86, MIPS, SPARK, RISC-V, PPC, AVR, etc)
  • CC65: 6502 platforms (Atari, C64, NES, Apple II, etc) [CS2X may be better suited]]
  • SDCC: Many targets (ColecoVision, etc) [CS2X may be better suited]
  • Assembly: CP1610 (Intellivision) [CS2X may be better suited]]
  • Retarget: Custom assembly targets via plugin system (FPGA CPU, 16bit bytes, etc)
  • Custom Standard lib(s) for various targets.
  • Documentation

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

【其他文章推薦】

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

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

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

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

降空污 國際級船舶新規2020上路 燃油含硫量最多0.5% 違規將受罰

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

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

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