1. 程式人生 > >java基礎階段幾個面試題

java基礎階段幾個面試題

1.說出你對面向物件的理解

在我理解,面向物件是向現實世界模型的自然延伸,這是一種“萬物皆物件”的程式設計思想。在現實生活中的任何物體都可以歸為一類事物,而每一個個體都是一類事物的例項。面向物件的程式設計是以物件為中心,以訊息為驅動,所以程式=物件+訊息。
面向物件有三大特性,封裝、繼承和多型。
封裝就是將一類事物的屬性和行為抽象成一個類,使其屬性私有化,行為公開化,提高了資料的隱祕性的同時,使程式碼模組化。這樣做使得程式碼的複用性更高。
繼承則是進一步將一類事物共有的屬性和行為抽象成一個父類,而每一個子類是一個特殊的父類--有父類的行為和屬性,也有自己特有的行為和屬性。這樣做擴充套件了已存在的程式碼塊,進一步提高了程式碼的複用性。

如果說封裝和繼承是為了使程式碼重用,那麼多型則是為了實現介面重用。多型的一大作用就是為了解耦--為了解除父子類繼承的耦合度。如果說繼承中父子類的關係式IS-A的關係,那麼介面和實現類之之間的關係式HAS-A。簡單來說,多型就是允許父類引用(或介面)指向子類(或實現類)物件。很多的設計模式都是基於面向物件的多型性設計的。

2.JVM的記憶體區及其GC演算法
https://blog.csdn.net/anjoyandroid/article/details/78609971
元空間:jdk1.8取消了持久代新增了元空間,並將方法區放在元空間中

3.集合框架下的各種介面和實現類有哪些,分別有啥特點

https://blog.csdn.net/sdgihshdv/article/details/72566485

https://blog.csdn.net/suifeng629/article/details/82179996
https://blog.csdn.net/C18298182575/article/details/87167323

4.string類有啥特點,有哪些常用的API

1.String類物件的相等判斷使用equals()方法完成,“==”實現的是地址數值的比較
2.字串內容一旦宣告則不可改變,String類物件內容的改變是依靠引用關係的變更實現的。
3.String類有兩種例項化方式,使用直接賦值可以不產生垃圾空間,並且可以自動入池,不要使用構造方法賦值。

indexOf()檢索字串中某個字元或某段字元的下標。

lastIndexOf()
和indexOf類似,不過是查詢最後一個出現的位置。
str.lastIndexOf(str,index),從下標index往前查詢最後一個出現的位置
substring()返回一個字串的子字串
charAt(index)返回下標對應的字元
trim()去掉字串前後的空格
startsWith()/endsWith()檢測字串是否已制定字串開頭或結尾,返回值是boolean
split()/根據括號內的字串分離字串,返回值是一個字串陣列
....

5.stringBuilder和stringBuffer的區別?

執行速度:StringBuilder >StringBuffer >String
執行緒安全:StringBuilder是執行緒不安全的,而StringBuffer是執行緒安全的
String:適用於少量的字串操作的情況
StringBuilder:適用於單執行緒下在字元緩衝區進行大量操作的情況
StringBuffer:適用多執行緒下在字元緩衝區進行大量操作的情況

為什麼StringBuilder是不安全的?
char[] value;
int count;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
對於count + =len;不是一個原子操作 兩個執行緒同時執行假設都是 計數器為 10 執行完後 就會變成11 而不是12

什麼是原子操作:
簡單的例子:
轉賬,A轉給B100,因為停電,導致A轉出了100,B卻沒收到100,所以要把100回滾給A。
原子操作就是多執行緒下各執行緒同時執行失敗且同時成功,在兩個執行緒下,由於count繼承於父類AbstractStringBuilder,當
其中一個執行緒對coun執行+len後,另一執行緒取到的count值仍為原來的count值,故+len後和上一個執行緒得到的結果一樣,
故執行緒不安全
而stringBuffer中原始碼:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
當一個執行緒訪問append後會立即上鎖,從而另一個執行緒無法訪問append方法,故是執行緒安全的
在多執行緒下,stringBuffer下各執行緒需要頻繁的加鎖解鎖操作,從而需要執行更長的時間,雖然stringBuilder不需要加鎖解鎖,
當由於執行緒不安全性,更適用於單執行緒

6.執行緒建立的3種方式,執行緒阻塞的API有哪些及其之間的區別?

