面試官問你陣列和ArrayList怎麼答?

邊緣煩惱發表於2019-03-26

我在想每個人在面試的時候都會被問到集合相關的問題,有好大一部分人在回答的時候並沒有那麼多的邏輯性,通常都是想到哪裡說到哪裡,這篇文章大概的捋一捋關於集合的相關問題。

面試官問你陣列和ArrayList怎麼答?

在每種程式語言中,都會有迴圈、陣列、流程控制語句,陣列是一種線性表資料結構,記憶體空間是連續的,儲存的資料型別也是一致的。

正是因為這兩點,陣列的隨機訪問才會非常的高效,這同時也是一把雙刃劍,使得陣列的其他操作效率變得很低,比如說,增加,刪除,為了保持陣列裡面資料的連續性,就會做大量的消耗效能的資料遷移操作。

針對陣列這種型別,java中有容器類,比如ArrayList,ArrayList是對陣列的包裝,在底層就是陣列實現的,因為陣列在定義的時候必須是指定的長度,定義之後就無法再增加長度了,就是說不可能在原來的陣列上接上一段,所以ArrayList就解決了這個問題,當超過陣列容量的時候,ArrayList會進行擴容,擴容之後的容量是之前的1.5倍,然後再把之前陣列中的資料複製過來。

接下來,我們看下ArrayList的原始碼:

 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
複製程式碼

原始碼中陣列最大的容量是Integer.MAX_VALUE -8,為什麼要減去8 呢,這個是上面的定義上面的註釋:

 /**
     * 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封裝了大部分陣列的操作方法,比如插入、刪除、搬移資料等等,都在集合內部幫你做好了,還有就是支援動態擴容,這點是陣列不能比擬的。

這裡需要注意一點,當我們在開始定義集合的時候,如果知道我們需要多大的集合,就應該在一開始就指定集合的大小,因為在集合的內部來進行資料的搬移,複製也是非常耗時的。

那麼陣列在什麼時候會用到呢?

1、java ArrayList無法儲存基本型別,int,long,如果儲存的話,就需要封裝為Integer、Long,而自動的拆裝箱,也有效能的消耗,所以總結下這點就是說如果要儲存基本型別,同時還特別關注效能,就可以使用陣列。

2、如果對資料的數量大小已知,操作也非常簡單,也不需要ArrayList中的大部分方法,也是可以直接使用陣列的。

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

關注公眾號,裡面會有更多精彩的內容

面試官問你陣列和ArrayList怎麼答?

相關文章