Java基礎面試題下

northli發表於2024-06-23

# Java基礎面試題(下)

> lecture:波哥

# 一、String相關面試題

## 1. 為什麼 String 在 java 中是不可變的?

- 如果不是不可變的:這種情況根本不可能,因為在字串池的情況下,一個字串物件/文字,例如 “Test” 已被許多參考變數引用,因此如果其中任何一個更改了值,其他引數將自動受到影響
- 字串被廣泛作為引數使用:例如,為了開啟網路連線,你可以將主機名和埠號作為字串傳遞,你可以將資料庫URL 作為字串傳遞, 以開啟資料庫連線,你可以透過將檔名作為引數傳遞給 File I/O 類來開啟 Java 中的任何檔案。如果String不是不可變的,這將導致嚴重的安全威脅,我的意思是有人可以訪問他有權授權的任何檔案,然後可以故意或意外地更改檔名並獲得對該檔案的訪問許可權。由於不變性,你無需擔心這種威脅。這個原因也說明了,為什麼String 在 Java 中是最終的,透過使 **java.lang **.String final,Java設計者確保沒有人覆蓋 String 類的任何行為。
- 執行緒間共享:由於 String 是不可變的,它可以安全地共享許多執行緒,這對於多執行緒程式設計非常重要. 並且避免了 Java 中的同步問題,不變性也使得String 例項在 Java 中是執行緒安全的,這意味著你不需要從外部同步 String 操作。
- String 快取其雜湊碼:Java 中的不可變 String 快取其雜湊碼,並且不會在每次呼叫 String 的 hashCode 方法時重新計算,這使得它在 Java 中的 HashMap 中使用的 HashMap 鍵非常快。簡而言之,因為 String 是不可變的,所以沒有人可以在建立後更改其內容,這保證了 String 的 hashCode 在多次呼叫時是相同的。
- 類載入機制使用:如果 String 是可變的,載入“java.io.Writer” 的請求可能已被更改為載入 “mil.vogoon.DiskErasingWriter”. 安全性和字串池是使字串不可變的主要原因

## 2. 為什麼 char 陣列比 String 更適合儲存密碼

```
String pass = this.xxx();
char[] pass1 = this.yyyy();
User user = new User();
user.setPass(null);
user.setPass1(null);
Token = tokne(user) ;
響應給前端
```

- 由於字串在 Java 中是不可變的,如果你將密碼儲存為純文字,它將在記憶體中可用,直到垃圾收集器清除它. 並且為了可重用性,會存在 String 在字串池中, 它很可能會保留在記憶體中持續很長時間,從而構成安全威脅。由於任何有權訪問記憶體轉儲的人都可以以明文形式找到密碼,這是另一個原因,你應該始終使用加密密碼而不是純文字。由於字串是不可變的,所以不能更改字串的內容,因為任何更改都會產生新的字串,而如果你使用char[],你就可以將所有元素設定為空白或零。因此,在字元陣列中儲存密碼可以明顯降低竊取密碼的安全風險。
- 使用 String 時,總是存在在日誌檔案或控制檯中列印純文字的風險,但如果使用 Array,則不會列印陣列的內容而是列印其記憶體位置。

# 二、序列化相關問題

## 1. 什麼是 Java 序列化

序列化是把物件改成可以存到磁碟或透過網路傳送到其他執行中的Java 虛擬機器的二進位制格式的過程, 並可以透過反序列化恢復物件狀態. Java 序列化API給開發人員提供了一個標準機制, 透過java.io.Serializable 和 java.io.Externalizable 介面, ObjectInputStream及ObjectOutputStream 處理物件序列化. Java 程式設計師可自由選擇基於類結構的標準序列化或是他們自定義的二進位制格式,通常認為後者才是最佳實踐, 因為序列化的二進位制檔案格式成為類輸出 API的一部分, 可能破壞 Java 中私有和包可見的屬性的封裝.

## 2. 如何序列化

