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