我是如何面試別人List相關知識的

Java團長_發表於2019-01-28

640?wx_fmt=jpeg

來源:程式設計新說


?先來點雞湯

前幾年易中天可謂非常的火,接受過很多采訪。他的情況比較特殊,在武漢讀高中時期,恰逢“知識青年上山下鄉”活動,就到新疆去了。

在新疆生產建設兵團工作、生活了10年,而後在烏魯木齊鋼鐵公司子弟中學任教。77年全國恢復高考後他沒有去考大學,78年國家恢復研究生招生後他去考了,然後被武漢大學中文系錄取。

當時主持人問他,為什麼跳過本科直接考研究生呢?他的回答是:考場上的事誰能說的準吶,如果我和我的學生一起去參加高考,萬一他考上了我沒考上,這多丟人呢(還怎麼好意思當人家的老師)。但是考研如果考不上,那在學生面前是不丟人的。

大名鼎鼎的教授當初都害怕考不上,看來考上考不上乃兵家常事。

面試也是一樣的,我們應該正確對待。知道的就回答,不知道的就請教,似是而非的就探討,開開心心的度過一個小時的交談就行了。至於結果那要看緣分了,而且這是一個雙向選擇。

?記一次面試

有位應聘者來面試,我和他坐到了小會議室裡。他,很年輕,剛入行,應該還培訓過,是不是計算機專業我已經記不清了。

但這不重要,照例還是從List問起。一是List可以說是最簡單的,二是簡單的問題更能考察一個人的思維表達能力。

我:做Java開發的,List肯定用過,你都用過哪些List的實現類呢?

他:一般都用ArrayList。

我:除了ArrayList,你還知道哪些List,沒用過也行。

他:(有點緊張)不知道。

其實他的水平大概我也清楚了,完全可以再問兩個問題草草把他打發走。但只要時間允許的情況下,我是不會這樣做的。

一方面是不讓面試者覺得自己因水平較差不受重視。

二是這部分人大都是轉行培訓剛入坑不久的新人,不想讓他們的自信心受到打擊。

三是面試的過程其實對面試官也是一種鍛鍊,也可以藉機refresh自己的記憶。

最後說句良心話,面試者為了這個面試花在路上的時間估計都要一個小時,如果用5分鐘就讓人家走,感覺有點說不過去。


我:還有一個LinkedList,不知道你有沒有見過。

他:知道,平時沒用過,所以沒什麼印象。

我:一個叫ArrayList,一個叫LinkedList,根據名字你說下它們底層是怎麼實現的?

他:應該一個是用陣列實現的,一個是連結串列實現的。

我:那你能不能說一下陣列和連結串列的主要區別是什麼?

大概過了好幾秒,他沒有回答,也不說不知道。我覺得可能是我問的方式略微籠統,我就又具體了一些。

我:陣列和連結串列是資料結構裡的概念,這你應該知道。我的意思是從資料結構的角度,陣列有什麼特點,連結串列有什麼特點,或者說它們在記憶體裡大致是怎麼分佈的?

他:資料結構的東西不太會。

我覺得我的問題已經很清晰了,但凡是正常的開發者,多多少少都應該能說出點,可是,他沒有。

看得出他有點緊張,所以我每次都是微笑著、用很柔和的聲音和他說話,就害怕太強勢了給他造成影響。

雖然這麼簡單的問題,他都不會,我還是很耐心地給他講解,就當是鍛鍊自己了。


我:定義一個陣列,只需指定一個長度即可。然後就可以通過變數名+索引(或者說下標)的形式訪問陣列元素了,下標不能超過陣列長度,否則就會發生索引越界異常。

比如陣列a,長度是10,那麼第一個元素就是a[0],最後一個就是a[9]。想訪問哪個元素只要指定下標就可以了。像這種可以隨意訪問任何元素的,有個專用名詞叫做隨機訪問。

那我們來看下它在記憶體中是如何分佈的,才支援隨機訪問。其實陣列在記憶體中是一段連續的空間,你可以把它想象成一個梯子,一個格子緊挨著一個格子。

陣列名,也就是這個a,指向了這個空間的起始處地址,也就是陣列的第一個元素的地址,所以其實和a[0]指向的是同一個地方。但a和a[0]的含義不一樣,a表示記憶體地址,a[0]表示這個地址上存的元素。