讓 Java中的類可以序列化很簡單. 你的 Java 類只需要實現 java.io.Serializable 介面, JVM 就會把 Object物件按預設格式序列化. 讓一個類是可序列化的需要有意為之. 類可序列會可能為是一個長期代價, 可能會因此而限制你修改或改變其實現.當你透過實現新增介面來更改類的結構時, 新增或刪除任何欄位可能會破壞預設序列化, 這可以透過自定義二進位制格式使不相容的可能性最小化,但仍需要大量的努力來確保向後相容性。序列化如何限制你更改類的能力的一個示例是 SerialVersionUID。如果不顯式宣告SerialVersionUID, 則 JVM 會根據類結構生成其結構, 該結構依賴於類實現介面和可能更改的其他幾個因素。假設你新版本的類檔案實現的另一個介面, JVM 將生成一個不同的 SerialVersionUID 的,當你嘗試載入舊版本的程式序列化的舊物件時, 你將獲得無效類異常 InvalidClassException。

## 3. Java 中的可序列化介面和可外部介面之間的區別是什麼?

Externalizable給我們提供 writeExternal() 和 readExternal() 方法, 這讓我們靈活地控制 Java 序列化機制, 而不是依賴於Java 的預設序列化。 正確實現 Externalizable 介面可以顯著提高應用程式的效能。

```java
public interface Serializable {
}

public interface Externalizable extends java.io.Serializable {
/**
* The object implements the writeExternal method to save its contents
* by calling the methods of DataOutput for its primitive values or
* calling the writeObject method of ObjectOutput for objects, strings,
* and arrays.
*
* @serialData Overriding methods should use this tag to describe
* the data layout of this Externalizable object.
* List the sequence of element types and, if possible,
* relate the element to a public/protected field and/or
* method of this Externalizable class.
*
* @param out the stream to write the object to
* @exception IOException Includes any I/O exceptions that may occur
*/
void writeExternal(ObjectOutput out) throws IOException;

/**
* The object implements the readExternal method to restore its
* contents by calling the methods of DataInput for primitive
* types and readObject for objects, strings and arrays. The
* readExternal method must read the values in the same sequence
* and with the same types as were written by writeExternal.
*
* @param in the stream to read data from in order to restore the object
* @exception IOException if I/O errors occur
* @exception ClassNotFoundException If the class for an object being
* restored cannot be found.
*/
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
```

## 4.serialVersionUID的作用

SerialVersionUID是一個用來標識Serializable類版本的唯一識別符號。它的作用是在序列化和反序列化過程中用來驗證類的版本一致性,確保反序列化過程中的類的版本和序列化時的版本是一致的,以避免出現類版本不一致導致的問題。如果在反序列化時發現類的版本不一致,就會丟擲InvalidClassException異常。因此,SerialVersionUID是用來確保類的版本一致性的重要機制。

## 5. 序列化時,某些變數不希望被序列化怎麼辦?

如果你不希望任何欄位是物件的狀態的一部分, 然後宣告它靜態或瞬態(trasient)根據你的需要, 這樣就不會是在 Java 序列化過程中被包含在內。

# 三、執行緒相關問題

## 1. 建立執行緒的方式有哪些?

- 直接繼承Thread
- 實現Runnable介面
- 實現Callable介面(Runnable介面)
- 透過執行緒池的方式獲取

## 2. Thread和Runnable的區別

每一個執行緒其實就是一個Thread物件

- 一個是繼承和介面
- Runnable介面定義run方法的邏輯。多個Thread物件可以執行相關的邏輯

## 3. 為什麼wait和notify方法要寫在同步塊中?

當一個執行緒需要呼叫物件的wait()方法的時候,這個執行緒必須擁有該物件的鎖,接著它就會釋放這個物件鎖並進入等待狀態直到其他執行緒呼叫這個物件上的notify()方法。同樣的,當一個執行緒需要呼叫物件的notify()方法時,它會釋放這個物件的鎖,以便其他在等待的執行緒就可以得到這個物件鎖。由於所有的這些方法都需要執行緒持有物件的鎖,這樣就只能透過同步來實現,所以他們只能在同步方法或者同步塊中被呼叫。如果你不這麼做,程式碼會丟擲IllegalMonitorStateException異常。

