秋招面試題(360)

目錄

1.object有哪些方法

2.hashMap結構,put過程,entry一定是鏈表嗎?

3.concurrentHashMap瞭解嗎?它的分段鎖是怎麼實現的?

4.ArrayList擴容機制?爲什麼ArrayList獲取元素比LinkList快?

5.JVM內存模型

6.SpringMvc執行請求過程

7 四類八種基礎數據類型及其包裝類,裝箱拆箱

8.String類可以繼承嗎,final說說,final修飾StringBuffer還可以append嗎

9.創建線程的那幾種方式?主線程可以捕獲到子線程的異常嗎?Callable接口?Thread和Runnable的區別,哪一種比較安全。?

10 gc方面的,怎麼判斷對象是否回收,怎麼回收,分代回收,比例多少,大對象怎麼分配,怎麼判斷是大對象

11.集合瞭解哪些?Collection 和 Collections區別

12.說說TCP?三次握手 兩次不行?,四次揮手,說說TCP的流量控制、擁塞避免


1.object有哪些方法

答:一用有12個成員方法,分爲6類

(1)clone() 

(2)hashCode()和equale()

(3)toString和getClass()

(4)wait(),wait(long),wait(long,int),notify(),notifyAll() 線程等待和喚醒

(5)finalize() 垃圾回收時調用該方法

(6)object() 構造方法

 

2.hashMap結構,put過程,entry一定是鏈表嗎?

https://www.jianshu.com/p/8324a34577a0

答:

(1)hashMap是散列表,存儲內容是鍵值對映射的方式。jdk1.6:結構採用“拉鍊法”即數組+鏈表的形式。hashMap有多個entry,每個entry本質時一個單項鍊表;

(2)put()過程,首先要根據添加的鍵值對對應的Key的hashCode計算entry位置,如果兩個hashCode值相同,則entry位置相同。再判斷entry中是否存在key,如果存在,則覆蓋,如果不存在,添加到entry鏈頭;

(3)jdk1.8,hashMap採用的是數組+鏈表+紅黑樹(當鏈條長度大於8時,鏈表轉成紅黑樹)。

HashMapjdk1.8的修改

(1)鏈表變成紅黑樹;

(2)計算hashcode的方法改變了(高位取模,hashcode=hashcode^hashcode<<16,爲了能夠更好的解決hash衝突);

(3)擴容機制:不再採用頭插法,因爲這樣可能存在循環死鎖,並且在重新計算hash值的時候也有改進,只計算擴容位是0還是1。

 

3.concurrentHashMap瞭解嗎?它的分段鎖是怎麼實現的?

答:

(1)concurrentHashMap是線程安全的hashMap,與jdk1.8的hashMap結構很相似採用node+鏈表+紅黑樹,但由於它採用分段鎖,所以支持多線程的訪問。有一個最重要的不同點就是ConcurrentHashMap不允許key或value爲null值。底層結構存放的是TreeBin對象,而不是TreeNode對象;

(2)分段鎖:

在jdk1.8以前,concurrentHashMap採用分段鎖+hashEntry來實現的,鎖採用的是可重入鎖ReentrantLock。vaule值是用volatile修飾的,因此獲取到的值都是最新值。計算sazi()的時候通過多次計算(最多三次),如果前兩次計算結果一樣,就是爭取結果,如果不同,再對每個分段進行加鎖,計算第三次。

在jdk1.8 concurrentHashMap棄用了分段類,但保留了分段屬性,採用Node類作爲基本的存儲單元(其實就是將每個entry作爲一個獨立的分段鎖)+CAS+Synchronized來實現的。

4.ArrayList擴容機制?爲什麼ArrayList獲取元素比LinkList快?

答:

(1)https://www.cnblogs.com/SunArmy/p/9844022.html

其實擴容實現步驟就是分兩步(1)擴容---增加長度(把原來數組複製到長度更大的數組中);(2)添加元素到新數組中;

ArrayList每添加元素時,都要調用ensureCapacity()方法來確保足夠容量,當需要擴容時,將新容量設置爲原來的1.5倍,如果設置後的容量仍舊不夠時,則直接將新容量設置爲所需容量大小,然後用Array.copyof()方法將元素複雜到新數組中。發生擴容的條件:當需要的最小容量大於等於原數組容量是,需要擴容。

ps:由於每次添加元素如果容量不夠的話都需要擴容,擴容非常耗時,因此,當事先知道元素數量的情況下,才使用ArrayList,否則建議使用LinkList;

