1. 你瞭解哪些集合類型?
你應該知道如下幾個最重要的類型:java
以後,你可能會被問到這樣一些問題,好比應該什麼時候使用此種特定類型,它比其餘的好在哪裏,它是怎麼存儲數據的以及隱匿在背後的數據結構是什麼。最好的方法是儘量多地瞭解這些集合類型,由於這類問題幾乎是無窮盡的。面試
2. HashMap 有什麼特色?
HashMap 基於Map接口實現,存儲鍵值對時,能夠接收 null 爲鍵值。HashMap 是非同步的。算法
3. HashMap 的工做原理是怎樣的?
HashMap 在 Map.Entry 靜態內部類實現中存儲鍵值對,使用哈希算法。在 put 和 get 方法中,使用 hashCode() 和 equals() 方法。設計模式
HashMap 中容量、負荷係數和閥值是重要的參數。HashMap 默認的初始容量是32,負荷係數是0.75。閥值 = 負荷係數 x 容量。添加 Entry時,若是 Map 的大小 > 閥值,HashMap 會對 Map 的內容從新哈希,使用更大的容量(容量老是2的冪)。關於 JDK 中的 hash 算法實現以及由此引起的哈希碰撞現象(DDos攻擊)均可能是面試的延伸問題。數組
4. 可否使用任何類做爲 Map 的 key?
可使用任何類做爲 Map 的 key,然而在使用以前,須要考慮如下幾點:緩存
若是有一個類 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 底層的實現都是使用數組方式存儲數據。數組元素數大於實際存儲的數據以便增長和插入元素,它們都容許直接按序號索引元素,可是插入元素要涉及數組元素移動等內存操做,因此索引數據快而插入數據慢。多線程
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 比較適合順序添加、隨機訪問的場景。
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 遍歷的最佳實踐是什麼?
遍歷方式有如下幾種:
最佳實踐:Java Collections 框架中提供了一個 RandomAccess 接口,用來標記 List 實現是否支持 Random Access。
推薦的作法就是,支持 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 時另外一個線程修改它。