1:TCP和UDP的區別,TCP為什么是三次握手,不是兩次?
因為TCP是全雙工協議,區別在于TCP可靠,UDP不可靠,效率更高。
詳解:
TCP(傳輸控制協議)和 UDP(用戶數據報協議)是兩種不同的傳輸層協議,它們有以下區別:
- 連接方式
- TCP 是面向連接的協議,在數據傳輸之前,需要先建立連接,數據傳輸完成后,需要關閉連接。
- UDP 是無連接的協議,不需要建立連接,直接將數據報發送出去。
- 可靠性
- TCP 提供可靠的傳輸服務,通過序列號、確認應答、重傳機制等保證數據的準確傳輸。
- UDP 不保證數據的可靠傳輸,可能會出現數據丟失、重復或亂序的情況。
- 傳輸效率
- TCP 由于需要建立連接、進行確認應答等,傳輸效率相對較低。
- UDP 沒有這些額外的開銷,傳輸效率較高。
- 應用場景
- TCP 適用于對數據準確性要求高的場景,如文件傳輸、電子郵件、遠程登錄等。
- UDP 適用于對實時性要求高、對數據準確性要求相對較低的場景,如視頻會議、在線游戲、實時監控等。
TCP 采用三次握手而不是兩次握手,主要有以下原因:
- 防止已失效的連接請求報文段被誤接收:如果采用兩次握手,當客戶端發送的第一個連接請求報文段在網絡中滯留,沒有及時到達服務器,而客戶端又重新發送了一個連接請求報文段,服務器收到后發送確認報文段并建立連接。此時,如果第一個連接請求報文段又到達了服務器,服務器會認為這是客戶端的又一次連接請求,從而再次建立連接,這樣就會導致服務器資源的浪費。而采用三次握手,客戶端在收到服務器的確認報文段后,會發送一個確認報文段給服務器,服務器只有在收到客戶端的確認報文段后,才會認為連接建立成功,這樣就可以避免已失效的連接請求報文段被誤接收。
- 確保雙方都能正常收發數據:三次握手可以讓客戶端和服務器都確認對方的接收和發送能力。第一次握手,客戶端向服務器發送連接請求,服務器可以知道客戶端的發送能力正常;第二次握手,服務器向客戶端發送確認報文段,客戶端可以知道服務器的接收和發送能力正常;第三次握手,客戶端向服務器發送確認報文段,服務器可以知道客戶端的接收能力正常。這樣,通過三次握手,雙方都可以確認對方的接收和發送能力正常,從而保證數據的可靠傳輸。
TCP 三次握手的過程如下:
第一次握手:客戶端發送 SYN 報文
- 客戶端向服務器發送一個帶有 SYN(同步序列號)標志的 TCP 報文段,該報文段中還包含客戶端隨機生成的初始序列號(Sequence Number,Seq),假設為 x。這個 SYN 報文表示客戶端希望與服務器建立連接,并告知服務器自己的初始序列號。
- 此時,客戶端進入 SYN_SENT 狀態,等待服務器的響應。
第二次握手:服務器發送 SYN + ACK 報文
- 服務器接收到客戶端的 SYN 報文后,會檢查其中的信息。如果服務器同意建立連接,它會向客戶端發送一個 SYN + ACK 報文段。這個報文段中,SYN 標志位為 1,表示這也是一個同步請求,同時 ACK 標志位也為 1,表示對客戶端 SYN 報文的確認。
- 服務器會為自己生成一個初始序列號,假設為 y,同時在報文中的確認號(Acknowledgment Number,Ack)字段設置為客戶端的序列號加 1,即 x + 1,表示服務器已經成功接收了客戶端的 SYN 報文,期望客戶端發送序列號為 x + 1 的數據。
- 發送完 SYN + ACK 報文后,服務器進入 SYN_RCVD 狀態。
第三次握手:客戶端發送 ACK 報文
- 客戶端收到服務器的 SYN + ACK 報文后,會檢查其中的確認號是否正確以及 SYN 標志位。如果確認無誤,客戶端會向服務器發送一個 ACK 報文段,ACK 標志位為 1,確認號為服務器的序列號加 1,即 y + 1,而序列號為客戶端在第一次握手中使用的序列號加 1,即 x + 1。
- 發送完 ACK 報文后,客戶端進入 ESTABLISHED 狀態,表示連接已經建立成功,可以開始發送數據。
- 服務器收到客戶端的 ACK 報文后,也會進入 ESTABLISHED 狀態,至此,TCP 連接建立完成,雙方可以開始進行數據傳輸。
TCP 連接的揮手過程用于終止已經建立的連接,通常需要四次揮手,具體過程如下:
第一次揮手:客戶端發送 FIN 報文
- 當客戶端數據傳輸完畢,想要關閉連接時,會向服務器發送一個帶有 FIN(結束標志)標志的 TCP 報文段,該報文段中還包含客戶端的序列號(假設為 m)。這個 FIN 報文表示客戶端不再有數據要發送給服務器,但仍然可以接收服務器的數據。
- 發送 FIN 報文后,客戶端進入 FIN_WAIT_1 狀態。
第二次揮手:服務器發送 ACK 報文
- 服務器接收到客戶端的 FIN 報文后,會發送一個 ACK 報文作為應答。該 ACK 報文的確認號為客戶端的序列號加 1,即 m + 1,而序列號為服務器自己當前的序列號(假設為 n)。這個 ACK 報文表示服務器已經收到客戶端的 FIN 報文,同意關閉客戶端到服務器方向的連接。
- 服務器發送完 ACK 報文后,客戶端到服務器方向的連接就關閉了,但服務器到客戶端方向的連接仍然可以繼續傳輸數據。此時客戶端進入 FIN_WAIT_2 狀態,而服務器進入 CLOSE_WAIT 狀態。
第三次揮手:服務器發送 FIN 報文
- 當服務器也完成了數據傳輸,不再需要向客戶端發送數據時,它會向客戶端發送一個 FIN 報文,其序列號為服務器之前的序列號加 1,即 n + 1(如果在發送 FIN 之前服務器又發送了一些數據,那么序列號會相應增加),表示服務器也希望關閉連接。
- 發送 FIN 報文后,服務器進入 LAST_ACK 狀態。
第四次揮手:客戶端發送 ACK 報文
- 客戶端收到服務器的 FIN 報文后,會發送一個 ACK 報文進行確認。該 ACK 報文的確認號為服務器的序列號加 1,即 n + 2,序列號為客戶端在 FIN_WAIT_2 狀態下的序列號加 1(即 m + 1,如果客戶端在等待過程中沒有收到其他數據)。
- 客戶端發送完 ACK 報文后,進入 TIME_WAIT 狀態。經過一段時間(通常是 2 倍的 MSL,即最長報文段壽命)后,客戶端才會真正關閉連接。服務器收到 ACK 報文后,立即關閉連接,進入 CLOSED 狀態。設置 TIME_WAIT 狀態及等待時間是為了確保客戶端發送的 ACK 報文能夠被服務器正確接收,如果服務器沒有收到 ACK 報文,會重新發送 FIN 報文,客戶端在 TIME_WAIT 狀態下可以處理這種情況,保證連接的可靠關閉。
?
2:Dubbox 和Dubbo的區別?
Dubbox 和Dubbo本質上沒有區別,只是擴展了Dubbo,以下擴展出來的功能
- 支持REST風格遠程調用(HTTP + JSON/XML);
- 支持基于Kryo和FST的Java高效序列化實現;
- 支持基于Jackson的JSON序列化;
- 支持基于嵌入式Tomcat的HTTP remoting體系;
- 升級Spring至3.x;
- 升級ZooKeeper客戶端;
- 支持完全基于Java代碼的Dubbo配置;
3:Dubbo一般使用什么注冊中心?還有別的選擇嗎?
Dubbo 一般用zookeeper做注冊中心
還可以用:Redis,Nacos,Consul
4:說說Java的異常類?
Throwable -> Error,Exception
Error:嚴重問題,例如內存溢出 OutOfMemoryError
Exception ->運行時異常: RuntimeException,編譯時異常:IOException
5:ArrayList和LinkedList的區別?分別用在什么場景?
都是對List接口的實現,但數據結構上一個是Array(動態數組),一個是Link(雙向鏈表)
ArrayList:查找快,插入刪除慢,適用于頻繁查找和修改的場景
LinkedList:插入刪除快,查找修改慢,適用于頻繁增刪的場景
1. 底層數據結構
ArrayList
:基于動態數組實現。它使用一個數組來存儲元素,當數組空間不足時,會自動進行擴容操作。LinkedList
:基于雙向鏈表實現。每個元素(節點)包含數據、指向前一個節點的引用和指向后一個節點的引用。
2. 隨機訪問性能
ArrayList
:支持快速的隨機訪問,因為它可以通過索引直接訪問數組中的元素,時間復雜度為?O(1)。LinkedList
:隨機訪問性能較差,因為需要從頭節點或尾節點開始遍歷鏈表,直到找到目標元素,時間復雜度為?O(n)。
3. 插入和刪除性能
ArrayList
:在數組末尾插入或刪除元素的效率較高,時間復雜度為?O(1);但在數組中間或開頭插入或刪除元素時,需要移動大量元素,時間復雜度為?O(n)。LinkedList
:在鏈表的任意位置插入或刪除元素的效率較高,因為只需要修改相鄰節點的引用,時間復雜度為?O(1);但在查找要插入或刪除的位置時,仍然需要遍歷鏈表,時間復雜度為?O(n)。
4. 內存占用
ArrayList
:由于使用數組存儲元素,會預先分配一定的內存空間,可能會造成一定的內存浪費;但由于每個元素只存儲數據本身,所以單個元素的內存占用較小。LinkedList
:由于每個節點需要額外存儲指向前一個節點和后一個節點的引用,所以單個節點的內存占用較大;但它不需要預先分配連續的內存空間,內存利用率較高。
ArrayList
?的適用場景
- 隨機訪問頻繁:如果需要頻繁地根據索引訪問元素,而插入和刪除操作較少,那么?
ArrayList
?是更好的選擇。例如,在一個學生成績列表中,需要經常根據學生的序號(索引)查詢成績。 - 數據量相對穩定:如果數據量相對穩定,不會頻繁地進行插入和刪除操作,那么?
ArrayList
?的性能會更好。
LinkedList
?的適用場景
- 插入和刪除頻繁:如果需要頻繁地在列表中間或開頭插入或刪除元素,而隨機訪問操作較少,那么?
LinkedList
?是更好的選擇。例如,在一個任務隊列中,需要經常在隊列頭部添加新任務或在隊列尾部移除已完成的任務。 - 內存使用靈活:如果對內存的使用比較敏感,希望在數據量變化較大時能夠靈活地分配和釋放內存,那么?
LinkedList
?是更好的選擇。
LinkedList
?和?ArrayList
?不同,它并沒有像?ArrayList
?那樣的擴容機制。解釋:
底層結構
LinkedList
?是基于雙向鏈表實現的,雙向鏈表由一系列節點組成,每個節點包含三個部分:存儲的數據、指向前一個節點的引用以及指向后一個節點的引用。
無需擴容的原因
由于鏈表的特性,它不需要預先分配連續的內存空間來存儲元素。當你向?LinkedList
?中添加新元素時,它會創建一個新的節點,并將這個新節點連接到鏈表的合適位置(頭部、尾部或者中間)。而在刪除元素時,會把對應的節點從鏈表中移除,并且釋放該節點占用的內存。
// 在尾部添加元素
linkedList.add("apple");// 在頭部添加元素
linkedList.addFirst("cherry");// 在指定位置添加元素
linkedList.add(1, "date");
題外話:LinkedList 可作為棧(后進先出),可作為隊列(先進先出)
?6:說說List排序?
- 使用 ?Collections.sort()默認正序,可以傳第二個參數自定義排序
- 自定義bean實現 Comparable 接口。
- 實現Comparator接口自定義比較器
numbers、books都是List集合,簡單的示例這里就不過多描述
// 使用 Collections.sort() 方法對列表進行排序 import java.util.Collections;
Collections.sort(numbers);// 使用 Comparator 按價格排序 import java.util.Comparator;
books.sort(Comparator.comparingDouble(Book::getPrice));
7:你都知道哪些常用的Map集合?
HashMap、HashTable、TreeMap、LinkedHashMap
HashMap
- ?鍵和值都允許為 null。
- ?不保證元素的順序,即插入順序和遍歷順序可能不同。
- ?基于哈希表實現,因此查找、插入和刪除操作的時間復雜度通常為 O (1)。
- ?使用場景:適用于不需要保證元素順序,且需要快速查找的場景。
@Test
void testHashMap() {HashMap<Object, Object> map = new HashMap<>();map.put("k1", "v1");map.put("k2", "v2");map.put("k3", "v3");map.put(null, null);System.out.println(map);// {null=null, k1=v1, k2=v2, k3=v3}
}
Hashtable
- 鍵和值都不允許為 null。
- 是線程安全的,因為它的方法都被 synchronized 修飾,所以在多線程環境下可以直接使用,但這也導致了它的性能相對較低。
- 同樣基于哈希表實現,查找、插入和刪除操作的時間復雜度通常為 O (1)。
- 使用場景:在需要線程安全的場景下可以使用,但如果對性能要求較高,建議使用 ConcurrentHashMap 替代。
@Test
void testHashtable() {Hashtable<Object, Object> hashtable = new Hashtable<>();hashtable.put("k3", "v3");hashtable.put("k1", "v1");hashtable.put("k2", "v2");// hashtable.put(null, null); java.lang.NullPointerExceptionSystem.out.println(hashtable);// {k3=v3, k2=v2, k1=v1}
}
TreeMap
- 不允許鍵為 null,但值可以為 null。
- 基于紅黑樹實現,會根據鍵的自然順序或者自定義的比較器進行排序。
- 查找、插入和刪除操作的時間復雜度為 O (log n)。
- 使用場景:適用于需要對鍵進行排序的場景,例如按時間順序排序等。
@Test
void testTreeMap() {TreeMap<Integer, Object> treeMap = new TreeMap<>();treeMap.put(3, "v3");treeMap.put(1, "v1");treeMap.put(2, "v2");//treeMap.put(null, null); java.lang.NullPointerExceptiontreeMap.put(4, null);System.out.println(treeMap);// {1=v1, 2=v2, 3=v3, 4=null}
}
LinkedHashMap
- 鍵和值都允許為 null。
- 繼承自 HashMap,同時維護了一個雙向鏈表,因此可以保證元素的插入順序或者訪問順序。
- 查找、插入和刪除操作的時間復雜度為 O (1)。
- 使用場景:適用于需要保證元素插入順序或者訪問順序的場景,例如實現 LRU(最近最少使用)緩存。
@Test
void testLinkedHashMap() {LinkedHashMap<Integer, Object> linkedHashMap = new LinkedHashMap<>();linkedHashMap.put(3, "v3");linkedHashMap.put(1, "v1");linkedHashMap.put(2, "v2");linkedHashMap.put(4, null);linkedHashMap.put(null, null);System.out.println(linkedHashMap);// {3=v3, 1=v1, 2=v2, 4=null, null=null}
}
8:Collection集合接口和Map接口有什么關系?
沒有直接關系,但是一些子類會有依賴,
Collection是最基本的集合接口,聲明了適用于JAVA集合(只包括Set和List)的通用方法。
Map接口并不是Collection接口的子接口,但是它仍然被看作是Collection框架的一部分。
9:equal?和?hashcode?作用和區別?
首先,我們要明白hashCode()和equals()方法的作用是什么,然后才能說他的區別,說了區別之后在說明使用的時候需要注意的地方,這樣的回答思路基本是OK的。
hashCode()和equals()的作用是什么?
hashCode()和equals()的作用其實是一樣的,目的都是為了比較兩個對象是否相等一樣
hahsCode()和equals()的區別是什么?
從兩個角度介紹他們的區別:一個是性能,一個是可靠性。
1、equals()既然已經實現比較兩個對象的功能了,為什么還需要hashCode()呢?
因為重寫的equals()里一般比較的較為全面和復雜,它會對這個對象內所以成員變量一一進行比較,這樣效率很低,而通過hashCode()對比,則只要生成一個hash值就能比較了,效率很高
2、那hashCode的效率這么高,為啥還要用equals()呢?
因為hashCode()并不是完全可靠,非常有可能的情況是,兩個完全不同的對象的hash值卻是一樣的。
結論:
- equals()相等的兩個對象它們的hashCode()肯定相等,即equals()絕對可靠。
- hahsCode()相同的兩個對象,它們的equals()不一定相同。即用hashCode()比較相同的時候不可靠
- hashCode()不同的兩個對象,它們的那么equals()肯定不同。即用hashCode()比較不同的時候絕對可靠
hashCode()和equals使用的注意事項
1、對于需要大量并且快速的對比的話如果都用equals()去做顯然效率太低。解決方案是:
每當需要比較的時候,先用hahsCode()去比,
如果hashCode()不一樣,則兩個對象肯定不一樣,就沒有必要再用equals()比較了;
如果hashCode()一樣,則這兩個對象有可能相同,這時候再去比較這兩個對象的equals(),如equals()也相同,則表示這兩個真的相同的,這樣既大大提高了效率,又保證了準確性。
2. 事實上,我們平時用的集合框架中的hashMap,hashSet,hashTable 中對key的比較就是使用上述這種方法。
3. Obejct默認的equals和HashCode方法返回的是對象的地址相關信息。
所以當我們通過new關鍵字創建了兩個內容相同的對象,雖然它們的內容相同,但是它們在內存中分配的地址不同,導致它們的hashCode()不同,這肯定不是我們想要的。所以當我們要將某個類應用到集合中去的時候,就必須重寫equals()方法和hashCode()方法。
擴展
1、阿里巴巴開發規約明確規定:?
只要重寫了equals()方法,就必須重寫hashCode()方法。
因為Set存儲的是不重復的對象,依據hashCode和equals進行判斷,所以Set存儲的對象必須重寫這兩個方法。
如果對象定義為Map的健,那么就必須重寫equals()方法和hashCode()方法。
String重寫了equals()方法和hashCode()方法,所以我們可以非常愉快的時候String對象作為key。
2、是不是每個對象都要重寫這兩個方法,到底什么時候重寫?
事實上一般情況下,我們并不需要重寫這兩個方法,只有該類被應用到集合框架中去的時候,才應該重寫。
3、我能不能值重寫equals()方法,不重寫hashCode()方法?
如果重寫了equals()方法,比如說基于對象的內容實現的,而保留了hashCode()的實現不改變,那么最終出現的情況很可能是,兩個對象明明是“相等的”,但是hashCode()卻不一樣。
4、為什么需要hashCode
通過hashCode可以提高對比的性能,這一點,在上面的比對過程中有體現