小總結:ArrayList 和LinkedList的面試題

ArrayList 無參數構造器構造,現在 add 一個值進去,此時數組的大小是多少,下一次擴容前最大可用大小是多少?

此處數組的大小是 1,下一次擴容前最大可用大小是 10,因爲 ArrayList 第一次擴容時,是有默認值的,默認值是 10,在第一次 add 一個值進去時,數組的可用大小被擴容到 10 了。

如果我連續往 Arraylist 裏面新增值,增加到第 11 個的時候,數組的大小是多少?

這裏的考查點就是擴容的公式,當增加到 11 的時候,此時我們希望數組的大小爲 11,但實際上數組的最大容量只有 10,不夠了就需要擴容,擴容的公式是:oldCapacity + (oldCapacity>> 1),oldCapacity 表示數組現有大小,目前場景計算公式是:10 + 10 /2 = 15,然後我們發現 15 已經夠用了,所以數組的大小會被擴容到 15。

數組初始化,被加入一個值後,如果我使用 addAll 方法,一下子加入 15 個值,那麼最終數組的大小是多少?

現在需要一下子加入 15 個值,那我們期望數組的大小值就是 16,此時數組最大可用大小隻有 10,明顯不夠,需要擴容,擴容後的大小是:10 + 10 /2 = 15,這時候發現擴容後的大小仍然不到我們期望的值 16,這時候源碼中有一種策略如下:

// newCapacity 本次擴容的大小,minCapacity 我們期望的數組最小大小
// 如果擴容後的值 < 我們的期望值,我們的期望值就等於本次擴容的大小
if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;

所以最終數組擴容後的大小爲 16。

現在我有一個很大的數組需要拷貝,原數組大小是 5k,請問如何快速拷貝?

因爲原數組比較大,如果新建新數組的時候,不指定數組大小的話,就會頻繁擴容,頻繁擴容就會有大量拷貝的工作,造成拷貝的性能低下,所以回答說新建數組時,指定新數組的大小爲 5k 即可。

爲什麼說擴容會消耗性能?

擴容底層使用的是 System.arraycopy 方法,會把原數組的數據全部拷貝到新數組上,所以性能消耗比較嚴重。

有一個 ArrayList,數據是 2、3、3、3、4,中間有三個 3,現在我通過 for (int i=0;i<list.size ();i++) 的方式,想把值是 3 的元素刪除,請問可以刪除乾淨麼?最終刪除的結果是什麼,爲什麼?刪除代碼如下:

List<String> list = new ArrayList<String>() {{
  add("2");
  add("3");
  add("3");
  add("3");
  add("4");
}};
for (int i = 0; i < list.size(); i++) {
  if (list.get(i).equals("3")) {
    list.remove(i);
  }
}

不能刪除乾淨,最終刪除的結果是 2、3、4,有一個 3 刪除不掉,原因我們看下圖:
在這裏插入圖片描述

還是上面的 ArrayList 數組,我們通過增強 for 循環進行刪除,可以麼?

不可以,會報錯。因爲增強 for 循環過程其實調用的就是迭代器的 next () 方法,當你調用 list#remove () 方法進行刪除時,modCount 的值會 +1,而這時候迭代器中的 expectedModCount 的值卻沒有變,導致在迭代器下次執行 next () 方法時,expectedModCount != modCount 就會報 ConcurrentModificationException 的錯誤。

還是上面的數組,如果刪除時使用 Iterator.remove () 方法可以刪除麼,爲什麼?

可以的,因爲 Iterator.remove () 方法在執行的過程中,會把最新的 modCount 賦值給 expectedModCount,這樣在下次循環過程中,modCount 和 expectedModCount 兩者就會相等。

以上三個問題對於 LinkedList 也是同樣的結果麼?

是的,雖然 LinkedList 底層結構是雙向鏈表,但對於上述三個問題,結果和 ArrayList 是一致的。

ArrayList 和 LinkedList 有何不同?

可以先從底層數據結構開始說起,然後以某一個方法爲突破口深入,比如:最大的不同是兩者底層的數據結構不同,ArrayList 底層是數組,LinkedList 底層是雙向鏈表,兩者的數據結構不同也導致了操作的 API 實現有所差異,拿新增實現來說,ArrayList 會先計算並決定是否擴容,然後把新增的數據直接賦值到數組上,而 LinkedList 僅僅只需要改變插入節點和其前後節點的指

向位置關係即可。 ArrayList 和 LinkedList 應用場景有何不同

ArrayList 更適合於快速的查找匹配,不適合頻繁新增刪除,像工作中經常會對元素進行匹配查詢的場景比較合適,LinkedList 更適合於經常新增和刪除,對查詢反而很少的場景。

ArrayList 和 LinkedList 兩者有沒有最大容量

ArrayList 有最大容量的,爲 Integer 的最大值,大於這個值 JVM 是不會爲數組分配內存空間的,LinkedList 底層是雙向鏈表,理論上可以無限大。但源碼中,LinkedList 實際大小用的是 int 類型,這也說明了 LinkedList 不能超過 Integer 的最大值,不然會溢出。

ArrayList 和 LinkedList 是如何對 null 值進行處理的

ArrayList 允許 null 值新增,也允許 null 值刪除。刪除 null 值時,是從頭開始,找到第一值是 null 的元素刪除;LinkedList 新增刪除時對 null 值沒有特殊校驗,是允許新增和刪除的。

ArrayList 和 LinedList 是線程安全的麼,爲什麼?

當兩者作爲非共享變量時,比如說僅僅是在方法裏面的局部變量時,是沒有線程安全問題的,只有當兩者是共享變量時,纔會有線程安全問題。主要的問題點在於多線程環境下,所有線程任何時刻都可對數組和鏈表進行操作,這會導致值被覆蓋,甚至混亂的情況。

如何解決線程安全問題

Java 源碼中推薦使用 Collections#synchronizedList 進行解決,Collections#synchronizedList 的返回值是 List 的每個方法都加了 synchronized 鎖,保證了在同一時刻,數組和鏈表只會被一個線程所修改,或者採用 CopyOnWriteArrayList 併發 List 來解決

描述下雙向鏈表的新增和刪除

上篇文章我已經畫過圖了。傳送門深入Java集合LinkedList

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章