天天看點

Java工程師面試1000題51-60

51、講一講ArrayList的内部實作。

回答這樣的問題,不要光回答個皮毛,可以詳細介紹一下ArrayList内部是如何實作數組的增加和删除的,要知道,數組在建立的時候長度是固定的,那麼我們往ArrayList中不斷添加對象的時候,它是如何管理的呢?

ArrayList内部是使用Object[]數組實作的,接下來,我們分别分析一下ArrayList的構造、add、remove、clear方法的具體實作原理(以JDK1.8為例)。

①空參構造:

Java工程師面試1000題51-60

elementData是ArrayList的一個屬性,是一個Object[]數組,源碼如下:

Java工程師面試1000題51-60
DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一個為空的Object[]數組,源碼如下:      
Java工程師面試1000題51-60

也就是說,當我們new一個空參的ArrayList的時候,系統内部實作了一個new Object[0]的數組。

②帶參構造1:

Java工程師面試1000題51-60

該構造函數傳入一個int值,該值如果大于0,則new一個int值容量的Object[]數組指派給elementData,如果等于0,則将EMPTY_ELEMENTDATA(源碼如下)指派給elementData,小于0抛出異常。

Java工程師面試1000題51-60

EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是相同的。

③帶參構造2:

Java工程師面試1000題51-60

如果調用構造函數的時候傳入了一個Collection的子類,則将該集合轉換為數組并指派給elementData,如果這個數組的長度不等于0,則會驗證一下數組的getClass是否等于Object,如果不等于則會重新強轉一遍,至于為什麼這樣做,jdk中有注釋c.toArray might (incorrectly) not return Object[] (see 6260652)see 6260652 這個編号代表JDK bug庫中的編号。可以去官網檢視bug詳情:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652  ,如果elementData數組的getClass等于Object不做任何操作,如果不等于,則再調用Arrays的copyOf方法轉為Object[]類型。

④add方法:

add方法有兩個重載,分别如下:

Java工程師面試1000題51-60
Java工程師面試1000題51-60

(判斷方法重載的依據:1、 必須是在同一個類中2、 方法名相同3、 方法參數的個數、順序或類型不同4、 與方法的修飾符或傳回值沒有關系。隻有前三條都符合了,第四條才有效)

先看第一個add方法,首先調用ensureCapacityInternal方法,将目前的size+1傳遞給ensureCapacityInternal方法,ensureCapacityInternal方法:

Java工程師面試1000題51-60
Java工程師面試1000題51-60

確定數組已使用長度(size)加1之後足夠存下 下一個資料;修改次數modCount 辨別自增1,如果目前數組已使用長度(size)加1後的大于目前的數組長度,則調用grow方法,增長數組,grow方法會将目前數組的長度變為原來容量的1.5倍;確定新增的資料有地方存儲之後,則将新元素添加到位于size的位置上。傳回添加成功布爾值。

第二個add方法其實和上面的add類似,該方法可以按照元素的位置,指定位置插入元素,具體的執行邏輯如下:

1)確定數插入的位置小于等于目前數組長度,并且不小于0,否則抛出異常

2)確定數組已使用長度(size)加1之後足夠存下 下一個資料

3)修改次數(modCount)辨別自增1,如果目前數組已使用長度(size)加1後的大于目前的數組長度,則調用grow方法,增長數組

4)grow方法會将目前數組的長度變為原來容量的1.5倍。

5)確定有足夠的容量之後,使用System.arraycopy 将需要插入的位置(index)後面的元素統統往後移動一位。

6)将新的資料内容存放到數組的指定位置(index)上

⑤remove方法:

Java工程師面試1000題51-60
Java工程師面試1000題51-60

remove方法有兩個重載,分别是根據索引remove和根據對象remove。

根據索引remove:1)判斷索引有沒有越界;2)自增修改次數;3)将指定位置(index)上的元素儲存到oldValue;4)将指定位置(index)上的元素都往前移動一位;5)将最後面的一個元素置空,好讓垃圾回收器回收;6)将原來的值oldValue傳回(注意:調用這個方法不會縮減數組的長度,隻是将最後一個數組元素置空而已。)

根據對象remove:循環周遊所有對象,得到對象所在索引位置,然後調用fastRemove方法,執行remove操作

⑥clear方法:

Java工程師面試1000題51-60

添加操作次數(modCount),将數組内的元素都置空,等待垃圾收集器收集,不減小數組容量。

52、并發集合和普通集合如何差別?

