Java 集合類知識點

1. 你瞭解哪些集合類型?

你應該知道如下幾個最重要的類型:java

  • ArrayList
  • LinkedList
  • HashMap
  • HashSet

以後,你可能會被問到這樣一些問題,好比應該什麼時候使用此種特定類型,它比其餘的好在哪裏,它是怎麼存儲數據的以及隱匿在背後的數據結構是什麼。最好的方法是儘量多地瞭解這些集合類型,由於這類問題幾乎是無窮盡的。面試

2. HashMap 有什麼特色?

HashMap 基於Map接口實現,存儲鍵值對時,能夠接收 null 爲鍵值。HashMap 是非同步的。算法

3. HashMap 的工做原理是怎樣的?

HashMap 在 Map.Entry 靜態內部類實現中存儲鍵值對,使用哈希算法。在 put 和 get 方法中,使用 hashCode() 和 equals() 方法。設計模式

  • 調用 put 方法時,使用鍵值對中的 Key hashCode() 和哈希算法找出存儲鍵值對索引。鍵值對 Entry 存儲在 LinkedList 中,若是存在 Entry,使用 equals() 方法來檢查 Key 是否已經存在:若是存在,則覆蓋 value;若是不存在,會建立一個新的 Entry 而後保存。
  • 調用 get 方法時,HashMap 使用鍵值 Key hashCode() 來找到數組中的索引,而後使用 equals() 方法找出正確的 Entry,返回 Entry 中的 Value。

HashMap 中容量、負荷係數和閥值是重要的參數。HashMap 默認的初始容量是32,負荷係數是0.75。閥值 = 負荷係數 x 容量。添加 Entry時,若是 Map 的大小 > 閥值,HashMap 會對 Map 的內容從新哈希,使用更大的容量(容量老是2的冪)。關於 JDK 中的 hash 算法實現以及由此引起的哈希碰撞現象(DDos攻擊)均可能是面試的延伸問題。數組

4. 可否使用任何類做爲 Map 的 key?

可使用任何類做爲 Map 的 key,然而在使用以前,須要考慮如下幾點:緩存

  • 若是類重寫了 equals() 方法,也應該重寫 hashCode() 方法。
  • 類的全部實例須要遵循與 equals() 和 hashCode() 相關的規則。
  • 若是一個類沒有使用 equals(),不該該在 hashCode() 中使用它。
  • 用戶自定義 Key 類最佳實踐是使之爲不可變的,這樣 hashCode() 值能夠被緩存起來,擁有更好的性能。不可變的類也能夠確保 hashCode() 和 equals() 在將來不會改變,這樣就會解決與可變相關的問題了。

若是有一個類 MyKey,在 HashMap 中使用它:安全

HashMap<MyKey, String> myHashMap = new HashMap<MyKey, String>();

//傳遞給 MyKey 的 name 參數被用於 equals() 和 hashCode() 中

MyKey key = new MyKey("Pankaj"); // 假設 hashCode=1234
myHashMap.put(key, "Value");


// 如下的代碼會改變 key 的 hashCode() 和 equals() 值
key.setName("Amit"); // 假設新的 hashCode=7890

//下面會返回 null,由於 HashMap 會嘗試查找存儲一樣索引的 key,而 key 已被改變了,匹配失敗,返回 null
System.out.println(myHashMap.get(new MyKey("Pankaj")));

這就是爲何 String 一般會用做 HashMap 的 Key,由於 String 的設計是不可變的(immutable)。數據結構

5. 插入數據時,ArrayList、LinkedList、Vector誰速度較快?

ArrayList、LinkedList、Vector 底層的實現都是使用數組方式存儲數據。數組元素數大於實際存儲的數據以便增長和插入元素,它們都容許直接按序號索引元素,可是插入元素要涉及數組元素移動等內存操做,因此索引數據快而插入數據慢。多線程

  • Vector 中的方法因爲加了 synchronized 修飾,所以 Vector 是線程安全容器,但性能上較ArrayList差。
  • LinkedList 使用雙向鏈表實現存儲,按序號索引數據須要進行前向或後向遍歷,但插入數據時只須要記錄當前項的先後項便可,因此 LinkedList 插入速度較快。
