「吊打面試官」系列-ArrayList

文章來源:https://mp.weixin.qq.com/s/3HmDLST9ybJr75nMSbfEkg


前言

作為一個在互聯網公司面一次拿一次Offer的麵霸,打敗了無數競爭對手,每次都只能看到無數落寞的身影失望的離開,略感愧疚(請允許我使用一下誇張的修辭手法)。

於是在一個寂寞難耐的夜晚,我痛定思痛,決定開始寫互聯網技術棧面試相關的文章,希望能幫助各位讀者以後面試勢如破竹,對面試官進行360°的反擊,吊打問你的面試官,讓一同面試的同僚瞠目結舌,瘋狂收割大廠Offer!

所有文章的名字只是我的噱頭,我們應該有一顆謙遜的心,所以希望大家懷著空杯心態好好學,一起進步。


正文

一個婀娜多姿,穿著襯衣的小姐姐,拿著一個精緻的小筆記本,徑直走過來坐在我的面前。

看著眼前這個美麗的女人,心想這不會就是Java基礎系列的面試官吧,真香。

不過看樣子這麼年輕應該問不出什麼深度的吧,嘻嘻。(哦?是麼)

「吊打面試官」系列-ArrayList

帥丙,上次面試到現在都過去2個星期你才過來,為啥鴿了這麼久?

美麗迷人的面試官您好,因為之前得了流感,差點就沒了,還有最近年底忙年會和年終review的事情,實在太忙了,不好意思。

「吊打面試官」系列-ArrayList

還做了一波導演(其實是打雜)去拍攝蘑菇街的年會視頻了,實在忙到爆炸,週末也沒能懟出文章哈哈。

「吊打面試官」系列-ArrayList


好吧那我理解了,下次這種情況記得提前跟我說,對了,多喝熱水。

面試官最後的多喝熱水,直接觸動我內心的防線,居然還有人這麼關心我,帥丙的眼角,又溼了……

「吊打面試官」系列-ArrayList

ArrayList有用過嗎?它是一個什麼東西?可以用來幹嘛?

有用過,ArrayList就是數組列表,主要用來裝載數據,當我們裝載的是基本類型的數據int,long,boolean,short,byte…的時候我們只能存儲他們對應的包裝類,它的主要底層實現是數組Object[] elementData。

與它類似的是LinkedList,和LinkedList相比,它的查找和訪問元素的速度較快,但新增,刪除的速度較慢。

小結:ArrayList底層是用數組實現的存儲。

特點:查詢效率高,增刪效率低,線程不安全。使用頻率很高。


為啥線程 不安全還使用他呢?

因為我們正常使用的場景中,都是用來查詢,不會涉及太頻繁的增刪,如果涉及頻繁的增刪,可以使用LinkedList,如果你需要線程安全就使用Vector,這就是三者的區別了,實際開發過程中還是ArrayList使用最多的。

不存在一個集合工具是查詢效率又高,增刪效率也高的,還線程安全的,至於為啥大家看代碼就知道了,因為數據結構的特性就是優劣共存的,想找個平衡點很難,犧牲了性能,那就安全,犧牲了安全那就快速。

Tip:這裡還是強調下大家不要為了用而用,我記得我以前最開始工作就有這個毛病。就是不管三七二十一,就是為了用而用,別人用我也用,也不管他的性能啊,是否線程安全啥的,所幸最開始公司接觸的,都是線下的系統,而且沒啥併發。

現在回想起來還有點後怕,而且我第一次出去面試就被一個算法的大佬給虐得體無完膚,其實他問的我都會用,但是就是說不上來為啥用,為啥這麼做。

回想起一年前的第一次面試,我又想到了那時候的點滴,那時候我身邊還有女人,那時候我還有頭髮,那時候……我的眼角又溼了。

「吊打面試官」系列-ArrayList

您說它的底層實現是數組,但是數組的大小是定長的,如果我們不斷的往裡面添加數據的話,不會有問題嗎?

ArrayList可以通過構造方法在初始化的時候指定底層數組的大小。

通過無參構造方法的方式ArrayList()初始化,則賦值底層數Object[] elementData為一個默認空數組Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}所以數組容量為0,只有真正對數據進行添加add時,才分配默認DEFAULT_CAPACITY = 10的初始容量。

大家可以分別看下他的無參構造器和有參構造器,無參就是默認大小,有參會判斷參數。

「吊打面試官」系列-ArrayList

數組的長度是有限制的,而ArrayList是可以存放任意數量對象,長度不受限制,那麼他是怎麼實現的呢?

其實實現方式比較簡單,他就是通過數組擴容的方式去實現的。

就比如我們現在有一個長度為10的數組,現在我們要新增一個元素,發現已經滿了,那ArrayList會怎麼做呢?

「吊打面試官」系列-ArrayList

第一步他會重新定義一個長度為10+10/2的數組也就是新增一個長度為15的數組。