## 4.執行緒的生命週期是怎麼樣的

生命週期:物件從建立到銷燬的全過程

執行緒的生命週期:執行緒物件(Thread)從開始到銷燬的全過程

## 5. 簡述一下你對執行緒池的理解

如果問到了這樣的問題,可以展開的說一下執行緒池如何用、執行緒池的好處、執行緒池的啟動策略
合理利用執行緒池能夠帶來三個好處。
第一:降低資源消耗。透過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。

第二:提高響應速度。當任務到達時,任務可以不需要等到執行緒建立就能立即執行。
第三:提高執行緒的可管理性。執行緒是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一的分配,調優和監控。

```java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)

```

引數含義:

```txt
corePoolSize:執行緒池核心執行緒數量
maximumPoolSize:執行緒池最大執行緒數量
keepAliverTime:當活躍執行緒數大於核心執行緒數時,空閒的多餘執行緒最大存活時間
unit:存活時間的單位
workQueue:存放任務的佇列
handler:超出執行緒範圍和佇列容量的任務的處理程式
```

執行緒池工作原理:

提交一個任務到執行緒池中,執行緒池的處理流程如下:

1. 判斷執行緒池裡的核心執行緒是否都在執行任務,如果不是(核心執行緒空閒或者還有核心執行緒沒有被建立)則建立一個新的工作執行緒來執行任務。如果核心執行緒都在執行任務,則進入下個流程。
2. 執行緒池判斷工作佇列是否已滿,如果工作佇列沒有滿,則將新提交的任務儲存在這個工作佇列裡。如果工作佇列滿了,則進入下個流程。
3. 判斷執行緒池裡的執行緒是否都處於工作狀態,如果沒有,則建立一個新的工作執行緒來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。

# 四、IO相關面試題

## 1.解釋下:同步、非同步、阻塞、非阻塞

同步和非同步指的是:當前執行緒是否需要等待方法呼叫執行完畢。

阻塞和非阻塞指的是:當前介面資料還未準備就緒時,執行緒是否被阻塞掛起

同步&非同步其實是處於框架這種高層次維度來看待的,而阻塞&非阻塞往往針對底層的系統呼叫方面來抉擇,也就是說兩者是從不同維度來考慮的。

這四個概念兩兩組合,會形成4個新的概念,如下:

**同步阻塞**:客戶端傳送請求給服務端,此時服務端處理任務時間很久,則客戶端則被服務端堵塞了,所以客戶端會一直等待服務端的響應,此時客戶端不能做其他任何事,服務端也不會接受其他客戶端的請求。這種通訊機制比較簡單粗暴,但是效率不高。

**同步非阻塞**:客戶端傳送請求給服務端,此時服務端處理任務時間很久,這個時候雖然客戶端會一直等待響應,但是服務端可以處理其他的請求,過一會回來處理原先的。這種方式很高效,一個服務端可以處理很多請求,不會在因為任務沒有處理完而堵著,所以這是非阻塞的。

**非同步阻塞**:客戶端傳送請求給服務端,此時服務端處理任務時間很久,但是客戶端不會等待伺服器響應,它可以做其他的任務,等伺服器處理完畢後再把結果響應給客戶端,客戶端得到回撥後再處理服務端的響應。這種方式可以避免客戶端一直處於等待的狀態,最佳化了使用者體驗,其實就是類似於網頁裡發起的ajax非同步請求。

**非同步非阻塞**:客戶端傳送請求給服務端,此時服務端處理任務時間很久,這個時候的任務雖然處理時間會很久,但是客戶端可以做其他的任務,因為他是非同步的,可以在回撥函式里處理響應;同時服務端是非阻塞的,所以服務端可以去處理其他的任務,如此,這個模式就顯得非常的高效了。

