面試官問你數組和ArrayList怎麼答?

我在想每一個人在面試的時候都會被問到集合相關的問題,有好大一部分人在回答的時候並無那麼多的邏輯性,一般都是想到哪裏說到哪裏,這篇文章大概的捋一捋關於集合的相關問題。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中的大部分方法,也是能夠直接使用數組的。

這樣的分享我會一直持續,你的關注、轉發和好看是對我最大的支持,感謝。

關注公衆號,裏面會有更多精彩的內容

相關文章
相關標籤/搜索