(2)ArrayList的實現是基於動態數組的數據結構,get和set方法可以實現隨機定位;LinkList的實現採用的是雙向鏈表,get需要移動指針,一步一步到查詢節點。因此ArrayList比LinkList獲取元素更快。

 

5.JVM內存模型

答:JVM定義了若干個程序執行期間的數據域。有些數據在JVM啓動時創建,JVM退出時銷燬,有些數據依賴與線程,在線程創建時創建,隨着線程退出而銷燬。

https://www.cnblogs.com/fubaizhaizhuren/p/4976839.html

(1)共享數據區域:

方法區:存儲每個類的信息(類名,方法信息,字段信息)、靜態變量、常量以及編譯器編譯後的代碼;

堆:存儲對象和數組;

(2)線程隔離數據區域:

程序計數器:當前程序所執行的字節碼行號指示器;

jvm棧:存儲局部變量表、操作棧、動態鏈接、方法出口等信息;

本地方法棧:與jvm棧作用相似,是爲本地方法服務的。

 

6.SpringMvc執行請求過程

答:一用11步https://blog.csdn.net/u014191220/article/details/81387596

(1)用戶發送請求到前端控制器(DispatcherServlet)。

(2)前端控制器請求處理器映射器(HandlerMapping)去查找處理器(Handler)。

(3)找到以後處理器映射器(HandlerMappering)向前端控制器返回執行鏈(HandlerExecutionChain)。

(4)前端控制器(DispatcherServlet)調用處理器適配器(HandlerAdapter)去執行處理器(Handler)。

(5)處理器適配器去執行Handler。

(6)處理器執行完給處理器適配器返回ModelAndView。

(7)處理器適配器向前端控制器返回ModelAndView。

(8)前端控制器請求視圖解析器(ViewResolver)去進行視圖解析。

(9)視圖解析器向前端控制器返回View。

(10)前端控制器對視圖進行渲染。

(11)前端控制器向用戶響應結果。

 

7 四類八種基礎數據類型及其包裝類,裝箱拆箱

答:

整數類型 byte,short,int,long;  包裝類型: Byte,Short,Integer,Long
浮點類型 float,double;   Float,Double
字符類型 chart;   Character
布爾類型 boolean   Boolean

裝箱:基本類型轉換爲包裝類型。其中包括自動裝箱和手動裝箱。自動裝箱時編譯器調用valueOf()方法

拆箱:包裝類型轉換爲基本類型。其中包括自動拆箱和手動拆箱。自動拆箱時,編譯器通過調用類似intValue(),doubleValue()這類的方法。

8.String類可以繼承嗎,final說說,final修飾StringBuffer還可以append嗎

答:不可以繼承,String被fianl修飾的類;

爲什麼要用final修飾????

原理:String本質上是一個被final修飾的char數組,如下所示

private final char value[];

value被final修飾,存放在棧中,但value數組的值存放在堆中,數組的值是可以改變的。

這樣做的原因:

主要是爲了”安全性“和”效率“的緣故,因爲:

1、由於String類不能被繼承,所以就不會沒修改,這就避免了因爲繼承引起的安全隱患;

2、String類在程序中出現的頻率比較高,如果爲了避免安全隱患,在它每次出現時都用final來修飾,這無疑會降低程序的執行效率,所以乾脆直接將其設爲final一提高效率;

使用final關鍵字修飾的變量

如果該變量是一個基本類型,一旦初始化後不能再被修改;

如果該變量是一個引用類型,則初始化後,不能再指向其他對象。但該變量的內容是可以被改變的。

由於StringBuffer是引用類型,且append是修改其內容,索引被final修飾後仍可以使用。

9.創建線程的那幾種方式?主線程可以捕獲到子線程的異常嗎?Callable接口?Thread和Runnable的區別,哪一種比較安全。?

(1)繼承Thread類,步驟:

a.定義Thread類的子類,並重寫該類的run()方法,該方法的方法體就是線程需要完成的任務,run()方法也稱爲線程執行體。

b.創建Thread子類的實例,也就是創建了線程對象。

c.啓動線程,即調用線程的start()方法。t.start();

(2)實現Runnable接口,步驟:

a.定義Runnable接口的實現類,一樣要重寫run()方法,這個run()方法和Thread中的run()方法一樣是線程的執行體。

b.創建Runnable實現類的實例,並用這個實例作爲Thread的target來創建Thread對象,這個Thread對象纔是真正的線程對象。(注: 啓動線程是start()方法,run()並不能啓動一個新的線程)