## 2.什麼是BIO?

**BIO** : **同步並阻塞** ,伺服器實現一個連線一個執行緒,即客戶端有連線請求時伺服器端就需要啟動一個執行緒進行處理,沒處理完之前此執行緒不能做其他操作(如果是單執行緒的情況下,我傳輸的檔案很大呢?),當然可以透過執行緒池機制改善。

BIO方式 **適用於連線數目比較小且固定的架構** ,這種方式對伺服器資源要求比較高,併發侷限於應用中JDK1.4以前的唯一選擇,但程式直觀簡單易理解。

## 3.什麼是NIO?

**NIO** : **同步非阻塞** ,伺服器實現一個連線一個執行緒,即客戶端傳送的連線請求都會註冊到多路複用器上,多複用器輪詢到連線有I/O請求時才啟動一個執行緒進行處理。

NIO方式 **適用於連線數目多且連線比較短(輕操作)的架構** ,比如聊天伺服器,併發侷限於應用中,程式設計比較複雜,JDK1.4之後開始支援。

## 4.什麼是AIO?

**AIO** : **非同步非阻塞** ,伺服器實現模式為一個有效請求一個執行緒,客戶端的I/O請求都是由作業系統先完成了再通知伺服器應用去啟動執行緒進行處理,AIO方式使用於連線數目多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫作業系統參與併發操作,程式設計比較複雜,JDK1.7之後開始支援。

AIO屬於NIO包中的類實現,其實 **IO主要分為BIO和NIO** ,AIO只是附加品,解決IO不能非同步的實現在以前很少有Linux系統支援AIO,Windows的IOCP就是該AIO模型。但是現在的伺服器一般都是支援AIO操作

## 5.位元組流和字元流的介紹

- 位元組流繼承inputStream和OutputStream
- 字元流繼承自InputSteamReader和OutputStreamWriter

字元流和位元組流的使用非常相似,但是實際上位元組流的操作不會經過緩衝區(記憶體)而是直接操作文字本身的,而字元流的操作會先經過緩衝區(記憶體)然後透過緩衝區再操作檔案

在選擇流型別時,需要考慮到處理的資料型別。如果處理的是文字資料,應選擇字元流;如果處理的是二進位制資料或非文字資料,應選擇位元組流。

# 五、JavaWEB面試題

## 1. 什麼是網路程式設計

網路程式設計的本質是多臺計算機之間的資料交換。資料傳遞本身沒有多大的難度,不就是把一個裝置中的資料傳送給其他裝置,然後接受另外一個裝置反饋的資料。現在的網路程式設計基本上都是基於請求/響應方式的,也就是一個裝置傳送請求資料給另外一個,然後接收另一個裝置的反饋。

在網路程式設計中,發起連線程式,也就是傳送第一次請求的程式,被稱作客戶端(Client),等待其他程式連線的程式被稱作伺服器(Server)。客戶端程式可以在需要的時候啟動,而伺服器為了能夠時刻相應連線,則需要一直啟動。例如以打電話為例,首先撥號的人類似於客戶端,接聽電話的人必須保持電話暢通類似於伺服器。連線一旦建立以後,就客戶端和伺服器端就可以進行資料傳遞了,而且兩者的身份是等價的。在一些程式中,程式既有客戶端功能也有伺服器端功能,最常見的軟體就是QQ、微信這類軟體了。

## 2. 網路程式設計中的兩個主要問題是如何解決的

1. 一個是如何準確的定位網路上一臺或多臺主機
2. 另一個就是找到主機後如何可靠高效的進行資料傳輸,

在TCP/IP協議中IP層主要負責網路主機的定位,資料傳輸的路由,由IP地址可以唯一地確定Internet上的一臺主機。而TCP層則提供面向應用的可靠(TCP)的或非可靠(UDP)的資料傳輸機制,這是網路程式設計的主要物件,一般不需要關心!P層是如何處理資料的。
目前較為流行的網路程式設計模型是客戶機/伺服器(C/S)結構。即通訊雙方一方作為伺服器等待客戶提出請求並予以響應。客戶則在雲要服務時向伺服器提 出申請。伺服器一般作為守護程序始終執行,監聽網路埠,一旦有客戶請求,就會啟動一個服務程序來響應該客戶,同時自己繼續監聽服務埠,使後來的客戶也 能及時得到服務。