在Java中,有普通集合、同步的集合(即線程安全的集合)、并發集合。并發集合常見的有

ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque

等。并發集合位于

java.util.concurrent

包下,是在JDK1.5之後才有的。

普通集合通常性能最高,但是不保證多線程的安全性和并發的可靠性;線程安全集合僅僅是給集合添加了

synchronized(同步的)

同步鎖,嚴重影響了性能,而且對并發的效率就更低了;并發集合通過複雜的政策不僅保證了多線程的安全,又提高了并發時的效率。

53、Java中ArrayList和LinkedList、Vector的差別?

ArrayList是一個可以處理變長數組的類型,可以存放任意類型的對象。ArrayList的所有方法都是預設在單一線程下進行的,是以ArrayList不具有線程安全性。

LinkedList可以看做為一個循環雙向連結清單,LinkedList也是線程不安全的,在LinkedList的内部實作中,并不是用普通的數組來存放資料的,而是使用結點<Node>來存放資料的,有一個指向連結清單頭的結點first和一個指向連結清單尾的結點last。LinkedList的插入方法的效率要高于ArrayList,但是查詢的效率要低一點。LinkedList是由一系清單項連接配接而成的,一個表項總是包含三部分:元素内容、前去表、後繼表。

Vector也是一個類似于ArrayList的可變長度的數組類型,它的内部也是使用數組來存放資料對象的。值得注意的是Vector與ArrayList唯一的差別是,Vector是線程安全的。在擴充容量的時候,Vector是擴充為原來的2倍,而ArrayList是擴充為原來的1.5倍。

54、數組和連結清單産生的背景?

在計算機中要對給定的資料集進行若幹處理,首要任務就是先把資料集的一部分(當資料量非常大時,可能隻可以一部分一部分讀取資料到記憶體中來處理)或者全部存儲到記憶體中,然後再對記憶體中的資料進行各種處理。

當記憶體空間中有足夠大的連續空間時,可以把資料連續的存放在記憶體中,各種程式設計語言中的數組一般都是按這種方式存儲的(也可能有例外),但是,當記憶體中隻有一些零散的可用空間時,想要連續存儲資料就變得比較困難了,這時候能想到的一種解決方式就是移動記憶體中的資料,把離散的記憶體空間進行整理,聚內建一塊連續的大空間,但是這種情況下就有可能要移動别人的資料了,保不齊在移動的過程中會把重要的資料丢失,另外一種,不影響别人的資料存儲方式就是把集中地資料分開離散地存儲到這些不連續空間中,這時候,為了能把所有資料可以聯系起來,需要在前一塊資料的存儲空間中記錄下一塊資料的位址,這樣隻要知道第一塊記憶體空間的位址就能環環相扣把所有資料都聯系起來了。C/C++中用指針實作的連結清單就是這種存儲形式。由上可知,記憶體中的存儲形式可以分為連續性存儲和離散型存儲,他們就對應了我們常說的數組和連結清單。

55、數組和連結清單的優缺點?

數組是将元素在記憶體中連續存儲的,它的優點是因為資料是連續存儲的,記憶體位址連續,是以在查找資料的時候效率更高,缺點是:在存儲之前,我們需要申請一塊連續的記憶體空間,并且在編譯的時候就必須确定好他的空間大小,在運作的時候空間大小是無法随着你的需要進行增加和減少的,當資料量超出了申請的記憶體空間時,就會出現越界情況。資料量小于所申請的記憶體空間時,又會出現記憶體浪費。并且,數組在增加和插入、删除資料的時候效率比較低。

連結清單是動态申請的記憶體空間,不需要向數組那樣需要提前申請好記憶體空間,連結清單隻需要在用的時候申請就可以了,根據需要動态的申請或者删除空間,對于資料的增加和删除比較靈活,還有就是連結清單中的資料在記憶體中可以存儲在任何位置,通過存在元素的指針來聯系資料。

56、數組和連結清單的使用場景?

數組的使用場景:資料較少,經常做的運算是按照序号通路資料的操作,數組更容易實作,任何進階語言都支援。

連結清單的使用場景:對線性表的長度或者規模難以估計,頻繁的做插入或者删除操作的時候使用。

57、說一下你對ConcurrentHashMap的了解。

ConcurrentHashMap是線程安全的HashMap的實作,有三個重要屬性,initialCapacity預設值為16,loadFactor屬性預設值0.75,concurrencyLevel屬性預設值是16.其内部使用了鎖分段技術,維持這鎖的是Segment的數組,在Segment數組中又存放着Entity[]數組,内部使用hash算法将資料較為均勻的分布在不同鎖内。

