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 }