c.第三步依然是通過調用線程對象的start()方法來啓動線程。new Thread(t).start();

(3)使用Callable接口和Future創建線程:和Runnable接口不一樣,Callable接口提供了一個call()方法作爲線程執行體,call()方法比run()方法功能要強大。

call()方法可以有返回值,call()方法可以聲明拋出異常

步驟:

a.創建Callable接口的實現類,並實現call()方法,然後創建該實現類的實例(從java8開始可以直接使用Lambda表達式創建Callable對象)。

b.使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了Callable對象的call()方法的返回值。(FutureTask是一個包裝器,它通過接受Callable對象來創建,它同時實現了Future和Runnable接口。)

c.使用FutureTask對象作爲Thread對象的target創建並啓動線程(因爲FutureTask實現了Runnable接口)

d.調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值

Runnable和Callable的區別是:

(1)Callable規定的方法是call(),Runnable規定的方法是run()。

(2)Callable的任務執行後可返回值,而Runnable的任務是不能返回值的。

(3)call方法可以拋出異常,run方法不可以。

(4)運行Callable任務可以拿到一個Future對象,表示異步計算(異步執行程序)的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。通過Future對象可以瞭解任務執行情況,可取消任務的執行,還可獲取執行結果。

正常情況下,如果不做特殊的處理,在主線程中是不能夠捕獲到子線程中的異常的。如果想要在主線程中捕獲子線程的異常,我們需要使用ExecutorService,同時做一些修改。

(4)線程池

1)線程池的種類:

a.newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。如果這個線程異常結束,會有另一個取代它,保證順序執行。單工作線程最大的特點是可保證順序地執行各個任務,並且在任意給定的時間不會有多個線程是活動的。代碼如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName() + ":" + index);
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

b. newScheduledThreadPool 創建一個定長線程池支持定時及週期性任務執行。延遲執行示例代碼如下:表示延遲3秒執行

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.schedule(new Runnable() {
            public void run() {
                System.out.println("delay 3 seconds");
            }
        }, 3, TimeUnit.SECONDS);
    }
}

c).newFixedThreadPool創建一個指定工作線程數量的線程池,可控制線程最大併發數,超出的線程會在隊列中等待。newFixedThreadPool是一個典型且優秀的線程池,它具有線程池提高程序效率和節省創建線程時所耗的開銷的優點。但是,在線程池空閒時,即線程池中沒有可運行任務時,它不會釋放工作線程,還會佔用一定的系統資源。代碼如下:

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName() + ":" + index);
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

d).newCachedThreadPool 創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程

這種類型的線程池特點是:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

  • 工作線程的創建數量幾乎沒有限制(其實也有限制的,數目爲Interger. MAX_VALUE), 這樣可靈活的往線程池中添加線程。
  • 如果長時間沒有往線程池中提交任務,即如果工作線程空閒了指定的時間(默認爲1分鐘),則該工作線程將自動終止。終止後,如果你又提交了新的任務,則線程池重新創建一個工作線程。
  • 在使用CachedThreadPool時,一定要注意控制任務的數量,否則,由於大量線程同時運行,很有會造成系統癱瘓。

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(index * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + index);
                }
            });
        }
    }
}

線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。

 

2)線程池參數問題:7個參數

java.uitl.concurrent.ThreadPoolExecutor類是線程池中核心的一個類,在ThreadPoolExecutor中提供了四個構造方法。通過源碼可以發現,前面三個的構造器最後都是調用了第四個構造器進行初始化。

  • corePoolSize:核心線程大小。核心線程即爲最小線程數,即使這些線程處於空閒狀態也不會銷燬。除非設置allowCoreThreadTimeout=true(默認false)時,核心線程會超時關閉。
  • maximumPoolSize:最大線程數。
  • keepAliveTime:空閒線程存活時間。當前線程數大於核心線程數時,在制定存活時間後,空閒線程後被銷燬。
  • TimeUnit:空閒線程存活時間單位。
  • workQueue 工作隊列:阻塞隊列。用於存放等待執行的任務。一共有四種。  
  1. ArrayBlockingQueue:基於數組的有界阻塞隊列,按照FIFO順序。可以防止資源耗盡問題。當前線程數如果>corePoolSize,再有新任務進來,向隊列中存放任務,如果任務已滿,如果當前線程數<maximumPoolSize,則創建新的線程。如果當前線程數>maximumPoolSize,採用拒絕策略。
  2. LinkedBlockingQueue:基於鏈表的無界阻塞隊列(其最大容量爲Interger.MAX),FIFO順序。當前線程數如果>corePoolSize,再有新任務進來,會一直向隊列中存放任務,而不創建新的線程。
  3. SynchronousQueue:一個不緩存任務的阻塞隊列,生產者放入一個任務必須等到消費者取出這個任務。就是說有新的任務進來時,不會緩存,而是直接被調度執行該任務,如果沒有可用的線程,則創建新的線程,如果線程數達到maximumPoolSize,則採用拒絕策略。
  4. PriorityBlockingQueue:具有優先級的無界阻塞隊列,優先級通過Comparator參數實現。
  • ThreadFactory:線程工廠。創建線程使用的工程。
  • RejectedExecutionHandler:任務拒絕處理器。一共四種拒絕策略:
  1. CallerRunsPolicy
  2. AbortPolicy
  3. DiscardPolicy
  4. DiscardOldestPolicy

 