Runnable,Thread,通過 Callable 和 Future 建立執行緒。
1. 繼承Thread類來建立一個執行緒, 並實現run方法(執行緒需要完成的功能); 構建子類物件,start()啟動執行緒
2. 實現Runnable介面來建立一個執行緒, 實現Runnable,實現run()方法; 將Runnable介面類的物件作為引數傳遞給Thread類物件, 並呼叫start()方法;
3. 實現Callable介面來建立一個執行緒, 先定義一個Callable的實現類, 重寫call()方法, call()有返回值; 兩種執行方式:
1). 藉助FutureTask執行, 建立Callable實現類的物件, 並作為引數傳遞給FutureTask, FutureTask作為引數傳遞給Thread類的物件, 並執行start()方法;
2). 藉助執行緒池來執行, 先建立執行緒池, 然後呼叫執行緒池的submit方法, 並將Callable實現列作為引數傳入,

方法二的好處:
1. 可以將一個Runnable實現類傳遞給多個執行緒物件, 適合用多個相同程式程式碼的程式設計處理同一個資源
2. Thread類建立執行緒是採用繼承的方式, 而Java中只能單繼承, 如果某個子類的需要建立執行緒只能採用實現Runnable介面或者實現Callable介面的方式.

方法三的好處:
1. 有返回值
2. call()可以丟擲異常
3. 執行Callable任務可以得到一個Future兌現,表示非同步計算的結果. 它提供了檢測計算是否完成的方法(isDone())以等待計算的完成,並檢索計算的結果.

執行緒阻塞api:
sleep()方法; 該方法允許指定以ms為單位的一段時間作為引數, 它使得執行緒在指定的時間內進入阻塞狀態,不能得到CPU時間, 指定時間已過,執行緒重新進入可執行狀態.
suspend()和resume()方法;配套使用, suspend()使得執行緒進入阻塞狀態,且不會自動恢復, 必須將其對應的resume()呼叫, 才可以使執行緒進入可執行狀態.
yield(); 使得執行緒放棄當前分得的CPU時間, 但是不使執行緒阻塞, 即執行緒仍然處於可執行狀態;
wait()和notify()方法;配套使用,若wait()有引數,相當於sleep(但可以通過notify強行喚醒), wait()沒有引數,相當於suspend(), 需要通過notify喚醒

sleep(0)和sleep(1)和不要sleep的區別:
sleep(0),如果執行緒排程器的可執行佇列中有大於或等於當前執行緒優先順序的就緒執行緒存在,作業系統會將當前執行緒從處理器上移除,排程其他優先順序高的就緒執行緒執行;如果可執行佇列中的沒有就緒執行緒或所有就緒執行緒的優先順序均低於當前執行緒優先順序,那麼當前執行緒會繼續執行,就像沒有呼叫 Sleep(0)一樣。
Sleep(1),會引發執行緒上下文切換:呼叫執行緒會從執行緒排程器的可執行佇列中被移除一段時間,這個時間段約等於 timeout 所指定的時間長度。為什麼說約等於呢?是因為睡眠時間單位為毫秒,這與系統的時間精度有關。通常情況下,系統的時間精度為 10 ms,那麼指定任意少於 10 ms但大於 0 ms 的睡眠時間,均會向上求值為 10 ms。

7.抽象類和介面的區別?有了抽象類為啥還要介面?

①.一類可以實現多個介面但只能繼承自一個抽象類,從抽象類派生出的子類同樣可以實現介面,從而,我們能得出一個結論:介面是為Java實現多繼承而存在的
②.抽象類中可以存在非抽象的方法,可介面不能存在非抽象的方法,並且接口裡面的方法只是一個宣告,必須用 public abstract來修飾,沒有具體的實現
③.抽象方法中的成員變數可以被不同的修飾符修飾,而介面中的成員變數預設都是靜態常量
④.抽象類是對物件進行的抽象,而介面是一種行為規範,這一點是比較重要的.
(所以為什麼有了介面還要有抽象類)

8.氣泡排序,選擇排序,快速排序(瞭解)

氣泡排序:什麼是冒泡?比如說水底隨機產生一些氣泡,一起往上冒泡,越輕的氣泡往上冒的越快
具體:12 34 10 78 67
如果從小到大排序:先將67和78比較,67比78小,依次往前比較,小的放前面,打的放後面,以此為一輪排序,然後再將新的陣列重複上述過程,共需要n輪排序(n為元素個數);