6. 多線程場景下如何使用 ArrayList?

ArrayList 不是線程安全的,若是遇到多線程場景,能夠經過 Collections 的 synchronizedList 方法將其轉換成線程安全的容器後再使用。例如像下面這樣:框架

List<String> synchronizedList = Collections.synchronizedList(list);

synchronizedList.add("aaa");

synchronizedList.add("bbb");

for (int i = 0; i < synchronizedList.size(); i++)

{

    System.out.println(synchronizedList.get(i));

}

使用Collections 的 synchronizedList 方法將其轉換成線程安全的容器後再使用。

7. 說一下 ArrayList 的優缺點

ArrayList的優勢以下:

  • ArrayList 底層以數組實現,是一種隨機訪問模式。ArrayList 實現了 RandomAccess 接口,所以查找的時候很是快。
  • ArrayList 在順序添加一個元素的時候很是方便。

ArrayList 的缺點以下:

  • 刪除元素的時候,須要作一次元素複製操做。若是要複製的元素不少,那麼就會比較耗費性能。
  • 插入元素的時候,也須要作一次元素複製操做,缺點同上。

ArrayList 比較適合順序添加、隨機訪問的場景。

8. 爲何 ArrayList 的 elementData 加上 transient 修飾?

ArrayList 中的數組定義以下:

private transient Object[] elementData;

再看一下 ArrayList 的定義:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

能夠看到 ArrayList 實現了 Serializable 接口,這意味着 ArrayList 支持序列化。transient 的做用是說不但願 elementData 數組被序列化,重寫了 writeObject 實現:

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();
        // Write out array length
    s.writeInt(elementData.length);
        // Write out all elements in the proper order.
    for (int i=0; i<size; i++)
        s.writeObject(elementData[i]);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }

每次序列化時,先調用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,而後遍歷 elementData,只序列化已存入的元素,這樣既加快了序列化的速度,又減少了序列化以後的文件大小。

9. 遍歷一個 List 有哪些不一樣的方式?每種方法的實現原理是什麼?Java 中 List 遍歷的最佳實踐是什麼?

遍歷方式有如下幾種:

  • for 循環遍歷,基於計數器。在集合外部維護一個計數器,而後依次讀取每個位置的元素,當讀取到最後一個元素後中止。
  • 迭代器遍歷,Iterator。Iterator 是面向對象的一個設計模式,目的是屏蔽不一樣數據集合的特色,統一遍歷集合的接口。Java 在 Collections 中支持了 Iterator 模式。
  • foreach 循環遍歷。foreach 內部也是採用了 Iterator 的方式實現,使用時不須要顯式聲明 Iterator 或計數器。優勢是代碼簡潔,不易出錯;缺點是隻能作簡單的遍歷,不能在遍歷過程當中操做數據集合,例如刪除、替換。

最佳實踐:Java Collections 框架中提供了一個 RandomAccess 接口,用來標記 List 實現是否支持 Random Access。

  • 若是一個數據集合實現了該接口,就意味着它支持 Random Access,按位置讀取元素的平均時間複雜度爲 O(1),如ArrayList。
  • 若是沒有實現該接口,表示不支持 Random Access,如LinkedList。

推薦的作法就是,支持 Random Access 的列表可用 for 循環遍歷,不然建議用 Iterator 或 foreach 遍歷。

10. 如何邊遍歷邊移除 Collection 中的元素?

邊遍歷邊修改 Collection 的惟一正確方式是使用 Iterator.remove() 方法,以下:

Iterator<Integer> it = list.iterator();
while(it.hasNext()){
    // do something
    it.remove();
}

一種最多見的錯誤代碼以下:

for(Integer i : list){
    list.remove(i)
}

運行以上錯誤代碼會報 ConcurrentModificationException 異常。這是由於當使用 foreach(for(Integer i : list)) 語句時,會自動生成一個iterator 來遍歷該 list,但同時該 list 正在被 Iterator.remove() 修改。Java 通常不容許一個線程在遍歷 Collection 時另外一個線程修改它。


原文地址:https://mp.weixin.qq.com/s/Im3JbJk2IuzlSXenoLQK6Q

相關文章
相關標籤/搜索