3)使用線程池會引發的問題:

Thread和Runnable的區別:最主要的區別就是Runnable可以避免java只能單繼承的限制。

有的博客上面有講到Runnable可以實現資源共享,只創建一個對象,多個線程同時獲取對象資源。而Thread需要創建多個對象,因此資源時不共享的。(這種說法是不正確的,Thread也可以創建一個對象,同時多個線程共享資源

舉例說明:假設電影院僅有10張票,一共有3個買票窗口,這個場景需要使用多線程來實現。

Thread

1) MyThread 類:線程必須用synchronized 實現線程安全。

class MyThread extends Thread {
    private int ticket = 10;
    public void run() {
        for (int i = 0; i < 10; i++) {
            synchronized (this){
            if (this.ticket > 0) {
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+"賣票了一張票,剩餘:" + this.ticket--+"張票");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            }
        }
    }
}

2)錯誤寫法:ThreadTicket 定義三個對象,三個窗口同時售票,這樣總票數就不是10,而是每個窗口都有10張票,一共30張。

public class ThreadTicket {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();
        new Thread(mt1,"售票窗口1").start();
        new Thread(mt2,"售票窗口2").start();
        new Thread(mt3,"售票窗口3").start();
    }
}

錯誤寫法運行結果:

正確寫法:定義一個對象,使用三個線程共享資源

public class ThreadTicket {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt,"售票窗口1").start();
        new Thread(mt,"售票窗口2").start();
        new Thread(mt,"售票窗口3").start();
    }
}

正確運行結果:

Runnable:

public class ThreadTicket {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt,"售票窗口1").start();
        new Thread(mt,"售票窗口2").start();
        new Thread(mt,"售票窗口3").start();
    }
}

class MyThread extends Thread {
    private int ticket = 10;
    public void run() {
        for (int i = 0; i < 10; i++) {
            synchronized (this){
            if (this.ticket > 0) {
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+"賣票了一張票,剩餘:" + this.ticket--+"張票");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            }
        }
    }
}

Thread通常是使用t.start();來啓動線程的,因此創建幾個線程,就需要創建幾個對象,這樣的話就存在資源不共享的情況。但是通過查看API發現:Thread是Runnable的實現類。所以啓動Thread當然也可以使用new Thread(t).start();這樣就實現了Thread的資源共享。

public class Thread extends Object implements Runnable

線程安全問題:多線程都存在線程安全問題。一共有三種方式來解決。

1)synchronized (this){}代碼塊;

2)synchronized 修飾方法;

3)Lock(ReentrantLock)顯示鎖(注意:需要顯示上鎖和釋放鎖)

這三種方式都能解決線程安全問題,但如果Thread採用多線程t.start();啓動線程的話會產生多個對象,資源不共享,還是 存在線程安全問題,因此如果使用Thread,要儘量使用new Thread(t).start();啓動線程

使用線程池要注意的問題:https://yq.aliyun.com/articles/706014

(1)避免死鎖,請儘量使用CAS

(2)使用ThreadLocal要注意

ThreadLocalMap使用ThreadLocal的弱引用作爲key,如果一個ThreadLocal沒有外部強引用來引用它,那麼系統 GC 的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key爲null的Entry,就沒有辦法訪問這些key爲null的Entry的value,如果當前線程再遲遲不結束的話,這些key爲null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成內存泄漏。

10 gc方面的,怎麼判斷對象是否回收,怎麼回收,分代回收,比例多少,大對象怎麼分配,怎麼判斷是大對象