## 3. 網路協議是什麼

在計算機網路要做到井井有條的交換資料,就必須遵守一些事先約定好的規則,比如交換資料的格式、是否需要傳送一個應答資訊。這些規則被稱為網路協議。

## 4.介紹下OSI七層和TCP/IP四層的關係

為了更好地促進網際網路的研究和發展,國際標準化組織ISO在1985 年指定了網路互聯模型。OSI 參考模型(Open System Interconnect Reference [Model](https://so.csdn.net/so/search?q=Model&spm=1001.2101.3001.7020)),具有 7 層結構

**應用層**:各種應用程式協議,比如HTTP、HTTPS、FTP、SOCKS安全套接字協議、DNS域名系統、GDP閘道器發現協議等等。
**表示層**:加密解密、轉換翻譯、壓縮解壓縮,比如LPP輕量級表示協議。
**會話層**:不同機器上的使用者建立和管理會話,比如SSL安全套接字層協議、TLS傳輸層安全協議、RPC遠端過程呼叫協議等等。

**傳輸層**:接受上一層的資料,在必要的時候對資料進行分割,並將這些資料交給網路層,保證這些資料段有效到達對端,比如TCP傳輸控制協議、UDP資料包協議。
**網路層**:控制子網的執行:邏輯編址、分組傳輸、路由選擇,比如IP、IPV6、SLIP等等。
**資料鏈路層**:物理定址,同時將原始位元流轉變為邏輯傳輸路線,比如XTP壓縮傳輸協議、PPTP點對點隧道協議等等。
**物理層**:機械、電子、定時介面通訊通道上的原始位元流傳輸,比如IEEE802.2等等。

而且在訊息通訊的過程中具體的執行流程為:

網路傳輸的資料其實會透過這七層協議來進行資料的封裝和拆解

## 5.TCP原理

三次握手:

1.第一次握手:客戶端將標誌位syn重置為1,隨機產生seq=a,並將資料包傳送給服務端
2.第二次握手:服務端收到syn=1知道客戶端請求連線,服務端將syn和ACK都重置為1,ack=a+1,隨機產一個值seq=b,並將資料包傳送給客戶端,服務端進入syn_RCVD狀態。
3.第三次握手:客戶端收到確認後,檢查ack是否為a+1,ACK是否為1,若正確將ACK重置為1,將ack改為b+1,然後將資料包傳送給服務端服務端檢查ack與ACK,若都正確,就建立連線,進入ESTABLISHEN.

四次揮手:

1.開始雙方都處於連線狀態
2.客戶端程序發出FIN報文,並停止傳送資料,在報文中FIN結束標誌為1,seq為a連線狀態下傳送給伺服器的最後一個位元組的序號+1,報文傳送結束後,客戶端進入FIN-WIT1狀態。
3.服務端收到報文,向客戶端傳送確認報文,ACK=1,seq為b服務端給客戶端傳送的最後位元組的序號+1,ack=a+1,傳送後客戶端進入close-wait狀態,不再傳送資料,但服務端傳送資料客戶端一九可以收到(城為半關閉狀態)。
4.客戶端收到伺服器的確認報文後,客戶端進入fin-wait2狀態進行等待伺服器傳送第三次的揮手報文。
5.服務端向fin報文FIN=1ACK=1,seq=c(伺服器向客戶端傳送最後一個位元組序號+1),ack=b+1,傳送結束後伺服器進入last-ack狀態等待最後的確認。
6.客戶端收到是釋放報文後,向伺服器傳送確認報文進入time-wait狀態,後進入close
7.服務端收到確認報文進入close狀態。

相關文章