「吊打面試官」系列-ArrayList

然後把原數組的數據,原封不動的複製到新數組中,這個時候再把指向原數的地址換到新數組,ArrayList就這樣完成了一次改頭換面。

「吊打面試官」系列-ArrayList

Tip:很多小夥伴說,你舉例幹嘛用10這麼長的數組舉例,這樣畫都要多畫一點格子了,帥丙我會做無意義的事情?因為我們在使用ArrayList的時候一般不會設置初始值的大小,那ArrayList默認的大小就剛好是10。

「吊打面試官」系列-ArrayList

然後你們也可以看到,他的構造方法,如果你傳入了初始值大小,那就使用你傳入的參數,如果沒,那就使用默認的,一切都是有跡可循的。

ArrayList的默認數組大小為什麼是10?

其實我也沒找到具體原因。

據說是因為sun的程序員對一系列廣泛使用的程序代碼進行了調研,結果就是10這個長度的數組是最常用的最有效率的。也有說就是隨便起的一個數字,8個12個都沒什麼區別,只是因為10這個數組比較的圓滿而已。

我記得你說到了,他增刪很慢,你能說一下ArrayList在增刪的時候是怎麼做的麼?主要說一下他為啥慢。

誒臥*,這個我想一下,大學看的有點忘記了,我想想。

「吊打面試官」系列-ArrayList

嗯嗯好的,我分別說一下他的新增的邏輯吧。

他有指定index新增,也有直接新增的,在這之前他會有一步校驗長度的判斷ensureCapacityInternal

,就是說如果長度不夠,是需要擴容的。

「吊打面試官」系列-ArrayList

在擴容的時候,老版本的jdk和8以後的版本是有區別的,8之後的效率更高了,採用了位運算,右移一位,其實就是除以2這個操作。

「吊打面試官」系列-ArrayList

指定位置新增的時候,在校驗之後的操作很簡單,就是數組的copy,大家可以看下代碼。

「吊打面試官」系列-ArrayList

不知道大家看懂arraycopy的代碼沒有,我畫個圖解釋下,你可能就明白一點:

比如有下面這樣一個數組我需要在index 5的位置去新增一個元素A

「吊打面試官」系列-ArrayList

那從代碼裡面我們可以看到,他複製了一個數組,是從index 5的位置開始的,然後把它放在了index 5+1的位置

「吊打面試官」系列-ArrayList

給我們要新增的元素騰出了位置,然後在index的位置放入元素A就完成了新增的操作了

「吊打面試官」系列-ArrayList

至於為啥說他效率低,我想我不說你也應該知道了,我這只是在一個這麼小的List裡面操作,要是我去一個幾百幾千幾萬大小的List新增一個元素,那就需要後面所有的元素都複製,然後如果再涉及到擴容啥的就更慢了不是嘛。

我問你個真實的場景,這個問題很少人知道,你可要好好回答喲!

ArrayList(int initialCapacity)會不會初始化數組大小?

這是什麼問題?臥槽問個ArrayList還能問到知識盲區?

「吊打面試官」系列-ArrayList

不要慌,我記得丙丙說過:無論我們遇到什麼困難都不要害怕,戰勝恐懼最好的辦法就是面對他!!!奧利給!!!…

不會初始化數組大小!

而且將構造函數與initialCapacity結合使用,然後使用set()會拋出異常,儘管該數組已創建,但是大小設置不正確。

使用sureCapacity()也不起作用,因為它基於elementData數組而不是大小。

還有其他副作用,這是因為帶有sureCapacity()的靜態DEFAULT_CAPACITY。

進行此工作的唯一方法是在使用構造函數後,根據需要使用add()多次。

大家可能有點懵,我直接操作一下代碼,大家會發現我們雖然對ArrayList設置了初始大小,但是我們打印List大小的時候還是0,我們操作下標set值的時候也會報錯,數組下標越界。

再結合源碼,大家仔細品讀一下,這是Java Bug裡面的一個經典問題了,還是很有意思的,大家平時可能也不會注意這個點。

「吊打面試官」系列-ArrayList

ArrayList插入刪除一定慢麼?

取決於你刪除的元素離數組末端有多遠,ArrayList拿來作為堆棧來用還是挺合適的,push和pop操作完全不涉及數據移動操作。

那他的刪除怎麼實現的呢?

刪除其實跟新增是一樣的,不過叫是叫刪除,但是在代碼裡面我們發現,他還是在copy一個數組。

為啥是copy數組呢?

「吊打面試官」系列-ArrayList

繼續打個比方,我們現在要刪除下面這個數組中的index5這個位置

「吊打面試官」系列-ArrayList

那代碼他就複製一個index5+1開始到最後的數組,然後把它放到index開始的位置

「吊打面試官」系列-ArrayList

index5的位置就成功被”刪除“了其實就是被覆蓋了,給了你被刪除的感覺。