答:(1)引用計數法和可達性分析算法。(2)可達性分析後不可達的對象不是馬上進行回收的,需要進行兩次標記過程。第一次標記:判斷該對象是否不要執行finalize()方法,如果該對象已經執行過finalize()方法或者沒有重寫該方法,則代表不需要執行,這樣直接將該對象進行第二次標記,被回收;如果該對象需要執行finalize()方法,則將該對象放入到F-queue隊列中,jvm自動創建一個低優先級的Finalizer線程執行對象的finalize()方法。再執行finalize()方法時,對象如果能夠重新建立連接,那麼該對象將擺脫這次回收,如果該對象沒有重新建立連接,那麼對該對象進行二次標記。二次標記的對象進行回收。(3)JVM分爲三代,年輕代,老年代和持久代。年輕代和老年代是1:2。(4)大對象直接分配到老年代,大對象是指需要大量連續內存空間的對象。通過-XX:PretrnureSizeThreshold參數設置大對象。大小超過該參數的對象被認爲是“大對象”,直接進入老年代。

11.集合瞭解哪些?Collection 和 Collections區別

答:

Collection :是接口,它的實現類有List和Set;Collections是類是針對集合的幫助類,集合搜索、排序等操作。

ps:Arrays.sortCollections.sort的實現原理:collections.sort方法底層就是調用的Array.sort方法

Arrays.sort()是針對數組的排序。主要分爲對基本類型排序和對引用類型排序兩種;

基本類型:當數組大小小於48時,參數leftmost==true時,採用的是原始的插入排序方法當leftmost==false時,採用的是優化的插入排序方法(一次性插入兩個數值),具體思路:

1.將要插入的數據,第一個值賦值a1,第二個值賦值a2,

2.然後判斷a1與a2的大小,令a1要大於a2。

3.接下來,首先是插入大的數值a1,將a1與k之前的數字一一比較,直到數值小於a1爲止,把a1插入到合適的位置,

4.接下來,插入小的數值a2,將a2與此時k之前的數字一一比較,直到數值小於a2爲止,將a2插入到合適的位置

5.如果最後剩下一個,就把最後一個沒有遍歷到的數據插入到合適位置

當數組大小小於286時,採用的是雙基準快速排序方法具體思路:

1.選擇兩個樞紐元p1,p2,一般選擇起始元素a[left]和末尾元素a[right](有其他選取方式)。

2.假設p1<p2,如果不是就交換。

3.基於這p1,p2將整個數組分成三部分,<p1的,p1<&<p2的,>p2的。

4.遞歸排序這三個部分。

當數組大小大於128時,採用的是合併排序方法,

引用數據類型:存在一個參數LegacyMergeSort.userRequested

LegacyMergeSort.userRequested==true時,當數組大小小於48時,調用的時傳統的插入排序當大於48時,調用改進的合併排序(對於已經排好序的,不再進行比較,而是直接複製過去,提高效率)

LegacyMergeSort.userRequested==false,調用TimSort.sort方法(一種起源於歸併排序和插入排序的混合排序算法,O(n)~O(nlogn)是目前最優的排序算法),具體思路:

1.掃描數組,確定其中的單調上升段和嚴格單調下降段,將嚴格下降段反轉;

2.定義最小基本片段長度,短於此的單調片段通過插入排序集中爲長於此的段;

3.反覆歸併一些相鄰片段,過程中避免歸併長度相差很大的片段,直至整個排序完成,所用分段選擇策略可以保證O(n log n)時間複雜性。

 TimSort 算法爲了減少對升序部分的回溯和對降序部分的性能倒退,將輸入按其升序和降序特點進行了分區。排序的輸入的單位不是一個個單獨的數字,而是一個個的塊-分區。其中每一個分區叫一個run。針對這些 run 序列,每次拿一個 run 出來按規則進行合併。每次合併會將兩個 run合併成一個 run。合併的結果保存到棧中。合併直到消耗掉所有的 run,這時將棧上剩餘的 run合併到只剩一個 run 爲止。這時這個僅剩的 run 便是排好序的結果。
就是找到已經排好序數據的子序列,然後對剩餘部分排序,然後合併起來

12.說說TCP?三次握手 兩次不行?,四次揮手,說說TCP的流量控制、擁塞避免

答:TCP 是面向連接的傳輸層協議。每一條 TCP 連接只能有兩個端點(endpoint),每一條 TCP 連接只能是點對點的(一對一)
TCP 提供可靠交付的服務,TCP 提供全雙工通信,面向字節流。

三次握手,四次揮手

https://blog.csdn.net/m0_37950361/article/details/79713970

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章