選擇排序:從一個數組裡選出最小的元素放在陣列第一位並交換位置,然後再將去掉第一位的陣列找出最小元素並放在這個新陣列第一位,
重複此操作。
12 34 10 78 67
第一輪:10| 34 12 78 67
第二輪:10 12| 34 78 67
第三輪:10 12 34| 78 67
第四輪:10 12 34 67| 78
排序結束

快速排序:基於基數排序。先取任意一基數,一般為陣列第一個元素(由於當第一個元素為最小值(最大值)時會使排序出現錯誤,故有時候也取中間的元素),然後將比基數小的數作為一個數組,比基數大的數作為一個數組,再將新的兩個陣列分別遞迴排序。
通過基數分成兩個陣列的過程:12 34 10 78 67 8 假設陣列為arr
取一基數temp=12 取low=0(陣列第一位),high=5(陣列最後一位)
第一輪:第一步:先從後往前比較:arr[high]=8<12=temp,結束這一步操作,high與low不變。如果這裡arr[high]>12,則令high-1得到新的high將arr[high]與temp比較,依此下去直到arr[high]<temp,這種情況high發生改變,low不變。
第二步:再從前往後將arr[low]與temp比較,原理與第一步相同,因為arr[1]>temp,此時low=1,結束這一步操作。
第三步:交換arr[low]與arr[high]的值
第一輪結果:12 8 10 78 67 34(low=1,high=5)
第二輪:與第一輪一樣,第一步,從arr[high]往前,直到arr[2]=10<12,此時high=2,結束這一步
第二步,從arr[low]往後,12,8,10都不大於12,到這裡的時候,因為low=2=high,故比較,得到索引index=low=high=2
第二輪結果:12 8 10 78 67 34
因為index得到了值3,將arr[index]作為分界點將最後一輪結果陣列[12 8 10 78 67 34]分為兩個陣列[12 8 10]和[78 67 34]
將新的到的兩個陣列重複進行上述操作
[12 8 10]->因為12為最大值,故取中間值8->[8]和[10 12]->[8]、[10]、[12]
[78 67 34]->取67,->[34]、[67 78]->[34]、[67]、[78]->[8]、[10]、[12]、[34]、[67]、[78]
(拓展:希爾排序、插入排序)

9.什麼是死鎖?如何避免死鎖

死鎖的定義:所謂死鎖是指多個執行緒因競爭資源而造成的一種僵局(互相等待),若無外力作用,這些程序都將無法向前推進。

產生原因:
1) 系統資源的競爭
通常系統中擁有的不可剝奪資源,其數量不足以滿足多個程序執行的需要,使得程序在 執行過程中,會因爭奪資源而陷入僵局,如磁帶機、印表機等。只有對不可剝奪資源的競爭 才可能產生死鎖,對可剝奪資源的競爭是不會引起死鎖的。
2) 程序推進順序非法
程序在執行過程中,請求和釋放資源的順序不當,也同樣會導致死鎖。例如,併發程序 P1、P2分別保持了資源R1、R2,而程序P1申請資源R2,程序P2申請資源R1時,兩者都 會因為所需資源被佔用而阻塞。

四個產生死鎖的條件:
互斥條件:程序要求對所分配的資源(如印表機)進行排他性控制,即在一段時間內某 資源僅為一個程序所佔有。此時若有其他程序請求該資源,則請求程序只能等待。
不剝奪條件:程序所獲得的資源在未使用完畢之前,不能被其他程序強行奪走,即只能 由獲得該資源的程序自己來釋放(只能是主動釋放)。
請求和保持條件:程序已經保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其他程序佔有,此時請求程序被阻塞,但對自己已獲得的資源保持不放。
迴圈等待條件:存在一種程序資源的迴圈等待鏈,鏈中每一個程序已獲得的資源同時被 鏈中下一個程序所請求。即存在一個處於等待狀態的程序集合{Pl, P2, ..., pn},其中Pi等 待的資源被P(i+1)佔有(i=0, 1, ..., n-1),Pn等待的資源被P0佔有。

避免死鎖:
1.加鎖順序(執行緒按照一定的順序加鎖)
2.加鎖時限(執行緒嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己佔有的鎖)
3.死鎖檢測
https://blog.csdn.net/ls5718/article/details/51896159

&n