關注公衆號:Java架構師聯盟,每日更新技術好文java
部分資料已經上傳到個人git倉庫中:有須要的能夠下載node
做爲一個程序員,一般,咱們的程序須要根據程序運行時才知道建立多少個對象。但若非程序運行,程序開發階段,咱們根本不知道到底須要多少個數量的對象,甚至不知道它的準確類型。爲了知足這些常規的編程須要,咱們要求能在任什麼時候候,任何地點建立任意數量的對象,而這些對象用什麼來容納呢?咱們首先想到了數組,可是數組只能放同一類型的數據,並且其長度是固定的,那怎麼辦呢?集合便應運而生了!程序員
而對於集合的相關內容,我經過思惟導圖整理了一下,分紅兩個部分,Map和Collection兩個部分,來看一下面試
由於篇幅緣由,只展現Collection的List,這也是在平常開發中應用最多的,或者說在面試過程當中被問到最多的一個知識點,由於它能夠擴充出來不少其餘的知識點編程
ArrayList實現與List、RandomAccess接口,是順序接口,即元素存放的數據與放進去的順序相同,容許放入null元素,也支持隨機訪問數組
底層經過數組實現。除該類未實現同步外,其他跟Vector大體相同數據結構
ArrayList至關於動態數據,其中最重要的兩個屬性分別是:elementData數組以及siz架構
爲了追求效率,ArrayList沒有實現同步(synchronizd),若是須要逗哥線程併發訪問,用戶能夠手動同步,也可使用Vector代替。如能夠先採用Collections.synchronizedList()方法對其進行包裝併發
在調用add()方法的時候首先進行擴容校驗,將插入的值放在尾部,並將size+1.
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
若是調用add(index,e)在指定位置添加的話也是首先擴容校驗,接着對數據進行復制,目的是把index位置空出來放本次插入的數據,並將後面的數據向後移動一個位置。
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //複製,向後移動 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
其實擴容最終調用的代碼
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); }
也是一個數組複製的過程。
注意:
因而可知ArrayList的主要消耗時數組擴容以及在指定位置添加數據,在平常使用時最好是指定大小,儘可能減小擴容。更要減小在指定位置插入數據的操做。
因爲ArrayList是基於動態宿主實現的,因此並非全部的空間都被使用。所以使用了transient修飾。能夠防止被自動序列化
transient Object[] elementData; // non-private to simplify nested class access
所以ArrayList自定義了序列化和反序列化
//序列化 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(); } } //反序列化 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 int capacity = calculateCapacity(elementData, size); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, 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(); } } }
當對象中自定義了writeObject和readObject方法時,JVM會調用這兩個自定義方法來實現序列化和反序列化
從實現中能夠看出ArrayList值序列化了被使用的數據
Vector也會是實現List接口,底層數據結構和ArrayList相似,也是一個動態數組存放的數據,不過在add()方法的時候使用synchronized進行同步數據,可是開銷較大,因此Vector是一個同步容器並非併發容器。
add()方法
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
add(index,e)方法:指定位置插入數據
public void add(int index, E element) { insertElementAt(element, index); } public synchronized void insertElementAt(E obj, int index) { modCount++; if (index > elementCount) { throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount); } ensureCapacityHelper(elementCount + 1); System.arraycopy(elementData, index, elementData, index + 1, elementCount - index); elementData[index] = obj; elementCount++; }
假若有20個數據須要添加,那麼會分別在第一次的時候將ArrayList
以後擴容會按照1.5倍增加,也就是當添加第11個數據的時候,ArrayList繼續擴容變爲10*1.5=15;
當添加第16個數據時。繼續擴容15*1.5=22個
不能超過int的最大值(231-1)-8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
LinkedList底層是基於雙向鏈表實現的;
自己實現了List和Deque(雙端隊列)接口
擁有List的一些特性(jdk1.7/1.8以後取消了循環,修改成雙向鏈表)
既能夠看作一個順序容器,又能夠看作一個隊列(Queue),同時又能夠看作一個棧(stack)。這樣看來,LinkedList簡直是個全能冠軍。當你須要使用棧或者隊列時,能夠考慮使用LinkedList,一方面是由於Java官方已經聲明不建議使用Stack類,更遺憾的是,Java里根本沒有一個叫作Queue的類(它是一個接口名)。關於棧或者隊列,如今的首選是ArrayDeque,它有着比LinkedList(當作棧或者隊列使用時)更好的性能。
public boolean add(E e) { linkLast(e); return true; } void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
可見每次插入都是移動指針,和ArrayList的拷貝數組來講效率要高上很多
public E get(int index) { checkElementIndex(index); return node(index).item; } Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
上述代碼,利用了雙向鏈表的特性,若是index離鏈表頭比較近,就從節點頭部遍歷。不然就從節點尾部開始遍歷。使用空間(雙向鏈表)來換取時間
node會以O(n/2)的性能去獲取一個節點
若是索引值大於鏈表大小的一半,那麼將從尾節點開始遍歷,這樣的效率是很是低的,特別是當index越接近size的中間值
LinkedList插入、刪除都是移動指針效率很高
產訊須要遍歷進行查詢,效率較低
怎麼樣,不知道總結的詳細不,看完以後有沒有一點收穫,而這一些知識的整理也參考了下面這份文檔的內容
Set
List
map
而這份文檔除了集合以外,還包括這些知識,我想不算所有,這裏面大部分對如今的你都應該有所幫助吧
你比別人強的地方,不是你作過多少年的CRUD工做,而是你比別人掌握了更多深刻的技能。不要總停留在CRUD的表面工做,理解並掌握底層原理並熟悉源碼實現,並造成本身的抽象思惟能力,作到靈活運用,纔是你突破瓶頸,脫穎而出的重要方向!
你在刷抖音,玩遊戲的時候,別人都在這裏學習,成長,提高,人與人最大的差距其實就是思惟。你可能不信,優秀的人,老是在一塊兒。