同理他的效率也低,因為數組如果很大的話,一樣需要複製和移動的位置就大了。

ArrayList是線程安全的麼?

當然不是,線程安全版本的數組容器是Vector。

Vector的實現很簡單,就是把所有的方法統統加上synchronized就完事了。

你也可以不使用Vector,用Collections.synchronizedList把一個普通ArrayList包裝成一個線程安全版本的數組容器也可以,原理同Vector是一樣的,就是給所有的方法套上一層synchronized。

ArrayList用來做隊列合適麼?

隊列一般是FIFO(先入先出)的,如果用ArrayList做隊列,就需要在數組尾部追加數據,數組頭部刪除數組,反過來也可以。

但是無論如何總會有一個操作會涉及到數組的數據搬遷,這個是比較耗費性能的。

結論:ArrayList不適合做隊列。

那數組適合用來做隊列麼?

這個女人是魔鬼麼?不過還是得微笑面對!

「吊打面試官」系列-ArrayList

數組是非常合適的。

比如ArrayBlockingQueue內部實現就是一個環形隊列,它是一個定長隊列,內部是用一個定長數組來實現的。

另外著名的Disruptor開源Library也是用環形數組來實現的超高性能隊列,具體原理不做解釋,比較複雜。

簡單點說就是使用兩個偏移量來標記數組的讀位置和寫位置,如果超過長度就折回到數組開頭,前提是它們是定長數組。

ArrayList的遍歷和LinkedList遍歷性能比較如何?

論遍歷ArrayList要比LinkedList快得多,ArrayList遍歷最大的優勢在於內存的連續性,CPU的內部緩存結構會緩存連續的內存片段,可以大幅降低讀取內存的性能開銷。

能跟我聊一下LinkedList相關的東西麼?

可以呀,不然今天天色已晚,不然我們下次再聊?

好吧,每次你都找藉口,下次可以集合最後章節了,我們好好聊聊,你好好準備吧。

總結

ArrayList就是動態數組,用MSDN中的說法,就是Array的複雜版本,它提供了動態的增加和減少元素,實現了ICollection和IList接口,靈活的設置數組的大小等好處。

面試裡面問的時候沒HashMap,ConcurrentHashMap啥的這麼常問,但是也有一定概率問到的,還是那句話,不打沒把握的仗

我們在源碼閱讀過程中,不需要全部都讀懂,需要做的就是讀懂核心的源碼,加深自己對概念的理解就好了,用的時候不至於啥都不知道,不要為了用而用就好了。


ArrayList常用的方法總結

  • boolean add(E e)

將指定的元素添加到此列表的尾部。

  • void add(int index, E element)

將指定的元素插入此列表中的指定位置。

  • boolean addAll(Collection c)

按照指定 collection 的迭代器所返回的元素順序,將該 collection 中的所有元素添加到此列表的尾部。

  • boolean addAll(int index, Collection c)

從指定的位置開始,將指定 collection 中的所有元素插入到此列表中。

  • void clear()

移除此列表中的所有元素。

  • Object clone()

返回此 ArrayList 實例的淺表副本。

  • boolean contains(Object o)

如果此列表中包含指定的元素,則返回 true。

  • void ensureCapacity(int minCapacity)

如有必要,增加此 ArrayList 實例的容量,以確保它至少能夠容納最小容量參數所指定的元素數。

  • E get(int index)

返回此列表中指定位置上的元素。

  • int indexOf(Object o)

返回此列表中首次出現的指定元素的索引,或如果此列表不包含元素,則返回 -1。

  • boolean isEmpty()

如果此列表中沒有元素,則返回 true

  • int lastIndexOf(Object o)

返回此列表中最後一次出現的指定元素的索引,或如果此列表不包含索引,則返回 -1。

  • E remove(int index)

移除此列表中指定位置上的元素。

  • boolean remove(Object o)

移除此列表中首次出現的指定元素(如果存在)。

  • protected void removeRange(int fromIndex, int toIndex)

移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之間的所有元素。

  • E set(int index, E element)

用指定的元素替代此列表中指定位置上的元素。

  • int size()

返回此列表中的元素數。

  • Object[] toArray()

按適當順序(從第一個到最後一個元素)返回包含此列表中所有元素的數組。

  • T[] toArray(T[] a)

按適當順序(從第一個到最後一個元素)返回包含此列表中所有元素的數組;返回數組的運行時類型是指定數組的運行時類型。

  • void trimToSize()

將此 ArrayList 實例的容量調整為列表的當前大小。


我目前是在職Java開發,如果你現在正在瞭解Java技術,想要學好Java,渴望成為一名Java開發工程師,在入門學習Java的過程當中缺乏基礎的入門視頻教程,你可以關注並私信我:01。我這裡有一套最新的Java基礎JavaSE的精講視頻教程,這套視頻教程是我在年初的時候,根據市場技術棧需求錄製的,非常的系統完整。


分享到:


相關文章: