我在想每一個人在面試的時候都會被問到集合相關的問題,有好大一部分人在回答的時候並無那麼多的邏輯性,一般都是想到哪裏說到哪裏,這篇文章大概的捋一捋關於集合的相關問題。java
在每種編程語言中,都會有循環、數組、流程控制語句,數組是一種線性表數據結構,內存空間是連續的,保存的數據類型也是一致的。面試
正是由於這兩點,數組的隨機訪問纔會很是的高效,這同時也是一把雙刃劍,使得數組的其餘操做效率變得很低,好比說,增長,刪除,爲了保持數組裏面數據的連續性,就會作大量的消耗性能的數據遷移操做。編程
針對數組這種類型,java中有容器類,好比ArrayList,ArrayList是對數組的包裝,在底層就是數組實現的,由於數組在定義的時候必須是指定的長度,定義以後就沒法再增長長度了,就是說不可能在原來的數組上接上一段,因此ArrayList就解決了這個問題,當超過數組容量的時候,ArrayList會進行擴容,擴容以後的容量是以前的1.5倍,而後再把以前數組中的數據複製過來。數組
接下來,咱們看下ArrayList的源碼:安全
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
複製代碼
源碼中數組最大的容量是Integer.MAX_VALUE -8,爲何要減去8 呢,這個是上面的定義上面的註釋:bash
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
複製代碼
首先是一些VM在數組中保存的頭信息;嘗試着去分配更大的數組可能會致使OutOfMemoryError,請求的數組大小超過了VM的限制。數據結構
在看這句代碼的時候,腦中有沒有出現兩個大大的問號??多線程
首先,有沒有想到爲何這個數組屬性須要用 transient修飾?編程語言
(想知道這個關鍵字是幹什麼的,能夠看下我以前的一篇文章:面試問你java中的序列化怎麼答?)性能
transient Object[] elementData; // non-private to simplify nested class access
複製代碼
你們能夠隨便的想一下,若是是面試的時候,你會怎麼回答?
因爲 ArrayList 是基於動態數組實現的,因此並非全部的空間都被使用。所以使用了 transient 修飾,能夠防止被自動序列化。
所以 ArrayList 自定義了序列化與反序列化,具體能夠看 writeObject 和 readObject 兩個方法。
須要注意的一點是,當對象中自定義了 writeObject 和 readObject 方法時,JVM 會調用這兩個自定義方法來實現序列化與反序列化。
第二個問題:這個屬性的類型爲何是Object而不是泛型?
這裏和你們說下:
java中泛型運用的目的就是對象的重用,就是同一個方法,能夠支持多種對象類型,Object和泛型在編寫的時候其實沒有太大的區別,只是JVM中沒有T這個概念,T只是存在編寫的時候,進入虛擬機運行的時候,虛擬機會對這種泛型標誌進行擦除,也就是替換T到指定的類型,若是沒有指定類型,就會用Object替換,同時Object能夠new Object(),就是說能夠實例化,而T則不能實例化。在反射方面來講,從運行時,返回一個T的實例時,不須要通過強制轉換,而後Object則須要通過轉換才能獲得。
當咱們試圖向ArrayList中添加一個元素的時候,java會自動檢查,以確保集合中確實還有容量來添加新元素,若是沒有,就會自動擴容,下面是核心代碼,我已經在代碼里加了註釋,幫助你們可以更好的理解:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
複製代碼
上面這段代碼中有個 變量 modCount++,看到這裏的時候確實有些疑惑,我找了下,這個變量是在AbstractList中定義的protected修飾的全局變量,這個變量是記錄告終構性改變的次數,結構性改變就是說修改列表大小的操做。
ArrayList是一個線程不安全的類,這個變量就是用來保證在多線程環境下使用迭代器的時候,同時又對集合進行了修改,同一時刻只能有一個線程修改集合,若是多於一個,就會拋出ConcurrentModficationException。
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:
//接下來,是把原數組d 數據複製到新的數組裏
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//當 Integer-8 依然沒法知足需求,就會取Integer的最大值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
複製代碼
接下來咱們看下,向指定位置添加元素是什麼樣的:
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++;
}
複製代碼
能夠看到,當一個集合足夠大的時候,add操做向數組的尾部添加元素效率仍是很是高的,可是當向指定的位置添加元素的時候,也是須要大量的移動複製操做:System.arraycopy()。
到這裏,ArrayList最大的優點是什麼呢?咱們在平時的開發中涉及到容器的時候爲何會選擇List家族的成員,而不直接選擇數組呢?你們回想一下本身以前敲代碼的經歷,答案也就出來了:
ArrayList封裝了大部分數組的操做方法,好比插入、刪除、搬移數據等等,都在集合內部幫你作好了,還有就是支持動態擴容,這點是數組不能比擬的。
這裏須要注意一點,當咱們在開始定義集合的時候,若是知道咱們須要多大的集合,就應該在一開始就指定集合的大小,由於在集合的內部來進行數據的搬移,複製也是很是耗時的。
那麼數組在何時會用到呢?
一、java ArrayList沒法存儲基本類型,int,long,若是保存的話,就須要封裝爲Integer、Long,而自動的拆裝箱,也有性能的消耗,因此總結下這點就是說若是要保存基本類型,同時還特別關注性能,就可使用數組。
二、若是對數據的數量大小已知,操做也很是簡單,也不須要ArrayList中的大部分方法,也是能夠直接使用數組的。
這樣的分享我會一直持續,你的關注、轉發和好看是對我最大的支持,感謝。
關注公衆號,裏面會有更多精彩的內容