Java基礎(chǔ)之?dāng)?shù)據(jù)結(jié)構(gòu)

1.List?

ArrayList 、Vector 、LinkedList的區(qū)別?

答:ArrayList?底層是數(shù)組

特點(diǎn):查詢快,增刪慢,線程不安全

LinkList?底層是鏈表

特點(diǎn):查詢慢,增刪快,線程不安全

Vector?底層是數(shù)組

特點(diǎn):查詢快,增刪慢,線程安全

Array 和 ArrayList 有什么區(qū)別?什么時(shí)候該應(yīng) Array 而不是 ArrayList 呢?

答:Array可以包含基本類型和對(duì)象類型,ArrayList只能包含對(duì)象類型。

Array大小是固定的,ArrayList的大小是動(dòng)態(tài)變化的。

ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。

對(duì)于基本類型數(shù)據(jù),集合使用自動(dòng)裝箱來減少編碼工作量。但是,當(dāng)處理固定大小的基本數(shù)據(jù)類型的時(shí)候,這種方式相對(duì)比較慢。

適用場(chǎng)景:?如果想要保存一些在整個(gè)程序運(yùn)行期間都會(huì)存在而且不變的數(shù)據(jù),我們可以將它們放進(jìn)一個(gè)全局?jǐn)?shù)組里,但是如果我們單純只是想要以數(shù)組的形式保存數(shù)據(jù),而不對(duì)數(shù)據(jù)進(jìn)行增加等操作,只是方便我們進(jìn)行查找的話,那么,我們就選擇ArrayList。而且還有一個(gè)地方是必須知道的,就是如果我們需要對(duì)元素進(jìn)行頻繁的移動(dòng)或刪除,或者是處理的是超大量的數(shù)據(jù),那么,使用ArrayList就真的不是一個(gè)好的選擇,因?yàn)樗男屎艿?,使用?shù)組進(jìn)行這樣的動(dòng)作就很麻煩,那么,我們可以考慮選擇LinkedList

2.Set

HashSet是如何保證數(shù)據(jù)不可重復(fù)的?HashSet和TreeSet有什么區(qū)別?TreeMap和TreeSet在排序時(shí)如何比較元素?Collections工具類中的sort()方法如何比較元素?

答:HashSet原理

我們使用Set集合都是需要去掉重復(fù)元素的, 如果在存儲(chǔ)的時(shí)候逐個(gè)equals()比較, 效率較低,哈希算法提高了去重復(fù)的效率, 降低了使用equals()方法的次數(shù)

當(dāng)HashSet調(diào)用add()方法存儲(chǔ)對(duì)象的時(shí)候, 先調(diào)用對(duì)象的hashCode()方法得到一個(gè)哈希值, 然后在集合中查找是否有哈希值相同的對(duì)象

如果沒有哈希值相同的對(duì)象就直接存入集合

如果有哈希值相同的對(duì)象, 就和哈希值相同的對(duì)象逐個(gè)進(jìn)行equals()比較,比較結(jié)果為false就存入, true則不存

TreeSet原理

TreeSet是用來排序的, 可以指定一個(gè)順序, 對(duì)象存入之后會(huì)按照指定的順序排列

TreeSet排序方式有兩種自然順序和比較器順序

總結(jié)一下他們的特點(diǎn):

1.TreeSet 是二差樹實(shí)現(xiàn)的,TreeSet中的數(shù)據(jù)是自動(dòng)排好序的,不允許放入null值

2.HashSet 是哈希表實(shí)現(xiàn)的,HashSet中的數(shù)據(jù)是無序的,可以放入null,但只能放入一個(gè)null?

3.HashSet要求放入的對(duì)象必須實(shí)現(xiàn)HashCode()方法,放入的對(duì)象,是以hashcode碼作為標(biāo)識(shí)的,而具有相同內(nèi)容的 String對(duì)象,hashcode是一樣,所以放入的內(nèi)容不能重復(fù)。但是同一個(gè)類的對(duì)象可以放入不同的實(shí)例?

4.TreeSet要求存放的對(duì)象所屬的類必須實(shí)現(xiàn)Comparable接口,該接口提供了比較元素的compareTo()方法,當(dāng)插入元素時(shí)會(huì)回調(diào)該方法比較元素的大小。TreeMap要求存放的鍵值對(duì)映射的鍵必須實(shí)現(xiàn)Comparable接口從而根據(jù)鍵對(duì)元素進(jìn)行排序。

