1. 程式人生 > >JAVA面試常問知識總結(四)——集合

JAVA面試常問知識總結(四)——集合

先附一張java集合框架圖

 

下面根據面試中常問的關於集合的問題進行了梳理:

Arraylist 與 LinkedList 有什麼不同?

  • 1. 是否保證執行緒安全: ArrayList 和 LinkedList 都是不同步的,也就是不保證執行緒安全;

  • 2. 底層資料結構: Arraylist 底層使用的是Object陣列;LinkedList 底層使用的是雙向連結串列資料結構(注意雙向連結串列和雙向迴圈連結串列的區別);

  • 3. 插入和刪除是否受元素位置的影響: ① ArrayList 採用陣列儲存,所以插入和刪除元素的時間複雜度受元素位置的影響。

     比如:執行add(E e)方法的時候, ArrayList 會預設在將指定的元素追加到此列表的末尾,這種情況時間複雜度就是O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element))時間複雜度就為 O(n-i)。因為在進行上述操作的時候集合中第 i 和第 i 個元素之後的(n-i)個元素都要執行向後位/向前移一位的操作。 ② LinkedList 採用連結串列儲存,所以插入,刪除元素時間複雜度不受元素位置的影響,都是近似 O(1)而陣列為近似 O(n)。

  • 4. 是否支援快速隨機訪問: LinkedList 不支援高效的隨機元素訪問,而 ArrayList 支援。快速隨機訪問就是通過元素的序號快速獲取元素物件(對應於get(int index)

    方法)。

  • 5. 記憶體空間佔用: ArrayList的空間浪費主要體現在在list列表的結尾會預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗比ArrayList更多的空間(因為要存放直接後繼和直接前驅以及資料)。

  補充內容:RandomAccess介面

1 public interface RandomAccess {
2 }

 檢視原始碼我們發現實際上 RandomAccess 介面中什麼都沒有定義。所以,在我看來 RandomAccess 介面不過是一個標識罷了。標識什麼? 標識實現這個介面的類具有隨機訪問功能。在binarySearch()方法中,它要判斷傳入的list 是否RamdomAccess的例項,如果是,呼叫indexedBinarySearch()方法,如果不是,那麼呼叫iteratorBinarySearch()方法

1 public static <T>
2     int binarySearch(List<? extends Comparable<? super T>> list, T key) {
3         if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
4             return Collections.indexedBinarySearch(list, key);
5         else
6             return Collections.iteratorBinarySearch(list, key);
7     }

  ArraysList 實現了 RandomAccess 介面, 而 LinkedList 沒有實現。為什麼呢?還是和底層資料結構有關!ArraysList 底層是陣列,而 LinkedList 底層是連結串列。陣列天然支援隨機訪問,時間複雜度為 O(1),所以稱為快速隨機訪問。連結串列需要遍歷到特定位置才能訪問特定位置的元素,時間複雜度為 O(n),所以不支援快速隨機訪問。,ArraysList 實現了 RandomAccess 介面,就表明了他具有快速隨機訪問功能。 RandomAccess 介面只是標識,並不是說 ArraysList 實現 RandomAccess 接口才具有快速隨機訪問功能的!

下面再總結一下 list 的遍歷方式選擇:

 

  • 實現了RadomAcces介面的list,優先選擇普通for迴圈 ,其次foreach  (採用ArrayList對隨機訪問比較快,而for迴圈中的get()方法,採用的即是隨機訪問的方法,因此在ArrayList裡,for迴圈較快)

 

  • 未實現RadomAcces介面的ist,優先選擇iterator遍歷(foreach遍歷底層也是通過iterator實現的,採用LinkedList則是順序訪問比較快,iterator中的next()方法,採用的即是順序訪問的方法,因此在LinkedList裡,使用iterator較快)

  ps:如果在涉及到集合元素的刪除操作時,一般呼叫刪除和新增方法都是具體集合的方法,例如:List list = new ArrayList(); list.add(...); list.remove(...);但是,如果在迴圈的過程中呼叫集合的remove()方法,就會導致迴圈出錯,因為迴圈過程中list.size()的大小變化了,就導致了錯誤。 所以,如果想在迴圈語句中刪除集合中的某個元素,就要用迭代器iterator的remove()方法,因為它的remove()方法不僅會刪除元素,還會維護一個標誌,用來記錄目前是不是可刪除狀態,例如,你不能連續兩次呼叫它的remove()方法,呼叫之前至少有一次next()方法的呼叫。

forEach不是關鍵字,關鍵字還是for,語句是由iterator實現的,它們最大的不同之處就在於remove()方法上。forEach就是為了讓用iterator迴圈訪問的形式簡單,寫起來更方便。當然功能不太全,所以但如有刪除操作,還是要用它原來的形式

 1 public static void remove(List<String> list, String target) {
 2     for (String item : list) {
 3         if (item.equals(target)) {
 4             list.remove(item);
 5             break;  //增強 for 迴圈中刪除元素後繼續迴圈會報 java.util.ConcurrentModificationException 異常,因為元素在使用的時候發生了併發的修改,導致異常丟擲,但是刪除完畢馬上使用 break 跳出,則不會觸發報錯。
 6         }
 7     }
 8 }
 9 
10 
11 public static void remove(List<String> list, String target) {
12     Iterator<String> iter = list.iterator();
13     while (iter.hasNext()) {
14         String item = iter.next();
15         if (item.equals(target)) {
16             iter.remove();
17         }
18     }
19 }

HashMap的底層是如何實現?