ConcurrentHashMap的put方法上沒有加上synchronized,執行時,首先使用key.hashcode對key進行hash操作,得到key的hash值,hash操作的算法也和map也不同,根據此hash值計算并擷取其對應的數組中的Segment對象(繼承自ReentrantLock),接着調用此Segment對象的put方法來完成目前操作。ConcurrentHashMap基于concurrencyLevel劃分出了多個Segment來對key-value進行存儲,進而避免每次put操作都得鎖住整個數組,在預設情況下,最佳情況下可運作16個線程并發無阻塞的操作集合對象,最大程度上減少并發時的阻塞現象。

ConcurrentHashMap進行get操作時,首先對key進行hash運算,基于對應的hash值找到對應的Segment對象,調用其get方法完成目前操作。而Segment的get操作會首先通過hash值和對象數組大小減1的值進行按位與操作來擷取數組上對應位置的HashEntry,在這個步驟中可能會因為對象數組大小的改變,以及數組上對應位置的HashEntry産生不一緻,那麼ConcurrentHashMap是怎麼保證的呢?那是因為對象數組大小的改變隻有在put操作時可能發生,由于HashEntry對象數組對應的變量是volatile類型的,是以可以保證如果HashEntry對象數組大小發生改變,讀操作可以看到最新的對象數組大小。

在擷取到了HashEntry對象之後,怎麼能保證它及其next屬性構成的連結清單上的對象不會改變呢?這點ConcurrentHashMap采用了一個簡單的方式,即HashEntry對象的hash、key、next屬性都是final的,這就意味着沒辦法插入一個HashEntry對象到基于next屬性構成的連結清單中間或者末尾。這樣就保證當擷取到HashEntry對象後,其基于next屬性建構的連結清單是不會發生變化的。

ConcurrentHashMap預設情況下采用将資料分為16個段進行存儲,并且16個段分别持有各自不同的鎖Segment,鎖僅用于put和remove等改變集合對象的操作,基于volatile及HashEntry連結清單的不變性實作了讀取的不加鎖。這些方式使得ConcurrentHashMap能夠保持極好的并發支援,尤其是對于讀遠比插入和删除頻繁的Map而言。ConcurrentHashMap采用的這些方法真可謂是對Java記憶體模型、并發機制有着深刻見解和牢牢掌握的展現!

58、List a = new ArrayList();和ArrayList a = new ArrayList();的差別?

List a = new ArrayList();這一句建立了一個ArrayList後把其上溯到了List了,此時a他是一個List對象了,有些ArrayList有但是List沒有的屬性和方法,他就不能使用了(例如ArrayList中有trimToSize方法,但是List中就沒有);而ArrayList a = new ArrayList();是建立了一個ArrayList對象,保留了ArrayList的所有屬性,是以需要使用ArrayList獨有的方法時候,不能使用前者。

59、Comparable和Comparator接口是什麼?

Comparable和Comparator接口被用來對對象集合或者數組進行排序。Comparable接口被用來提供對象的自然排序,我們可以使用它來提供基于單個邏輯的排序。Comparator接口被用來提供不同的排序算法,我們可以選擇需要使用的Comparator來對給定的對象集合進行排序。

如果我們想使用Array或Collection的排序方法時,需要在自定義類裡實作Java提供Comparable接口。Comparable接口有compareTo(T OBJ)方法,它被排序方法所使用。我們應該重寫這個方法,如果“this”對象比傳遞的對象參數更小、相等或更大時,它傳回一個負整數、0或正整數。但是,在大多數實際情況下,我們想根據不同參數進行排序。比如,作為一個CEO,我想對雇員基于薪資進行排序,一個HR想基于年齡對他們進行排序。這就是我們需要使用Comparator接口的情景,因為Comparable.compareTo(Object o)方法實作隻能基于一個字段進行排序,我們不能根據對象排序的需要選擇字段。Comparator接口的compare(Object o1, Object o2)方法的實作需要傳遞兩個對象參數,若第一個參數比第二個小,傳回負整數;若第一個等于第二個,傳回0;若第一個比第二個大,傳回正整數。

60、Collections類是什麼?

Java.util.Collections是一個工具類僅包含靜态方法,它們操作或傳回集合。它包含操作集合的多态算法,傳回一個由指定集合支援的新集合和其它一些内容。這個類包含集合架構算法的方法,比如折半搜尋、排序、混編和逆序等。