網(wǎng)站優(yōu)化方案 site ww企業(yè)seo優(yōu)化服務(wù)
學(xué)習(xí)計(jì)劃:將目前已經(jīng)學(xué)的知識(shí)點(diǎn)串成一個(gè)思維導(dǎo)圖。在往后的學(xué)習(xí)過程中,不斷往思維導(dǎo)圖里補(bǔ)充,形成自己整個(gè)知識(shí)體系。對(duì)于思維導(dǎo)圖里的每個(gè)技術(shù)知識(shí),自己用簡(jiǎn)潔的話概括出來,?訓(xùn)練自己的表達(dá)能力。
面向?qū)ο笕筇匦?/h2>
封裝
封裝就是使用private修飾屬性或方法,這樣類的對(duì)象就沒法直接訪問或修改屬性,只能通過get/set方法進(jìn)行訪問或修改。
封裝的好處:降低代碼的耦合度,利于維護(hù);通過get/set方法訪問屬性的時(shí)候,可以增加一些額外的邏輯,這是單獨(dú)訪問屬性無法做到的。
繼承
繼承就是子類繼承父類,子類繼承了父類所有的方法和屬性。并且子類還可以增加一些自己的屬性和方法。
繼承的好處是提高代碼的復(fù)用性,比如說一個(gè)類想要擁有另一個(gè)類的屬性和方法,就可以采用繼承的方式實(shí)現(xiàn)。
多態(tài)
多態(tài)就是父類引用指向子類的實(shí)例,多態(tài)只有在運(yùn)行時(shí)才能確定調(diào)用的是哪個(gè)類的方法。
多態(tài)的好處是提高代碼的復(fù)用性,比如說使用一個(gè)方法時(shí)將參數(shù)設(shè)置為父類的引用,來接收各種各樣的子類實(shí)例。這樣寫一個(gè)方法就可以有各種效果。
面向?qū)ο蟮睦斫?/h2>
面向?qū)ο缶褪侨f事萬物抽象為對(duì)象,將一些行為、特點(diǎn)抽象為方法和屬性。
面向?qū)ο蟮暮锰幘褪鞘勾a的耦合度降低,我們要實(shí)現(xiàn)什么功能時(shí)直接調(diào)用對(duì)象的方法即可。比如說要實(shí)現(xiàn)開門這個(gè)動(dòng)作,面向?qū)ο蟮乃悸肪褪窍瘸橄蟪鰀oor這個(gè)對(duì)象,然后再抽象出door的一些特征作為屬性,比如說門的大小、顏色。然后抽象出door的開門、關(guān)門的方法。我們?nèi)绻胍獙?shí)現(xiàn)開門動(dòng)作的話就直接調(diào)對(duì)象的這個(gè)方法即可。
?
重載與重寫的區(qū)別
重載就是在同一個(gè)類中的多個(gè)方法,方法名相同,但是方法參數(shù)類型或參數(shù)個(gè)數(shù)不同。在編譯期間就可以確定是調(diào)用哪個(gè)方法。
重寫就是子類重寫父類的方法。
重載算多態(tài)的體現(xiàn)嗎
不算,重載是編譯期間就確定要調(diào)用的方法,而多態(tài)是要在運(yùn)行時(shí)才確定要調(diào)用的方法。?
反射機(jī)制
反射就是獲取類在運(yùn)行時(shí)的大Class實(shí)例,只有獲取了這個(gè)實(shí)例才能獲取有關(guān)這個(gè)類的各種信息比如方法、屬性。原理是因?yàn)閖vm在加載類時(shí)都會(huì)為這個(gè)類在堆中生成大class實(shí)例,并且指向方法區(qū)中有關(guān)這個(gè)類的各種信息。
泛型?
泛型就是規(guī)定一種類型。比如說我們創(chuàng)建一個(gè)list,我們就可以使用泛型規(guī)定list里的每個(gè)元素的參數(shù)類型。
另一方面,當(dāng)方法的形參類型不確定的時(shí)候,也可以使用泛型,這樣就可以提高方法的可復(fù)用性。方法放進(jìn)去的是時(shí)候,取出來的就是什么。相較于多態(tài),可以避免強(qiáng)制類型轉(zhuǎn)換的異常。?
序列化與反序列化
序列化就是將對(duì)象轉(zhuǎn)換為字節(jié)流或者json文本格式的過程,反序列化就是由字節(jié)流或json文本格式轉(zhuǎn)換為對(duì)象的過程。
對(duì)象只有序列化后才能進(jìn)行傳輸和存儲(chǔ)。
比如說當(dāng)我們想要將對(duì)象存進(jìn)redis中的value里,我們可以使用json序列化器,但是由于序列化為json后會(huì)多存儲(chǔ)一個(gè)類路徑,浪費(fèi)空間。因此我們會(huì)選擇String序列化器,首先將對(duì)象手動(dòng)轉(zhuǎn)變?yōu)閖son格式的字符串,將字符串轉(zhuǎn)換為字節(jié)流傳輸。
String、StringBuilder、StringBuffer的區(qū)別?
1、可變性
String是不可變的,因?yàn)镾tring底層是一個(gè)private final修飾的字符數(shù)組。final意味這個(gè)字符數(shù)組的地址值不能改變,并不意味著字符數(shù)組里的內(nèi)容不能改變,String真正不可變的原因在于這個(gè)private。由于是private修飾的,并且String沒有提供修改這個(gè)字符數(shù)組的方法,因此沒有任何渠道能修改這個(gè)字符數(shù)組,因此String是不可變的。
而StringBuilder和StringBuffer底層就只是字符數(shù)組,因此是可變的。
2、線程安全性
?String是不可變的,因此String是線程安全的。
由于StringBuilder底層方法沒有加鎖,因此是線程不安全的,而StringBuffer底層方法是加了synchronized鎖的,因此是線程安全的。
3、性能
對(duì)于字符串的拼接,如果String類型的變量使用“+”來拼接,底層會(huì)新創(chuàng)建一個(gè)String對(duì)象,而原來那個(gè)String對(duì)象就變成了無引用,使得堆中垃圾變多,gc時(shí)間變多,自然影響性能。而對(duì)于StringBuilder類型與StringBuffer類型使用append方法,不會(huì)新創(chuàng)建對(duì)象,效率更高,而StringBuilder性能又會(huì)比StringBuffer高,因?yàn)榈讓記]有加鎖。?
hashCode() & equals()?
hashCode方法就是獲取對(duì)象的哈希碼。而哈希碼主要用于確定對(duì)象在哈希表中的下標(biāo)位置。比如說就有用在HashMap、HashSet中。
hashCode方法在HashMap或HashSet中經(jīng)常配合equals方法使用,因?yàn)閔ashCode存在哈希碰撞問題,所以不同對(duì)象的哈希碼可能相同,那么就需要使用equals進(jìn)一步判斷對(duì)象是否相同。另外,如果沒有hashCode方法只有equals方法時(shí),就需要一個(gè)一個(gè)對(duì)象的進(jìn)行比較,效率很低,所以hashCode的作用使得查找比較的效率提高。?
clone()?
淺拷貝與深拷貝:
子類直接調(diào)用父類(也就是Object類)的clone的方法,就是淺拷貝。淺拷貝會(huì)在堆上創(chuàng)建新的對(duì)象,但是如果對(duì)象內(nèi)部的屬性是引用類型的話,淺拷貝只會(huì)復(fù)制這個(gè)內(nèi)部對(duì)象的引用地址,淺拷貝后的對(duì)象與原對(duì)象共享這個(gè)內(nèi)部對(duì)象。
深拷貝就是需要重寫clone方法,手動(dòng)進(jìn)行深拷貝。深拷貝就是不僅會(huì)在堆上創(chuàng)建新對(duì)象,也會(huì)創(chuàng)建新的內(nèi)部對(duì)象。?
wait() & notify()?
共同點(diǎn):這兩個(gè)方法都可以讓線程暫停執(zhí)行。讓線程變成等待態(tài)。
不同點(diǎn):
1、sleep方法時(shí)間到了,線程會(huì)自動(dòng)變成運(yùn)行態(tài);而wait方法需要等待其他線程調(diào)用同一個(gè)對(duì)象的notify方法后,線程才會(huì)變成運(yùn)行態(tài)。
2、wait方法會(huì)使線程釋放鎖,而sleep方法不會(huì)使線程釋放鎖。
3、wait方法是在Object類里的方法,而sleep方法是在Thread類里的方法。
?
為什么wait方法是在object類的?
因?yàn)閣ait方法需要釋放當(dāng)前線程所占有的鎖,又因?yàn)檫@個(gè)鎖是對(duì)象鎖,因此wait操作的應(yīng)該是對(duì)象,而不是線程。?
集合框架圖
ArrayList?
ArrayList & LinkedList 區(qū)別
ArrayList底層是一個(gè)數(shù)組,因此隨機(jī)訪問元素的速度很快,但是插入和刪除的速度就很慢,時(shí)間復(fù)雜度是O(n)級(jí)別的。
LinkedList底層是一個(gè)雙向鏈表,插入和刪除速度快,但是隨機(jī)訪問元素的速度就很慢,需要依次遍歷。
?
ArrayList初始化
如果在new ArrayList的時(shí)候沒有指定集合容量,那么底層就會(huì)初始化一個(gè)數(shù)組大小為0的空數(shù)組,當(dāng)?shù)谝淮蝍dd元素時(shí)就會(huì)將數(shù)組的長(zhǎng)度擴(kuò)容到默認(rèn)長(zhǎng)度10的大小。
啟發(fā):當(dāng)我們初始化ArrayList的時(shí)候,就應(yīng)該指定好數(shù)組容量,否則當(dāng)容量不夠時(shí)就需要擴(kuò)容和復(fù)制,和帶來一定的性能損耗。
?
ArrayList添加元素/擴(kuò)容機(jī)制
當(dāng)ArrayList add元素時(shí),首先會(huì)先確保數(shù)組長(zhǎng)度是足夠的,因此會(huì)將數(shù)組長(zhǎng)度與list中已經(jīng)存儲(chǔ)的元素個(gè)數(shù)+1進(jìn)行比較,如果數(shù)組長(zhǎng)度不夠,那么就需要進(jìn)行擴(kuò)容。擴(kuò)容就是將數(shù)組長(zhǎng)度擴(kuò)容為原來的1.5倍,然后再將原數(shù)組中的元素復(fù)制到新數(shù)組中。
啟發(fā):擴(kuò)容為原來的1.5倍,底層源碼是通過位運(yùn)算進(jìn)行計(jì)算,比如,假設(shè)oldCapacity=13,二進(jìn)制數(shù)是1101,1101 >> 1 = 0110 = 6,6+13=19。擴(kuò)容后的長(zhǎng)度就是19。使用位運(yùn)算速度更快。
?
ArrayList怎么實(shí)現(xiàn)復(fù)制?如何自己實(shí)現(xiàn)?
有三種方法,可以使用ArrayList類里的clone方法,這是一個(gè)淺拷貝;還可以使用ArrayList類里的addAll方法,這也是一個(gè)淺拷貝;還可以使用構(gòu)造器,將原list作為參數(shù)裝進(jìn)構(gòu)造器里就會(huì)得到一個(gè)新的list,這也是一個(gè)淺拷貝方法。
自己實(shí)現(xiàn)的話,首先創(chuàng)建一個(gè)新的ArrayList對(duì)象,然后依次遍歷原ArrayList中的每個(gè)元素,將其添加進(jìn)新ArrayList對(duì)象中。在這個(gè)過程中還可以手動(dòng)實(shí)現(xiàn)深拷貝。
?
ArrayList是線程安全的嗎?
ArrayList是線程不安全的。舉個(gè)例子,當(dāng)調(diào)用ArrayList里的add方法時(shí),在多線程的環(huán)境下,可能有多個(gè)線程拿到同一個(gè)size,那么就會(huì)將各自的元素添加進(jìn)數(shù)組的同一個(gè)位置中,這樣就出現(xiàn)了數(shù)據(jù)覆蓋問題,從而導(dǎo)致了線程不安全。?
HashMap?
HashMap初始化過程
當(dāng)new一個(gè)HashMap時(shí),table數(shù)組不會(huì)被初始化,只有等第一次put元素時(shí),table數(shù)組才會(huì)被new出來,如果沒有指定哈希表大小的話,則默認(rèn)table數(shù)組長(zhǎng)度為16。如果指定了哈希表大小,則會(huì)按大于該指定值的2的n次方進(jìn)行分配。
?
HashMap put元素過程
首先會(huì)根據(jù)元素的key通過hashcode方法計(jì)算出哈希值,再由哈希值計(jì)算出哈希槽的索引位置。
接著判斷這個(gè)索引位置上是否有元素,如果沒有元素則直接我們的新元素插入;
如果這個(gè)索引位置上有元素,判斷這個(gè)元素是否為樹結(jié)點(diǎn),
? ? 如果是樹結(jié)點(diǎn),則走樹邏輯;
? ? 如果不是,則依次遍歷這個(gè)索引位置上的鏈表結(jié)點(diǎn),通過hashcode方法和equals方法判斷兩個(gè)元素的key是否相等。
? ? ? ? ?如果判斷出鏈表上的結(jié)點(diǎn)的key與新增元素的key是相等的,則用e指針記錄這個(gè)重復(fù)結(jié)點(diǎn),最后再用新增元素的value替換這 ? ? ? ? 個(gè)重復(fù)元素的value。
? ? ? ? ?而如果遍歷完鏈表里的所有結(jié)點(diǎn)發(fā)現(xiàn)都沒有重復(fù)的,那么就直接在鏈表結(jié)尾插入這個(gè)新增元素。
新增完元素后還需要判斷哈希表里的元素是否超過閾值,閾值就是哈希表容量*負(fù)載因子。如果超過了需要進(jìn)行擴(kuò)容。
?
HashMap擴(kuò)容過程
根據(jù)哈希表容量*負(fù)載因子可以得到擴(kuò)容的閾值。如果哈希表里的元素個(gè)數(shù)超過了閾值,那么就會(huì)進(jìn)行擴(kuò)容。擴(kuò)容是擴(kuò)容為原來哈希表大小的兩倍。然后在將原哈希表上的所有元素重新計(jì)算哈希值尾插法遷移至新的哈希表中。
由于都是兩倍兩倍的擴(kuò)容,因此我們的哈希表大小一定都是2的n次方。這是為了方便使用位運(yùn)算計(jì)算哈希碼對(duì)應(yīng)的數(shù)組索引位置。
?
jdk1.7的HashMap出現(xiàn)的死鏈問題
死鏈問題是擴(kuò)容遷移過程中的頭插法導(dǎo)致的。頭插法會(huì)使鏈表中元素倒序插入到新表中。如果兩個(gè)線程同時(shí)執(zhí)行遷移操作,同時(shí)執(zhí)行遍歷到某個(gè)哈希槽的第一個(gè)位置,其中一個(gè)線程使得table數(shù)組的鏈表變成倒序,然后另一個(gè)線程接著正常執(zhí)行,那么就會(huì)出現(xiàn)指針指回前面的結(jié)點(diǎn),從而出現(xiàn)循環(huán)鏈表。
?
HashMap出現(xiàn)的線程不安全問題
1、當(dāng)新增元素時(shí)可能出現(xiàn)線程不安全問題
如果兩個(gè)線程同時(shí)執(zhí)行put元素操作,且都計(jì)算出元素所對(duì)應(yīng)的索引下標(biāo)是同一個(gè)位置,然后同時(shí)判斷出這個(gè)索引位置沒有元素或者同時(shí)循環(huán)遍歷到鏈表為尾部,那么就會(huì)出現(xiàn)元素覆蓋問題,從而導(dǎo)致數(shù)據(jù)丟失。
?2、擴(kuò)容遷移過程出現(xiàn)的線程不安全問題
情況1:當(dāng)一個(gè)線程執(zhí)行擴(kuò)容遷移的過程中,其他線程仍然可以在原表中進(jìn)行新增元素,如果新增元素落在原表已遍歷過的哈希槽上的話,遷移遍歷完成后,當(dāng)table數(shù)組引用指向新表時(shí),在原表中新增的元素就會(huì)丟失。
情況2:當(dāng)多個(gè)線程都在各自內(nèi)存中擴(kuò)容遷移,也就是說它們各自都含有一個(gè)新表,當(dāng)線程遷移完成后,會(huì)將新表賦值給共享的table數(shù)組,因此就會(huì)出現(xiàn)在新表中插入元素被覆蓋的問題。