Collections工具類的sort方法有兩種重載的形式,第一種要求傳入的待排序容器中存放的對(duì)象必須實(shí)現(xiàn)Comparable接口以實(shí)現(xiàn)元素的比較;第二種不強(qiáng)制性的要求容器中的元素必須可比較,但是要求傳入第二個(gè)參數(shù),參數(shù)是Comparator接口的子類型(需要重寫compare方法實(shí)現(xiàn)元素的比較),相當(dāng)于一個(gè)臨時(shí)定義的排序規(guī)則,其實(shí)就是通過接口注入比較元素大小的算法,也是對(duì)回調(diào)模式的應(yīng)用(Java中對(duì)函數(shù)式編程的支持)。

Set 里的元素是不能重復(fù)的,那么用什么方法來區(qū)分重復(fù)與否呢?是用 == 還是 equals()? 它們有何區(qū)別?

答:Set?里的元素是不能重復(fù)的,元素重復(fù)與否是使用?equals()方法進(jìn)行判斷的。

equals()和==方法決定引用值是否指向同一對(duì)象?equals()在類中被覆蓋,為的是當(dāng)兩個(gè)

分離的對(duì)象的內(nèi)容和類型相配的話,返回真值。

equals()和==的區(qū)別

==操作符專門用來比較兩個(gè)變量的值是否相等,也就是用于比較變量所對(duì)應(yīng)的內(nèi)存中所存

儲(chǔ)的數(shù)值是否相同,?要比較兩個(gè)基本類型的數(shù)據(jù)或兩個(gè)引用變量是否相等,只能用==操作

符。

如果一個(gè)變量指向的數(shù)據(jù)是對(duì)象類型的,那么,這時(shí)候涉及了兩塊內(nèi)存,?對(duì)象本身占用一塊

內(nèi)存(堆內(nèi)存),變量也占用一塊內(nèi)存,例如?Objet obj = new Object();變量?obj?是一個(gè)內(nèi)存,

new Object()是另一個(gè)內(nèi)存,此時(shí),變量?obj?所對(duì)應(yīng)的內(nèi)存中存儲(chǔ)的數(shù)值就是對(duì)象占用的那

塊內(nèi)存的首地址。對(duì)于指向?qū)ο箢愋偷淖兞?,如果要比較兩個(gè)變量是否指向同一個(gè)對(duì)象,即

要看這兩個(gè)變量所對(duì)應(yīng)的內(nèi)存中的數(shù)值是否相等,這時(shí)候就需要用==操作符進(jìn)行比較。

equals?方法是用于比較兩個(gè)獨(dú)立對(duì)象的內(nèi)容是否相同,就好比去比較兩個(gè)人的長(zhǎng)相是否相

同,它比較的兩個(gè)對(duì)象是獨(dú)立的。例如,對(duì)于下面的代碼:

String a=newString("foo");

String b=newString("foo");

兩條?new?語句創(chuàng)建了兩個(gè)對(duì)象,然后用?a/b?這兩個(gè)變量分別指向了其中一個(gè)對(duì)象,這是兩

個(gè)不同的對(duì)象,它們的首地址是不同的,即?a?和?b?中存儲(chǔ)的數(shù)值是不相同的,所以,表達(dá)

式?a==b?將返回?false,而這兩個(gè)對(duì)象中的內(nèi)容是相同的,所以,表達(dá)式?a.equals(b)將返回

true。

在實(shí)際開發(fā)中,我們經(jīng)常要比較傳遞進(jìn)行來的字符串內(nèi)容是否等,例如,?String input

=?…;input.equals(“quit”),許多人稍不注意就使用==進(jìn)行比較了,這是錯(cuò)誤的,隨便從網(wǎng)上

找?guī)讉€(gè)項(xiàng)目實(shí)戰(zhàn)的教學(xué)視頻看看,里面就有大量這樣的錯(cuò)誤。記住,字符串的比較基本上都

是使用?equals?方法。

如果一個(gè)類沒有自己定義?equals?方法,那么它將繼承?Object?類的?equals?方法,?Object?類

的?equals?方法的實(shí)現(xiàn)代碼如下:

boolean equals(Object o){returnthis==o;

}

