本文快速回顧了Java中容器的知識點,用做面試複習,事半功倍。html
上篇:主要爲容器概覽,容器中用到的設計模式,List源碼java
中篇:Map源碼git
下篇:Set源碼,容器總結github
Java基礎知識點面試手冊(上)面試
容器主要包括 Collection 和 Map 兩種,Collection 又包含了 List、Set 以及 Queue。算法
數組和集合的區別:編程
TreeSet:基於紅黑樹實現,支持有序性操做,可是查找效率不如 HashSet,HashSet 查找時間複雜度爲 O(1),TreeSet 則爲 O(logN);設計模式
LinkedHashSet:具備 HashSet 的查找效率,且內部使用鏈表維護元素的插入順序。數組
ArrayList:基於動態數組實現,支持隨機訪問;安全
Vector:和 ArrayList 相似,但它是線程安全的;
LinkedList:能夠用它來支持雙向隊列;
HashMap:基於哈希實現;
HashTable:和 HashMap 相似,但它是線程安全的,這意味着同一時刻多個線程能夠同時寫入 HashTable 而且不會致使數據不一致。它是遺留類,不該該去使用它。
ConcurrentHashMap:支持線程安全,而且 ConcurrentHashMap 的效率會更高,由於 ConcurrentHashMap 引入了分段鎖。
LinkedHashMap:使用鏈表來維護元素的順序,順序爲插入順序或者最近最少使用(LRU)順序。
https://blog.csdn.net/Kato_op/article/details/80356618
Fail-fast 機制是 java 集合(Collection)中的一種錯誤機制。 當多個線程對同一個集合的內容進行操做時,就可能會產生 fail-fast 事件。
迭代器在遍歷時直接訪問集合中的內容,而且在遍歷過程當中使用一個modCount變量,
集合中在被遍歷期間若是內容發生變化(增刪改),就會改變modCount的值,
每當迭代器使用 hashNext()/next()遍歷下一個元素以前,都會執行checkForComodification()方法檢測,modCount變量和expectedmodCount值是否相等,
注意,若是集合發生變化時修改modCount值, 恰好有設置爲了expectedmodCount值, 則異常不會拋出.(好比刪除了數據,再添加一條數據)
因此,通常來講,存在非同步的併發修改時,不可能做出任何堅定的保證。
迭代器的快速失敗行爲應該僅用於檢測程序錯誤, 而不是用他來同步。
java.util包下的集合類都是Fail-Fast機制的,不能在多線程下發生併發修改(迭代過程當中被修改).
採用安全失敗(Fail-Safe)機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先copy原有集合內容,在拷貝的集合上進行遍歷。
原理:
因爲迭代時是對原集合的拷貝的值進行遍歷,因此在遍歷過程當中對原集合所做的修改並不能被迭代器檢測到,因此不會出發ConcurrentModificationException
缺點:
迭代器並不能訪問到修改後的內容(簡單來講就是, 迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的)
使用場景:
java.util.concurrent包下的容器都是Fail-Safe的,能夠在多線程下併發使用,併發修改
從 JDK 1.5 以後可使用 foreach 方法來遍歷實現了 Iterable 接口的聚合對象。
List<String> list =newArrayList<>(); list.add("a"); list.add("b"); for(String item : list){ System.out.println(item); }
適配器模式解釋:https://www.jianshu.com/p/93821721bf08
java.util.Arrays#asList() 能夠把數組類型轉換爲 List 類型。
@SafeVarargs public static <T> List<T> asList(T... a)
若是要將數組類型轉換爲 List 類型,應該注意的是 asList() 的參數爲泛型的變長參數,所以不能使用基本類型數組做爲參數,只能使用相應的包裝類型數組。
Integer[ ] arr = {1, 2, 3}; List list = Arrays.asList(arr);
也可使用如下方式生成 List。
List list = Arrays.asList(1 ,2, 3);
實現了 RandomAccess 接口,所以支持隨機訪問。這是理所固然的,由於 ArrayList 是基於數組實現的。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
若是不夠時,須要使用 grow() 方法進行擴容,新容量的大小爲 oldCapacity+(oldCapacity>>1),也就是舊容量的 1.5 倍。
擴容操做須要調用 Arrays.copyOf() 把原數組整個複製到新數組中
所以最好在建立 ArrayList 對象時就指定大概的容量大小,減小擴容操做的次數。
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
add(E e)
首先去檢查一下數組的容量是否足夠
add(int index, E element)
步驟:
步驟:
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }
須要調用 System.arraycopy() 將 index+1 後面的元素都複製到 index 位置上,複製的代價很高。
看到arraycopy(),咱們能夠發現:該方法是由C/C++來編寫的
modCount 用來記錄 ArrayList 結構發生變化的次數。結構發生變化是指添加或者刪除至少一個元素的全部操做,或者是調整內部數組的大小,僅僅只是設置元素的值不算結構發生變化。
在進行序列化或者迭代等操做時,須要比較操做先後 modCount 是否改變,若是改變了須要拋出 ConcurrentModificationException。
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 size as capacity for behavioural compatibility with clone() s.writeInt(size); // 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(); } }
ArrayList 提供了三種方式的構造器:
補充:transient講解
http://www.importnew.com/21517.html
你只須要實現Serilizable接口,將不須要序列化的屬性前添加關鍵字transient,序列化對象的時候,這個屬性就不會序列化到指定的目的地中。
ArrayList 基於數組實現,而且具備動態擴容特性,所以保存元素的數組不必定都會被使用,那麼就不必所有進行序列化。
保存元素的數組 elementData 使用 transient 修飾,該關鍵字聲明數組默認不會被序列化。
transient Object[] elementData; // non-private to simplify nested class access
ArrayList 實現了 writeObject() 和 readObject() 來控制只序列化數組中有元素填充那部份內容。
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }
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 size as capacity for behavioural compatibility with clone() s.writeInt(size); // 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(); } }
序列化時須要使用 ObjectOutputStream 的 writeObject() 將對象轉換爲字節流並輸出。而 writeObject() 方法在傳入的對象存在 writeObject() 的時候會去反射調用該對象的 writeObject() 來實現序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理相似。
ArrayList list = new ArrayList(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); oos.writeObject(list);
可使用 Collections.synchronizedList(); 獲得一個線程安全的 ArrayList。
List<String> list = new ArrayList<>(); List<String> synList = Collections.synchronizedList(list);
也可使用 concurrent 併發包下的 CopyOnWriteArrayList 類。
List<String> list = new CopyOnWriteArrayList<>();
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } } final void setArray(Object[] a) { array = a; } @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; }
CopyOnWriteArrayList 在寫操做的同時容許讀操做,大大提升了讀操做的性能,所以很適合讀多寫少的應用場景。
基於雙向鏈表實現,內部使用 Node 來存儲鏈表節點信息。
private static class Node<E> { E item; Node<E> next; Node<E> prev; }
每一個鏈表存儲了 Head 和 Tail 指針:
transient Node<E> first; transient Node<E> last;
set方法和get方法其實差很少,根據下標來判斷是從頭遍歷仍是從尾遍歷
LinkedList實現了Deque接口,所以,咱們能夠操做LinkedList像操做隊列和棧同樣
LinkedList的方法比ArrayList的方法多太多了,這裏我就不一一說明了。具體可參考:
本人目前爲後臺開發工程師,主要關注Python爬蟲,後臺開發等相關技術。
擁有專欄:Leetcode題解(Java/Python)、Python爬蟲開發
https://www.zhihu.com/people/yang-zhen-dong-1/
擁有專欄:碼農面試助攻手冊
https://juejin.im/user/5b48015ce51d45191462ba55
https://www.jianshu.com/u/b5f225ca2376