這裡的下標0其實指的是相對於起始處地址的偏移量。0表示沒有偏移,所以就是起始處地址的那個元素,也即第一個元素。

a[1]表示相對於起始處地址偏移量為1的那個元素,實際可以認為底層執行的是
*(a + 1)。a+1表示從起始地址開始向後偏移1個之後的地址,那麼*(星號)的意思就是取出那個地址上儲存的元素。因為向後偏移了1個,其實就是第二個,所以a[1]叫取出陣列的第二個元素。

因陣列在記憶體中是一段連續的空間,所以不管訪問哪個元素都是這兩步,加上偏移量,然後取資料。這就是它支援隨機訪問的原因。說白了就是所有元素按順序挨在了一起。

也可以看出來,不管陣列的長度是多長,訪問元素的方式都是這兩步,都在常量的時間內完成。所以按索引訪問陣列元素的時間複雜度就是O(1)。

ArrayList只不過是對陣列的包裝,因為陣列在記憶體中分配時必須指定長度,且一旦分配好後便無法再增加長度,即不可能在原陣列後面再接上一段的。

ArrayList之所以可以一直往裡新增,是因為它內部做了處理。當底層陣列填滿後,它會再分配一個更大的新的陣列,把原陣列裡的元素拷貝過來,然後把原陣列拋棄掉。使用新的陣列作為底層陣列來繼續儲存。

他:你講的非常好,我完全聽懂了,比我當時那個培訓班的老師講的好多了。

我:LinkedList也實現了List介面,也可以按索引訪問元素,表面上用起來感覺差不多,但是其底層卻有天壤之別。

與陣列一下子分配好指定長度的空間備用不同,連結串列不會預先分配空間。而是在每次新增一個元素時臨時專門為它自己分配一個空間。

因為記憶體空間的分配是由作業系統完成的,可以說每次分配的位置都是隨機的,並沒有確定的規律。所以說連結串列的每個元素都在完全不同的記憶體地址上,那我們該如何找到它們呢?

唯一的做法就是把每個元素的記憶體地址都要儲存起來。怎麼儲存呢?那就讓上一個元素除了儲存具體的資料之外,也儲存一份下一個元素在記憶體中的地址。

整個就像前後按順序依次相連的一條鏈,我們只要儲存第一個元素的記憶體地址,就可以順藤摸瓜找到所有的元素。

這其實就像一個挖寶藏遊戲,假設共10步,告訴你第一步去哪裡挖。然後挖出一個字條,上面寫著第二步去哪裡挖。依次這樣挖下去。第九步挖出字條後才知道寶藏的位置,然後第十步就把它挖出來了。

可見為了得到寶藏必須這樣一步一步挖下去。中間的任何一步都不能跳過,因為第十步寶藏的位置在第九步裡放著呢,第九步的位置在第八步裡放著呢,依次倒著下來就到了第一步的位置,而第一步的位置已經告訴你了。

所以陣列更像是康莊大道、四平八穩。連結串列更像是曲徑通幽、人跡罕至。一個像探險,步步為營。一個像回家,輕車熟路。

可見按索引訪問連結串列元素時,必須從頭一個個遍歷,而且連結串列越長,位置越靠後,所需花費的時間就越長。所以按索引訪問連結串列元素的時間複雜度就是O(n),n為連結串列的長度。

也說明了連結串列不支援隨機訪問。所以ArrayList就實現了RandomAccess(隨機訪問)介面,而LInkedList就沒有。

他:你講的真好。

?後記

後來這個應聘者給我司前臺打電話,說他自己水平太差,無法到我司來。但是叮囑前臺一定要轉達對我的感謝。

說面試時他內心非常緊張,但面試官總是面帶微笑很溫和地跟他說話。遇到不懂的地方,總是非常有耐心地給他講解,旁徵博引,舉一反三。最後他都聽懂了,而且也不緊張了。

我感覺這是我收到的對我最高的評價,不是嗎?

PS:由於文章有點長了,線性表的插入和刪除會以原始碼解析的形式講解。


(完)


640?


Java團長

專注於Java乾貨分享

640?wx_fmt=jpeg

掃描上方二維碼獲取更多Java乾貨

相關文章