這說明,如果一個(gè)類沒有自己定義?equals?方法,它默認(rèn)的?equals?方法(從?Object?類繼承

的)就是使用==操作符,也是在比較兩個(gè)變量指向的對(duì)象是否是同一對(duì)象,這時(shí)候使用

equals?和使用==會(huì)得到同樣的結(jié)果,如果比較的是兩個(gè)獨(dú)立的對(duì)象則總返回?false。如果你

編寫的類希望能夠比較該類創(chuàng)建的兩個(gè)實(shí)例對(duì)象的內(nèi)容是否相同,那么你必須覆蓋?equals

方法,由你自己寫代碼來決定在什么情況即可認(rèn)為兩個(gè)對(duì)象的內(nèi)容是相同的

由上述可見:

總結(jié)如下:

==:

  基本類型:比較的是值是否相同

  引用類型:比較的是地址值是否相同

equals():

  引用類型:默認(rèn)情況下,比較的是地址值,可進(jìn)行重寫,比較的是對(duì)象的成員變量值是否相同

3.Map

HashMap的實(shí)現(xiàn)機(jī)制,底層數(shù)據(jù)結(jié)構(gòu)是什么?怎么樣HashMap線程安全,HashMap和HashTable有什么區(qū)別?

答:HashMap和Hashtable的底層實(shí)現(xiàn)都是數(shù)組+鏈表結(jié)構(gòu)實(shí)現(xiàn)的,這點(diǎn)上完全一致。添加、刪除、獲取元素時(shí)都是先計(jì)算hash,根據(jù)hash和table.length計(jì)算index也就是table數(shù)組的下標(biāo),然后進(jìn)行相應(yīng)操作。

1.兩者最主要的區(qū)別在于Hashtable是線程安全,而HashMap則非線程安全

Hashtable的實(shí)現(xiàn)方法里面都添加了synchronized關(guān)鍵字來確保線程同步,因此相對(duì)而言HashMap性能會(huì)高一些,我們平時(shí)使用時(shí)若無特殊需求建議使用HashMap,在多線程環(huán)境下若使用HashMap需要使用Collections.synchronizedMap()方法來獲取一個(gè)線程安全的集合(Collections.synchronizedMap()實(shí)現(xiàn)原理是Collections定義了一個(gè)SynchronizedMap的內(nèi)部類,這個(gè)類實(shí)現(xiàn)了Map接口,在調(diào)用方法時(shí)使用synchronized來保證線程同步,當(dāng)然了實(shí)際上操作的還是我們傳入的HashMap實(shí)例,簡(jiǎn)單的說就是Collections.synchronizedMap()方法幫我們?cè)诓僮鱄ashMap時(shí)自動(dòng)添加了synchronized來實(shí)現(xiàn)線程同步,類似的其它Collections.synchronizedXX方法也是類似原理)

2.HashMap可以使用null作為key,而Hashtable則不允許null作為key

雖說HashMap支持null值作為key,不過建議還是盡量避免這樣使用,因?yàn)橐坏┎恍⌒氖褂昧耍粢虼艘l(fā)一些問題,排查起來很是費(fèi)事

HashMap以null作為key時(shí),總是存儲(chǔ)在table數(shù)組的第一個(gè)節(jié)點(diǎn)上

3.HashMap是對(duì)Map接口的實(shí)現(xiàn),HashTable實(shí)現(xiàn)了Map接口和Dictionary抽象類

4.HashMap的初始容量為16,Hashtable初始容量為11,兩者的填充因子默認(rèn)都是0.75

HashMap擴(kuò)容時(shí)是當(dāng)前容量翻倍即:capacity*2,Hashtable擴(kuò)容時(shí)是容量翻倍+1即:capacity*2+1

5.兩者計(jì)算hash的方法不同

Hashtable計(jì)算hash是直接使用key的hashcode對(duì)table數(shù)組的長(zhǎng)度直接進(jìn)行取模

HashMap計(jì)算hash對(duì)key的hashcode進(jìn)行了二次hash,以獲得更好的散列值,然后對(duì)table數(shù)組長(zhǎng)度取摸

6.HashMap和Hashtable的底層實(shí)現(xiàn)都是數(shù)組+鏈表結(jié)構(gòu)實(shí)現(xiàn)

為什么HashMap中String、Integer這樣的包裝類適合作為K?如果我想要讓自己的Object作為K應(yīng)該怎么辦呢?

