百度一面,頂不住
撈撈面經
題目來源:https://www.nowcoder.com/feed/main/detail/d39aabc0debd4dba810b4b9671d54348
注:養成先看真題,自己模擬回答,再看解析參考(別忘隨手一鍵三連哦~)
1.基礎題
- 有幾種網絡io模型?
- 異步網絡模型在什么場景下你了解有應用過?(回答了線程相關的場景)
- 除了用線程完成,還有什么操作可以完成異步操作?
- 同步阻塞和同步非阻塞在Java層面怎么實現?(說前面網絡io模型答得挺順暢,具體實現細節還需要提升一下)
- 描述一下一次完整的 Http 請求
- 知道的長連接有幾種實現方式?
- 一個 Http 請求包含哪幾部分內容?
2.代碼題
- 設計一個 HashSet(完全不會)
3.場景題
- 1T 的數據怎么加載到 200M 的內存中,并且找到兩行一樣的數據?
- Java 打開 1T 文件,第一部操作做什么?
- 用代碼打開一個文件和用鼠標打開一個文件有什么區別?
注意:博主基礎題即不過多介紹,只選經典題目分析。
你了解哪些網絡 IO 模型?
常見的網絡IO模型有以下幾種:
- 阻塞式IO模型(Blocking IO Model):在這種模型中,當一個線程執行一個 IO 操作時,它會一直阻塞,直到 IO 操作完成。
- 非阻塞式IO模型(Non-Blocking IO Model):在這種模型中,當一個線程執行一個IO操作時,它不會一直阻塞,而是會立即返回,告訴調用者 IO 操作是否完成。
- 多路復用IO模型(Multiplexed IO Model):一個線程可以同時監視多個 IO 操作,當有一個 IO 操作完成時,它會通知線程進行處理。
- 信號驅動式IO模型(Signal Driven IO Model):在這種模型中,當一個 IO 操作完成時,操作系統會向應用程序發送一個信號,應用程序在接收到信號后進行處理。
- 異步IO模型(Asynchronous IO Model):應用程序發起一個 IO 操作后,不需要等待操作完成,而是可以繼續執行其他操作,當 IO 操作完成后,操作系統會通知應用程序進行處理。
異步網絡模型你在什么場景下使用過,具體可以應用到哪些地方?
- 高并發的Web應用程序:在Web應用程序中,異步網絡模型可以提高服務器的并發處理能力,減少線程的阻塞等待時間,提高系統的吞吐量。
- 高性能的網絡服務器:在網絡服務器中,異步網絡模型可以提高服務器的并發處理能力,減少線程的阻塞等待時間,提高系統的吞吐量。
- 大規模的實時數據處理系統:在實時數據處理系統中,異步網絡模型可以提高數據的處理效率,減少數據處理的延遲時間,提高系統的實時性。
- 大規模的分布式系統:在分布式系統中,異步網絡模型可以提高系統的并發處理能力,減少線程的阻塞等待時間,提高系統的吞吐量。
異步網絡模型可以應用于任何需要高并發、高性能、高實時性的場景,以提高系統的性能和可擴展性,提高用戶體驗。
能結合具體業務場景舉個例嗎?
異步網絡模型在社交和購物等場景下也非常常見。比如:
- 社交應用程序:在社交應用程序中,異步網絡模型可以用于處理用戶的聊天消息、動態更新等請求,提高系統的實時性和性能。
- 購物網站:在購物網站中,異步網絡模型可以用于處理用戶的訂單、支付、物流等請求,提高系統的并發處理能力和性能。
舉個具體實際的例子,常常玩的 王者榮耀。(個人看法)它需要處理大量的游戲玩家請求,包括登錄、注冊、查詢游戲數據、游戲操作等。如果使用阻塞式IO模型,每個請求都需要創建一個線程來處理,當并發請求量較大時,線程的創建和銷毀會帶來很大的開銷,導致服務器的性能和吞吐量下降。
而如果使用異步網絡模型,可以通過 事件驅動的方式處理請求,當有玩家請求到達時,服務器不需要創建新的線程,而是通 過異步IO操作來處理請求,當IO操作完成后,服務器會回調相應的處理函數進行處理,這樣可以大大減少線程的創建和銷毀開銷,提高服務器的性能和吞吐量。
另外,異步網絡模型還可以應用于實時數據處理系統,比如金融交易系統、在線廣告系統等,這些系統需要實時處理大量的數據請求,如果使用阻塞式IO模型,會導致數據處理的延遲時間較長,影響系統的實時性。而使用異步網絡模型,可以通過事件驅動的方式實時處理數據請求,提高系統的實時性和性能。
怎樣可以完成異步操作?
- 回調函數:在 Java 中,可以使用回調函數的方式來完成異步操作,比如使用 Java 的回調接口或者 Lambda 表達式來實現異步回調。
- Future對象:Future 對象是 Java 中的一種異步編程解決方案,它可以將異步操作封裝成一個 Future 對象,然后使用 Future.get() 方法來等待異步操作的完成,從而實現異步操作的同步化編程。
- CompletableFuture對象:CompletableFuture是Java 8中新增的異步編程解決方案,它可以將異步操作封裝成一個 CompletableFuture 對象,然后使用 CompletableFuture的方法來處理異步操作的結果,比如 thenApply()、thenAccept()、thenRun()等方法。
- 異步框架:可以采用一些異步框架可以用于實現異步操作,比如Netty、Vert.x等框架,它們可以通過事件驅動的方式實現異步操作,提高系統的性能和可擴展性。
在Java中,同步阻塞和同步非阻塞可以通過不同的IO模型來實現?
在Java中,同步阻塞和同步非阻塞可以通過不同的 IO 模型來實現。
- 同步阻塞 IO 模型:在Java中,同步阻塞 IO 模型是最常見的 IO 模型,它使用 InputStream 和 OutputStream 等阻塞式 IO 類來實現數據的讀寫操作。在同步阻塞 IO 模型中,當一個線程調用阻塞式 IO 類的 read() 或 write() 方法時,該線程會被阻塞,直到IO操作完成或者出現異常。
- 同步非阻塞 IO 模型:在Java中,同步非阻塞 IO 模型可以通過使用Java NIO(New IO)來實現。Java NIO 提供了一種基于通道和緩沖區的IO模型,可以實現非阻塞式的IO操作。在同步非阻塞 IO 模型中,當一個線程調用 Java NIO 的通道的 read() 或 write() 方法時,該線程不會被阻塞,而是立即返回,然后可以通過輪詢的方式來檢查 IO 操作的狀態,從而實現非阻塞式的 IO 操作。
能結合具體場景講解嗎?
當涉及到高并發、高性能、高可靠性的場景時,選擇合適的 IO 模型非常重要。下面結合具體場景來講解:
- Web服務器:對于 Web 服務器來說,同步阻塞 IO 模型是最常用的IO模型,因為它可以提供穩定的性能和可靠性。在Java中,可以使用Servlet API來實現同步阻塞 IO 模型。如果需要更高的性能和可擴展性,可以考慮使用異步 IO 模型,比如 Java NIO 或者 Netty 等框架。
- 游戲服務器:對于游戲服務器來說,需要處理大量的并發連接和實時數據交互,因此同步非阻塞 IO 模型是比較適合的選擇。在Java中,可以使用 Java NIO 或者 Netty 等框架來實現同步非阻塞IO模型。
- 數據庫訪問:對于數據庫訪問來說,同步阻塞IO模型是最常用的IO模型,因為它可以提供穩定的性能和可靠性。在 Java中,可以使用JDBC API 來實現同步阻塞 IO 模型。如果需要更高的性能和可擴展性,可以考慮使用異步 IO 模型,比如使用異步數據庫驅動程序,比如HikariCP等。
除了同步阻塞和同步非阻塞 IO 模型之外,還有一些其他的 IO 模型,比如異步IO模型、多路復用IO模型等。在實際應用中,應該根據具體的場景和需求來選擇合適的 IO 模型。
描述一下一次完整的 Http 請求?
一次完整的HTTP請求通常包括以下步驟:(如果是從瀏覽器發起地址請求,還需要地址各種解析哦~)
- 建立 TCP 連接:客戶端通過 TCP 協議與服務器建立連接,進行 “三次握手”。客戶端發送 SYN 包,服務器回應 SYN+ACK 包,客戶端再回應 ACK 包,完成連接建立。
- 發送 HTTP 請求:客戶端向服務器發送 HTTP 請求,請求中包含請求行、請求頭和請求體。請求行包括請求方法、請求URL和HTTP協議版本;請求頭包括一些附加信息,比如請求頭部字段、Cookie 等;請求體包含請求的數據,比如POST請求中的表單數據。
- 服務器處理請求:服務器接收到客戶端發送的 HTTP 請求后,會根據請求的內容進行處理,比如查詢數據庫、讀取文件等。
- 服務器返回 HTTP 響應:服務器處理完請求后,會向客戶端返回 HTTP 響應,響應中包含響應行、響應頭和響應體。響應行包括 HTTP 協議版本、狀態碼和狀態描述;響應頭包括一些附加信息,比如響應頭部字段、Cookie 等;響應體包含響應的數據,比如 HTML 頁面、JSON 數據等。
- 關閉TCP連接:客戶端接收到服務器返回的HTTP響應后,會關閉TCP連接,進行 “四次揮手”。客戶端發送 FIN 包,服務器回應 ACK 包,然后服務器發送 FIN 包,客戶端回應ACK 包,完成連接關閉。
總之,一次完整的 HTTP 請求包括建立 TCP 連接、發送 HTTP 請求、服務器處理請求、服務器返回 HTTP 響應和關閉 TCP 連接等步驟。在實際應用中,還需要考慮 HTTP 緩存、Cookie、會話管理等問題。
長連接有哪些實現方式?
- 長連接是指客戶端和服務器之間保持連接狀態,可以在一定時間內進行多次請求和響應,而不必每次請求都重新建立連接。
- 長連接可以減少連接建立和斷開的開銷,提高網絡傳輸效率,常用于實時通信、推送服務等場景。
常見的長連接實現方式包括:
- HTTP長連接:HTTP/1.1 協議支持長連接,客戶端和服務器之間可以保持連接狀態,可以在一定時間內進行多次請求和響應。在 HTTP 長連接中,客戶端發送請求后,服務器會保持連接狀態,直到客戶端發送關閉連接的請求或者超時時間到達。
- WebSocket:WebSocket 是一種基于 HTTP 協議的長連接技術,它可以在客戶端和服務器之間建立雙向通信的連接,實現實時通信和推送服務。WebSocket 協議通過 HTTP協議的升級實現,客戶端和服務器之間可以發送和接收數據幀,而不必重新建立連接。
- TCP長連接:TCP 協議支持長連接,客戶端和服務器之間可以保持連接狀態,可以在一定時間內進行多次請求和響應。在TCP長連接中,客戶端和服務器之間建立連接后,可以保持連接狀態,直到客戶端或服務器發送關閉連接的請求或者網絡異常斷開連接。
長連接可以提高網絡傳輸效率,常用于實時通信、推送服務等場景
設計一個Hashset?
我隨便設計的一個簡單的 Hashset(僅供參考):
- 定義一個哈希表數組,數組的長度為質數,每個元素是一個鏈表,用于存儲哈希沖突的元素。
- 定義一個哈希函數,將元素映射到哈希表數組中的一個位置。可以使用取模運算或者位運算等方式實現哈希函數。
- 實現添加元素的方法。首先根據哈希函數計算元素的哈希值,然后將元素添加到對應位置的鏈表中。如果鏈表中已經存在相同的元素,則不添加。
- 實現刪除元素的方法。首先根據哈希函數計算元素的哈希值,然后在對應位置的鏈表中查找元素,如果找到則刪除。
- 實現查找元素的方法。首先根據哈希函數計算元素的哈希值,然后在對應位置的鏈表中查找元素,如果找到則返回元素,否則返回null。
- 實現獲取元素個數的方法。遍歷哈希表數組,統計所有鏈表中元素的個數。
- 實現清空哈希表的方法。遍歷哈希表數組,將所有鏈表清空。
下面是一個簡單的Java代碼實現:
public class MyHashSet<T> {
private static final int DEFAULT_CAPACITY = 16;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private Node<T>[] table;
private int size;
private int threshold;
private float loadFactor;
public MyHashSet() {
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public MyHashSet(int initialCapacity, float loadFactor) {
table = new Node[initialCapacity];
this.loadFactor = loadFactor;
threshold = (int) (initialCapacity * loadFactor);
}
public boolean add(T value) {
int hash = hash(value);
int index = indexFor(hash, table.length);
Node<T> node = table[index];
while (node != null) {
if (node.value.equals(value)) {
return false;
}
node = node.next;
}
Node<T> newNode = new Node<>(value, table[index]);
table[index] = newNode;
size++;
if (size > threshold) {
resize(table.length * 2);
}
return true;
}
public boolean remove(T value) {
int hash = hash(value);
int index = indexFor(hash, table.length);
Node<T> node = table[index];
Node<T> prev = null;
while (node != null) {
if (node.value.equals(value)) {
if (prev == null) {
table[index] = node.next;
} else {
prev.next = node.next;
}
size--;
return true;
}
prev = node;
node = node.next;
}
return false;
}
public boolean contains(T value) {
int hash = hash(value);
int index = indexFor(hash, table.length);
Node<T> node = table[index];
while (node != null) {
if (node.value.equals(value)) {
return true;
}
node = node.next;
}
return false;
}
public int size() {
return size;
}
public void clear() {
Arrays.fill(table, null);
size = 0;
}
private int hash(T value) {
return value.hashCode();
}
private int indexFor(int hash, int length) {
return hash & (length - 1);
}
private void resize(int newCapacity) {
Node<T>[] newTable = new Node[newCapacity];
for (Node<T> node : table) {
while (node != null) {
Node<T> next = node.next;
int index = indexFor(hash(node.value), newCapacity);
node.next = newTable[index];
newTable[index] = node;
node = next;
}
}
table = newTable;
threshold = (int) (newCapacity * loadFactor);
}
private static class Node<T> {
T value;
Node<T> next;
public Node(T value, Node<T> next) {
this.value = value;
this.next = next;
}
}
}
1T 的數據怎么加載到 200M 的內存中,并找到兩行一樣的數據?
將 1T 的數據加載到 200M 的內存中是不可能的,因為1T 的數據遠遠超過了 200M 的內存大小。因此,需要采用一些特殊的算法和技術來解決這個問題。
一種解決方案是使用 外部排序算法,將1T的數據分成多個小文件,每個小文件可以加載到內存中進行排序。然后,使用歸并排序的思想將這些小文件合并成一個大文件,并在合并的過程中找到兩行一樣的數據。
具體步驟如下(參考):
- 將 1T 的數據分成多個小文件,每個小文件的大小為 200M
- 對每個小文件進行排序,可以使用快速排序等算法。
- 將排序后的小文件合并成一個大文件,可以使用歸并排序的思想。
- 在合并的過程中,記錄前一個文件的最后一行和當前文件的第一行,比較這兩行是否相同,如果相同則找到了兩行一樣的數據。
- 最后,將找到的兩行一樣的數據輸出即可。
而在實際操作中,還需要考慮磁盤讀寫速度、文件的讀寫方式等因素,以提高算法的效率和準確性。
Java打開 1T 的文件,第一步做什么?
在 Java 中打開 1T 的文件,第一步應該是確定文件的讀取方式和讀取范圍。
- 確定文件的讀取方式:根據文件的類型和大小,選擇適當的文件讀取方式。如果文件是文本文件,可以使用 BufferedReader 逐行讀取;如果文件是二進制文件,可以使用DataInputStream 或者 FileChannel 進行讀取。
- 確定文件的讀取范圍:由于1T的文件非常大,無法一次性讀取到內存中,因此需要確定讀取的范圍。可以將文件分成多個塊,每次讀取一個塊的數據,處理完后再讀取下一個塊的數據。可以根據文件的大小和內存的大小來確定塊的大小。
用代碼打開一個文件和用鼠標打開用什么區別嗎?
其底層區別主要在于操作系統和文件系統的交互方式。
用鼠標打開文件是通過操作系統提供的圖形用戶界面(GUI)來實現的,用戶點擊圖標,但實際操作系統會根據用戶的操作來調用相應的API,從而實現文件的打開、讀取、寫入等操作。而這些 API 實際通常是操作系統提供的底層文件系統接口,例如 Windows 的 Win32 API、Linux 的 POSIX API 等。
而用代碼打開文件則是 **通過編程語言提供的文件操作API **來實現的,這些API通常是對操作系統底層文件系統接口的封裝和抽象。通常可以使用 File、FileInputStream、FileOutputStream 等類來實現文件的打開、讀取、寫入等操作,這些類會調用底層的操作系統文件系統接口來實現相應的功能。
因此,從底層的角度來看,用代碼打開文件和用鼠標打開文件的區別在于調用的API不同,但底層的文件系統接口是相同的。
一次 Http 請求包含哪幾部分內容?
- 請求行(Request Line):包含請求方法、請求的URL和HTTP協議版本。常見的請求方法有 GET、POST、PUT、DELETE 等。
- 請求頭部(Request Headers):包含請求的各種頭部信息,例如 User-Agent、Content-Type、Cookie 等。頭部信息提供了關于請求的附加信息,用于服務器處理請求。
- 請求體(Request Body):對于 GET 請求,請求體通常為空。對于 POST 請求等需要傳遞數據的請求,請求體包含了要發送給服務器的數據。