文章來源:https://mp.weixin.qq.com/s/3HmDLST9ybJr75nMSbfEkg
前言
作為一個在互聯網公司面一次拿一次Offer的麵霸,打敗了無數競爭對手,每次都只能看到無數落寞的身影失望的離開,略感愧疚(請允許我使用一下誇張的修辭手法)。
於是在一個寂寞難耐的夜晚,我痛定思痛,決定開始寫互聯網技術棧面試相關的文章,希望能幫助各位讀者以後面試勢如破竹,對面試官進行360°的反擊,吊打問你的面試官,讓一同面試的同僚瞠目結舌,瘋狂收割大廠Offer!
所有文章的名字只是我的噱頭,我們應該有一顆謙遜的心,所以希望大家懷著空杯心態好好學,一起進步。
正文
一個婀娜多姿,穿著襯衣的小姐姐,拿著一個精緻的小筆記本,徑直走過來坐在我的面前。
看著眼前這個美麗的女人,心想這不會就是Java基礎系列的面試官吧,真香。
不過看樣子這麼年輕應該問不出什麼深度的吧,嘻嘻。(哦?是麼)
帥丙,上次面試到現在都過去2個星期你才過來,為啥鴿了這麼久?
美麗迷人的面試官您好,因為之前得了流感,差點就沒了,還有最近年底忙年會和年終review的事情,實在太忙了,不好意思。
還做了一波導演(其實是打雜)去拍攝蘑菇街的年會視頻了,實在忙到爆炸,週末也沒能懟出文章哈哈。
好吧那我理解了,下次這種情況記得提前跟我說,對了,多喝熱水。
面試官最後的多喝熱水,直接觸動我內心的防線,居然還有人這麼關心我,帥丙的眼角,又溼了……
ArrayList有用過嗎?它是一個什麼東西?可以用來幹嘛?
有用過,ArrayList就是數組列表,主要用來裝載數據,當我們裝載的是基本類型的數據int,long,boolean,short,byte…的時候我們只能存儲他們對應的包裝類,它的主要底層實現是數組Object[] elementData。
與它類似的是LinkedList,和LinkedList相比,它的查找和訪問元素的速度較快,但新增,刪除的速度較慢。
小結:ArrayList底層是用數組實現的存儲。
特點:查詢效率高,增刪效率低,線程不安全。使用頻率很高。
為啥線程 不安全還使用他呢?
因為我們正常使用的場景中,都是用來查詢,不會涉及太頻繁的增刪,如果涉及頻繁的增刪,可以使用LinkedList,如果你需要線程安全就使用Vector,這就是三者的區別了,實際開發過程中還是ArrayList使用最多的。
不存在一個集合工具是查詢效率又高,增刪效率也高的,還線程安全的,至於為啥大家看代碼就知道了,因為數據結構的特性就是優劣共存的,想找個平衡點很難,犧牲了性能,那就安全,犧牲了安全那就快速。
Tip:這裡還是強調下大家不要為了用而用,我記得我以前最開始工作就有這個毛病。就是不管三七二十一,就是為了用而用,別人用我也用,也不管他的性能啊,是否線程安全啥的,所幸最開始公司接觸的,都是線下的系統,而且沒啥併發。
現在回想起來還有點後怕,而且我第一次出去面試就被一個算法的大佬給虐得體無完膚,其實他問的我都會用,但是就是說不上來為啥用,為啥這麼做。
回想起一年前的第一次面試,我又想到了那時候的點滴,那時候我身邊還有女人,那時候我還有頭髮,那時候……我的眼角又溼了。
您說它的底層實現是數組,但是數組的大小是定長的,如果我們不斷的往裡面添加數據的話,不會有問題嗎?
ArrayList可以通過構造方法在初始化的時候指定底層數組的大小。
通過無參構造方法的方式ArrayList()初始化,則賦值底層數Object[] elementData為一個默認空數組Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}所以數組容量為0,只有真正對數據進行添加add時,才分配默認DEFAULT_CAPACITY = 10的初始容量。
大家可以分別看下他的無參構造器和有參構造器,無參就是默認大小,有參會判斷參數。
數組的長度是有限制的,而ArrayList是可以存放任意數量對象,長度不受限制,那麼他是怎麼實現的呢?
其實實現方式比較簡單,他就是通過數組擴容的方式去實現的。
就比如我們現在有一個長度為10的數組,現在我們要新增一個元素,發現已經滿了,那ArrayList會怎麼做呢?
第一步他會重新定義一個長度為10+10/2的數組也就是新增一個長度為15的數組。
然後把原數組的數據,原封不動的複製到新數組中,這個時候再把指向原數的地址換到新數組,ArrayList就這樣完成了一次改頭換面。
Tip:很多小夥伴說,你舉例幹嘛用10這麼長的數組舉例,這樣畫都要多畫一點格子了,帥丙我會做無意義的事情?因為我們在使用ArrayList的時候一般不會設置初始值的大小,那ArrayList默認的大小就剛好是10。
然後你們也可以看到,他的構造方法,如果你傳入了初始值大小,那就使用你傳入的參數,如果沒,那就使用默認的,一切都是有跡可循的。
ArrayList的默認數組大小為什麼是10?
其實我也沒找到具體原因。
據說是因為sun的程序員對一系列廣泛使用的程序代碼進行了調研,結果就是10這個長度的數組是最常用的最有效率的。也有說就是隨便起的一個數字,8個12個都沒什麼區別,只是因為10這個數組比較的圓滿而已。
我記得你說到了,他增刪很慢,你能說一下ArrayList在增刪的時候是怎麼做的麼?主要說一下他為啥慢。
誒臥*,這個我想一下,大學看的有點忘記了,我想想。
嗯嗯好的,我分別說一下他的新增的邏輯吧。
他有指定index新增,也有直接新增的,在這之前他會有一步校驗長度的判斷ensureCapacityInternal ,就是說如果長度不夠,是需要擴容的。
在擴容的時候,老版本的jdk和8以後的版本是有區別的,8之後的效率更高了,採用了位運算,右移一位,其實就是除以2這個操作。
指定位置新增的時候,在校驗之後的操作很簡單,就是數組的copy,大家可以看下代碼。
不知道大家看懂arraycopy的代碼沒有,我畫個圖解釋下,你可能就明白一點:
比如有下面這樣一個數組我需要在index 5的位置去新增一個元素A
那從代碼裡面我們可以看到,他複製了一個數組,是從index 5的位置開始的,然後把它放在了index 5+1的位置
給我們要新增的元素騰出了位置,然後在index的位置放入元素A就完成了新增的操作了
至於為啥說他效率低,我想我不說你也應該知道了,我這只是在一個這麼小的List裡面操作,要是我去一個幾百幾千幾萬大小的List新增一個元素,那就需要後面所有的元素都複製,然後如果再涉及到擴容啥的就更慢了不是嘛。
我問你個真實的場景,這個問題很少人知道,你可要好好回答喲!
ArrayList(int initialCapacity)會不會初始化數組大小?
這是什麼問題?臥槽問個ArrayList還能問到知識盲區?
不要慌,我記得丙丙說過:無論我們遇到什麼困難都不要害怕,戰勝恐懼最好的辦法就是面對他!!!奧利給!!!…
不會初始化數組大小!
而且將構造函數與initialCapacity結合使用,然後使用set()會拋出異常,儘管該數組已創建,但是大小設置不正確。
使用sureCapacity()也不起作用,因為它基於elementData數組而不是大小。
還有其他副作用,這是因為帶有sureCapacity()的靜態DEFAULT_CAPACITY。
進行此工作的唯一方法是在使用構造函數後,根據需要使用add()多次。
大家可能有點懵,我直接操作一下代碼,大家會發現我們雖然對ArrayList設置了初始大小,但是我們打印List大小的時候還是0,我們操作下標set值的時候也會報錯,數組下標越界。
再結合源碼,大家仔細品讀一下,這是Java Bug裡面的一個經典問題了,還是很有意思的,大家平時可能也不會注意這個點。
ArrayList插入刪除一定慢麼?
取決於你刪除的元素離數組末端有多遠,ArrayList拿來作為堆棧來用還是挺合適的,push和pop操作完全不涉及數據移動操作。
那他的刪除怎麼實現的呢?
刪除其實跟新增是一樣的,不過叫是叫刪除,但是在代碼裡面我們發現,他還是在copy一個數組。
為啥是copy數組呢?
繼續打個比方,我們現在要刪除下面這個數組中的index5這個位置
那代碼他就複製一個index5+1開始到最後的數組,然後把它放到index開始的位置
index5的位置就成功被”刪除“了其實就是被覆蓋了,給了你被刪除的感覺。
同理他的效率也低,因為數組如果很大的話,一樣需要複製和移動的位置就大了。
ArrayList是線程安全的麼?
當然不是,線程安全版本的數組容器是Vector。
Vector的實現很簡單,就是把所有的方法統統加上synchronized就完事了。
你也可以不使用Vector,用Collections.synchronizedList把一個普通ArrayList包裝成一個線程安全版本的數組容器也可以,原理同Vector是一樣的,就是給所有的方法套上一層synchronized。
ArrayList用來做隊列合適麼?
隊列一般是FIFO(先入先出)的,如果用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的精講視頻教程,這套視頻教程是我在年初的時候,根據市場技術棧需求錄製的,非常的系統完整。
閱讀更多 編程仔日常 的文章