答:String、Integer等包裝類的特性能夠保證Hash值的不可更改性和計(jì)算準(zhǔn)確性,能夠有效的減少Hash碰撞的幾率

都是final類型,即不可變性,保證key的不可更改性,不會(huì)存在獲取hash值不同的情況

內(nèi)部已重寫了equals()、hashCode()等方法,遵守了HashMap內(nèi)部的規(guī)范(不清楚可以去上面看看putValue的過程),不容易出現(xiàn)Hash值計(jì)算錯(cuò)誤的情況;

如果我想要讓自己的Object作為K,就重寫hashCode()和equals()方法

重寫hashCode()是因?yàn)樾枰?jì)算存儲(chǔ)數(shù)據(jù)的存儲(chǔ)位置,需要注意不要試圖從散列碼計(jì)算中排除掉一個(gè)對(duì)象的關(guān)鍵部分來提高性能,這樣雖然能更快但可能會(huì)導(dǎo)致更多的Hash碰撞;

重寫equals()方法,需要遵守自反性、對(duì)稱性、傳遞性、一致性以及對(duì)于任何非null的引用值x,x.equals(null)必須返回false的這幾個(gè)特性,目的是為了保證key在哈希表中的唯一性;

HashMap是怎么解決哈希沖突的?

答:Hash,一般翻譯為“散列”,也有直接音譯為“哈?!钡?,這就是把任意長(zhǎng)度的輸入通過散列算法,變換成固定長(zhǎng)度的輸出,該輸出就是散列值(哈希值);這種轉(zhuǎn)換是一種壓縮映射,也就是,散列值的空間通常遠(yuǎn)小于輸入的空間,不同的輸入可能會(huì)散列成相同的輸出,所以不可能從散列值來唯一的確定輸入值。簡(jiǎn)單的說就是一種將任意長(zhǎng)度的消息壓縮到某一固定長(zhǎng)度的消息摘要的函數(shù)。

所有散列函數(shù)都有如下一個(gè)基本特性:根據(jù)同一散列函數(shù)計(jì)算出的散列值如果不同,那么輸入值肯定也不同。但是,根據(jù)同一散列函數(shù)計(jì)算出的散列值如果相同,輸入值不一定相同。

什么是哈希沖突?當(dāng)兩個(gè)不同的輸入值,根據(jù)同一散列函數(shù)計(jì)算出相同的散列值的現(xiàn)象,我們就把它叫做碰撞(哈希碰撞)。

HashMap的數(shù)據(jù)結(jié)構(gòu)

在Java中,保存數(shù)據(jù)有兩種比較簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu):數(shù)組和鏈表。數(shù)組的特點(diǎn)是:尋址容易,插入和刪除困難;鏈表的特點(diǎn)是:尋址困難,但插入和刪除容易;所以我們將數(shù)組和鏈表結(jié)合在一起,發(fā)揮兩者各自的優(yōu)勢(shì),使用一種叫做鏈地址法的方式可以解決哈希沖突:

這樣我們就可以將擁有相同哈希值的對(duì)象組織成一個(gè)鏈表放在hash值所對(duì)應(yīng)的bucket下,但相比于hashCode返回的int類型,我們HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4(即2的四次方16)要遠(yuǎn)小于int類型的范圍,所以我們?nèi)绻皇菃渭兊挠胔ashCode取余來獲取對(duì)應(yīng)的bucket這將會(huì)大大增加哈希碰撞的概率,并且最壞情況下還會(huì)將HashMap變成一個(gè)單鏈表,所以我們還需要對(duì)hashCode作一定的優(yōu)化

hash()函數(shù)

上面提到的問題,主要是因?yàn)槿绻褂胔ashCode取余,那么相當(dāng)于參與運(yùn)算的只有hashCode的低位,高位是沒有起到任何作用的,所以我們的思路就是讓hashCode取值出的高位也參與運(yùn)算,進(jìn)一步降低hash碰撞的概率,使得數(shù)據(jù)分布更平均,我們把這樣的操作稱為擾動(dòng),在JDK 1.8中的hash()函數(shù)如下:

static final inthash(Object key){

int h;

return(key ==null) ?0: (h = key.hashCode()) ^ (h >>>16);// 與自己右移16位進(jìn)行異或運(yùn)算(高低位異或)

}

這比在JDK 1.7中,更為簡(jiǎn)潔,相比在1.7中的4次位運(yùn)算,5次異或運(yùn)算(9次擾動(dòng)),在1.8中,只進(jìn)行了1次位運(yùn)算和1次異或運(yùn)算(2次擾動(dòng));

JDK1.8新增紅黑樹

通過上面的鏈地址法(使用散列表)和擾動(dòng)函數(shù)我們成功讓我們的數(shù)據(jù)分布更平均,哈希碰撞減少,但是當(dāng)我們的HashMap中存在大量數(shù)據(jù)時(shí),加入我們某個(gè)bucket下對(duì)應(yīng)的鏈表有n個(gè)元素,那么遍歷時(shí)間復(fù)雜度就為O(n),為了針對(duì)這個(gè)問題,JDK1.8在HashMap中新增了紅黑樹的數(shù)據(jù)結(jié)構(gòu),進(jìn)一步使得遍歷復(fù)雜度降低至O(logn);

總結(jié)

簡(jiǎn)單總結(jié)一下HashMap是使用了哪些方法來有效解決哈希沖突的:

1. 使用鏈地址法(使用散列表)來鏈接擁有相同hash值的數(shù)據(jù);

2. 使用2次擾動(dòng)函數(shù)(hash函數(shù))來降低哈希沖突的概率,使得數(shù)據(jù)分布更平均;

3. 引入紅黑樹進(jìn)一步降低遍歷的時(shí)間復(fù)雜度,使得遍歷更快;

TreeMap 是采用什么樹實(shí)現(xiàn)的?TreeMap、HashMap、LindedHashMap的區(qū)別。

答:TreeMap的實(shí)現(xiàn)是紅黑樹算法的實(shí)現(xiàn),LinkedHashMap可以保證HashMap集合有序。存入的順序和取出的順序一致。TreeMap實(shí)現(xiàn)SortMap接口,能夠把它保存的記錄根據(jù)鍵排序,默認(rèn)是按鍵值的升序排序,也可以指定排序的比較器,當(dāng)用Iterator 遍歷TreeMap時(shí),得到的記錄是排過序的。HashMap不保證順序,即為無序的,具有很快的訪問速度。HashMap最多只允許一條記錄的鍵為Null;允許多條記錄的值為 Null;HashMap不支持線程的同步。

4.數(shù)組

在java中,聲明一個(gè)數(shù)組過程中,是如何分配內(nèi)存的?

答:1.當(dāng)聲明數(shù)組類型變量時(shí),為其分配了(32位)引用空間,由于未賦值,因此并不指向任何對(duì)象;

2.當(dāng)創(chuàng)建了一個(gè)數(shù)組對(duì)象(也就是new出來的)并將其地址賦值給了變量,其中創(chuàng)建出來的那幾個(gè)數(shù)組元素相當(dāng)于引用類型變量,因此各自占用(32位的)引用空間并按其默認(rèn)初始化規(guī)則被賦值為null

3.程序繼續(xù)運(yùn)行,當(dāng)創(chuàng)建新的對(duì)象并(將其地址)賦值給各數(shù)組元素,此時(shí)堆內(nèi)存就會(huì)有值了

怎么判斷數(shù)組是 null 還是為空

答:1.數(shù)組為null和數(shù)組為空的區(qū)別

數(shù)組為null:是創(chuàng)建了數(shù)組的引用,但在堆中并沒有數(shù)組中的元素

例:

int[] array1 = null;

array1是數(shù)組類型的空引用,棧中名為array1的內(nèi)存空間沒有存放任何地址。

數(shù)組為空:數(shù)組是空其實(shí)就是數(shù)組的長(zhǎng)度為0,數(shù)組是真正的對(duì)象,只是對(duì)象中沒有元素,也就是說里面沒有內(nèi)容

例:

int[] array = {};

此時(shí)創(chuàng)建了數(shù)組,數(shù)組的長(zhǎng)度為0,是一個(gè)空數(shù)組,但是array不是null,它也是一個(gè)對(duì)象,只不過它的元素個(gè)數(shù)為0。

2.判斷數(shù)組是否為空?

判斷數(shù)組為空,使用array.length==0可以

但array==null不可以,這種會(huì)報(bào)錯(cuò),Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1

3.判斷數(shù)組是否為null?

直接使用變量名==null

例:

String[ ]? arr = null;

if(arr == null){......}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容