1. 程式人生 > 實用技巧 >史上最全!押題率90%的 Android 中高階工程師面試複習大綱及真題答案整理(上篇)

史上最全!押題率90%的 Android 中高階工程師面試複習大綱及真題答案整理(上篇)

緣起

轉眼間2020就接近尾聲了,年後有跳槽想法的小夥伴們心裡應該也有自己的決定了。金三銀四青銅五,總不能到跳槽的黃金期再開始複習吧。沒辦法,都是兄弟,寵著!2020年度Android中高階面試複習大全奉上。

篇幅過長,預計分三篇文章講解,好兄弟們記得點個關注或者點贊Mark插個眼,後續不容錯過哦

廢話就懶得多說了,進入正題。(再插一句:點贊都是好兄弟,白嫖都是好妹妹)

正文

概述

一文搞定 Android 中高階工程師面試必問所有知識點,希望可以通過此文幫助一些想換工作的朋友更好的複習,準備面試。

以下只整理列出面試頻率較高的題

1、Java、計算機網路相關

1.1 容器(HashMap、HashSet、LinkedList、ArrayList、陣列等)

1.1.1說說HashMap的工作原理

簡單來說:HashMap基於hashing原理,我們通過put()和get()方法儲存和獲取物件。當我們將鍵值對傳遞給put()方法時,它呼叫鍵物件的hashCode()方法來計算hashcode,讓後找到bucket位置來儲存Entry物件。當兩個物件的hashcode相同時,它們的bucket位置相同,‘碰撞’會發生。因為HashMap使用連結串列儲存物件,這個Entry會儲存在連結串列中,當獲取物件時,通過鍵物件的equals()方法找到正確的鍵值對,然後返回值物件。

網上文章很多,可以去我的B站看視訊講解:HashMap原理解析

1.1.2、用過 ArrayList 嗎 ?說一下它的底層實現 ?

有使用,它的底層是基於陣列的資料結構, 預設第一次初始化長度為 10 ,由於 add ,put , size 沒有處理執行緒安全,所以它是非執行緒安全的。

要不我手動畫一下它的整體結構吧。如下圖所示。

圖解:

  • Index: ArrayList 的索引下標
  • elementData: ArrayList 的索引下標對應的資料
  • size: ArrayList 的大小

1.1.3、用過 LinkedList 嗎?說一下它的底層實現 ?

有用過,它的底層資料結構是雙向連結串列組成, 我還是畫一下它的結構圖吧。如下所示:

圖解:

  • 連結串列每個節點我們叫做 Node,Node 有 prev 屬性,代表前一個節點的位置,next 屬性,代表後一個節點的位置;
  • first 是雙向連結串列的頭節點,它的前一個節點是 null。
  • last 是雙向連結串列的尾節點,它的後一個節點是 null;
  • 當連結串列中沒有資料時,first 和 last 是同一個節點,前後指向都是 null;

1.2 記憶體

1.2.1 JVM記憶體區域

JVM基本構成

從上圖可知,JVM主要包括四個部分:

1.類載入器(ClassLoader):在JVM啟動時或者在類執行將需要的class載入到JVM中。(下圖表示了從java原始檔到JVM的整個過程,可配合理解。

2.執行引擎:負責執行class檔案中包含的位元組碼指令;

3.記憶體區(也叫執行時資料區):是在JVM執行的時候操作所分配的記憶體區。執行時記憶體區主要可以劃分為5個區域,如圖:

1.2.2 開執行緒影響哪塊記憶體?

每當有執行緒被建立的時候,JVM就需要為其在記憶體中分配虛擬機器棧和本地方法棧來記錄呼叫方法的內容,分配程式計數器記錄指令執行的位置,這樣的記憶體消耗就是建立執行緒的記憶體代價。

1.2.3 JVM的記憶體模型的理解?

Java記憶體模型即Java Memory Model,簡稱JMM。JMM定義了Java 虛擬機器(JVM)在計算機記憶體(RAM)中的工作方式。JVM是整個計算機虛擬模型,所以JMM是隸屬於JVM的。
Java執行緒之間的通訊總是隱式進行,並且採用的是共享記憶體模型。這裡提到的共享記憶體模型指的就是Java記憶體模型(簡稱JMM),JMM決定一個執行緒對共享變數的寫入何時對另一個執行緒可見。從抽象的角度來看,JMM定義了執行緒和主記憶體之間的抽象關係:執行緒之間的共享變數儲存在主記憶體(main memory)中,每個執行緒都有一個私有的本地記憶體(local memory),本地記憶體中儲存了該執行緒以讀/寫共享變數的副本。本地記憶體是JMM的一個抽象概念,並不真實存在。它涵蓋了快取,寫緩衝區,暫存器以及其他的硬體和編譯器優化。
總之,JMM就是一組規則,這組規則意在解決在併發程式設計可能出現的執行緒安全問題,並提供了內建解決方案(happen-before原則)及其外部可使用的同步手段(synchronized/volatile等),確保了程式執行在多執行緒環境中的應有的原子性,可視性及其有序性。

1.3 垃圾回收演算法(JVM)

1.3.1、描述一下GC的原理和回收策略?

提到垃圾回收,我們可以先思考一下,如果我們去做垃圾回收需要解決哪些問題?

一般說來,我們要解決三個問題:

1、回收哪些記憶體?

2、什麼時候回收?

3、如何回收?

這些問題分別對應著引用管理和回收策略等方案。

提到引用,我們都知道Java中有四種引用型別:

  • 強引用:程式碼中普遍存在的,只要強引用還存在,垃圾收集器就不會回收掉被引用的物件。
  • 軟引用:SoftReference,用來描述還有用但是非必須的物件,當記憶體不足的時候會回收這類物件。
  • 弱引用:WeakReference,用來描述非必須物件,弱引用的物件只能生存到下一次GC發生時,當GC發生時,無論記憶體是否足夠,都會回收該物件。
  • 虛引用:PhantomReference,一個物件是否有虛引用的存在,完全不會對其生存時間產生影響,也無法通過虛引用取得一個物件的引用,它存在的唯一目的是在這個物件被回收時可以收到一個系統通知。

不同的引用型別,在做GC時會區別對待,我們平時生成的Java物件,預設都是強引用,也就是說只要強引用還在,GC就不會回收,那麼如何判斷強引用是否存在呢?

一個簡單的思路就是:引用計數法,有對這個物件的引用就+1,不再引用就-1,但是這種方式看起來簡單美好,但它卻不能解決迴圈引用計數的問題。

因此可達性分析演算法登上歷史舞臺,用它來判斷物件的引用是否存在。

可達性分析演算法通過一系列稱為GCRoots的物件作為起始點,從這些節點從上向下搜尋,所走過的路徑稱為引用鏈,當一個物件沒有任何引用鏈與GCRoots連線時就說明此物件不可用,也就是物件不可達。

GC Roots物件通常包括:

  • 虛擬機器棧中引用的物件(棧幀中的本地變量表)
  • 方法中類的靜態屬性引用的物件
  • 方法區中常量引用的物件
  • Native方法引用的物件

可達性分析演算法整個流程如下所示:

第一次標記:物件在經過可達性分析後發現沒有與GC Roots有引用鏈,則進行第一次標記並進行一次篩選,篩選條件是:該物件是否有必要執行finalize()方法。沒有覆蓋finalize()方法或者finalize()方法已經被執行過都會被認為沒有必要執行。 如果有必要執行:則該物件會被放在一個F-Queue佇列,並稍後在由虛擬機器建立的低優先順序Finalizer執行緒中觸發該物件的finalize()方法,但不保證一定等待它執行結束,因為如果這個物件的finalize()方法發生了死迴圈或者執行時間較長的情況,會阻塞F-Queue佇列裡的其他物件,影響GC。

第二次標記:GC對F-Queue佇列裡的物件進行第二次標記,如果在第二次標記時該物件又成功被引用,則會被移除即將回收的集合,否則會被回收。

總之,JVM在做垃圾回收的時候,會檢查堆中的所有物件否會被這些根集物件引用,不能夠被引用的物件就會被圾收集器回收。一般回收演算法也有如下幾種:

1).標記-清除(Mark-sweep)

標記-清除演算法採用從根集合進行掃描,對存活的物件進行標記,標記完畢後,再掃描整個空間中未被標記的物件,進行回收。標記-清除演算法不需要進行物件的移動,並且僅對不存活的物件進行處理,在存活物件比較多的情況下極為高效,但由於標記-清除演算法直接回收不存活的物件,因此會造成記憶體碎片。

2).標記-整理(Mark-Compact)

標記-整理演算法採用標記-清除演算法一樣的方式進行物件的標記,但在清除時不同,在回收不存活的物件佔用的空間後,會將所有的存活物件往左端空閒空間移動,並更新對應的指標。標記-整理演算法是在標記-清除演算法的基礎上,又進行了物件的移動,因此成本更高,但是卻解決了記憶體碎片的問題。該垃圾回收演算法適用於物件存活率高的場景(老年代)。

3).複製(Copying)

複製演算法將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活著的物件複製到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。這種演算法適用於物件存活率低的場景,比如新生代。這樣使得每次都是對整個半區進行記憶體回收,記憶體分配時也就不用考慮記憶體碎片等複雜情況。

4).分代收集演算法

不同的物件的生命週期(存活情況)是不一樣的,而不同生命週期的物件位於堆中不同的區域,因此對堆記憶體不同區域採用不同的策略進行回收可以提高 JVM 的執行效率。當代商用虛擬機器使用的都是分代收集演算法:新生代物件存活率低,就採用複製演算法;老年代存活率高,就用標記清除演算法或者標記整理演算法。Java堆記憶體一般可以分為新生代、老年代和永久代三個模組:

新生代:

1.所有新生成的物件首先都是放在新生代的。新生代的目標就是儘可能快速的收集掉那些生命週期短的物件。

2.新生代記憶體按照8:1:1的比例分為一個eden區和兩個survivor(survivor0,survivor1)區。大部分物件在Eden區中生成。回收時先將eden區存活物件複製到一個survivor0區,然後清空eden區,當這個survivor0區也存放滿了時,則將eden區和survivor0區存活物件複製到另一個survivor1區,然後清空eden和這個survivor0區,此時survivor0區是空的,然後將survivor0區和survivor1區交換,即保持survivor1區為空, 如此往復。

3.當survivor1區不足以存放 eden和survivor0的存活物件時,就將存活物件直接存放到老年代。若是老年代也滿了就會觸發一次Full GC,也就是新生代、老年代都進行回收。

4.新生代發生的GC也叫做Minor GC,MinorGC發生頻率比較高(不一定等Eden區滿了才觸發)。

老年代:

1.在老年代中經歷了N次垃圾回收後仍然存活的物件,就會被放到老年代中。因此,可以認為老年代中存放的都是一些生命週期較長的物件。

2.記憶體比新生代也大很多(大概比例是1:2),當老年代記憶體滿時觸發Major GC,即Full GC。Full GC發生頻率比較低,老年代物件存活時間比較長。

永久代:

永久代主要存放靜態檔案,如Java類、方法等。永久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者呼叫一些class,例如使用反射、動態代理、CGLib等bytecode框架時,在這種時候需要設定一個比較大的永久代空間來存放這些執行過程中新增的類。

垃圾收集器

垃圾收集演算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實現:

  • Serial收集器(複製演算法): 新生代單執行緒收集器,標記和清理都是單執行緒,優點是簡單高效;

  • Serial Old收集器 (標記-整理演算法): 老年代單執行緒收集器,Serial收集器的老年代版本;

  • ParNew收集器 (複製演算法): 新生代收並行集器,實際上是Serial收集器的多執行緒版本,在多核CPU環境下有著比Serial更好的表現;

  • CMS(Concurrent Mark Sweep)收集器(標記-清除演算法): 老年代並行收集器,以獲取最短回收停頓時間為目標的收集器,具有高併發、低停頓的特點,追求最短GC回收停頓時間。

  • Parallel Old收集器 (標記-整理演算法): 老年代並行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;

  • Parallel Scavenge收集器 (複製演算法): 新生代並行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 使用者執行緒時間/(使用者執行緒時間+GC執行緒時間),高吞吐量可以高效率的利用CPU時間,儘快完成程式的運算任務,適合後臺應用等對互動相應要求不高的場景;

  • G1(Garbage First)收集器 (標記-整理演算法): Java堆並行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基於“標記-整理”演算法實現,也就是說不會產生記憶體碎片。此外,G1收集器不同於之前的收集器的一個重要特點是:G1回收的範圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的範圍僅限於新生代或老年代。

記憶體分配和回收策略

JAVA自動記憶體管理:給物件分配記憶體 以及 回收分配給物件的記憶體。

1、物件優先在Eden分配,當Eden區沒有足夠空間進行分配時,虛擬機器將發起一次MinorGC。

2、大物件直接進入老年代。如很長的字串以及陣列。很長的字串以及陣列。

3、長期存活的物件將進入老年代。當物件在新生代中經歷過一定次數(預設為15)的Minor GC後,就會被晉升到老年代中。

4、動態物件年齡判定。為了更好地適應不同程式的記憶體狀況,虛擬機器並不是永遠地要求物件年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。

1.4 類載入過程(需要多看看,重在理解,對於熱修復和外掛化比較重要)

類的載入器

大家都知道,一個Java程式都是由若干個.class檔案組織而成的一個完整的Java應用程式,當程式在執行時,即會呼叫該程式的一個入口函式來呼叫系統的相關功能,而這些功能都被封裝在不同的class檔案當中,所以經常要從這個class檔案中要呼叫另外一個class檔案中的方法,如果另外一個檔案不存在的話,則會引發系統異常。

而程式在啟動的時候,並不會一次性載入程式所要用到的class檔案,而是根據程式的需要,通過Java的類載入制(ClassLoader)來動態載入某個class檔案到記憶體當的,從而只有class檔案被載入到了記憶體之後,才能被其它class檔案所引用。所以ClassLoader就是用來動態載入class件到記憶體當中用的。

雙親機制

類的載入就是虛擬機器通過一個類的全限定名來獲取描述此類的二進位制位元組流,而完成這個載入動作的就是類載入器。

類和類載入器息息相關,判定兩個類是否相等,只有在這兩個類被同一個類載入器載入的情況下才有意義,否則即便是兩個類來自同一個Class檔案,被不同類載入器載入,它們也是不相等的。

注:這裡的相等性保函Class物件的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果以及Instance關鍵字對物件所屬關係的判定結果等。

類載入器可以分為三類:

  • 啟動類載入器(Bootstrap ClassLoader):負責載入<JAVA_HOME>\lib目錄下或者被-Xbootclasspath引數所指定的路徑的,並且是被虛擬機器所識別的庫到記憶體中。

  • 擴充套件類載入器(Extension ClassLoader):負責載入<JAVA_HOME>\lib\ext目錄下或者被java.ext.dirs系統變數所指定的路徑的所有類庫到記憶體中。

  • 應用類載入器(Application ClassLoader):負責載入使用者類路徑上的指定類庫,如果應用程式中沒有實現自己的類載入器,一般就是這個類載入器去載入應用程式中的類庫。

1、原理介紹

ClassLoader使用的是雙親委託模型來搜尋類的,每個ClassLoader例項都有一個父類載入器的引用(不是繼承的關係,是一個包含的關係),虛擬機器內建的類載入器(Bootstrap ClassLoader)本身沒有父類載入器,但可以用作其它lassLoader例項的的父類載入器。

當一個ClassLoader例項需要載入某個類時,它會在試圖搜尋某個類之前,先把這個任務委託給它的父類載入器,這個過程是由上至下依次檢查的,首先由最頂層的類載入器Bootstrap ClassLoader試圖載入,如果沒載入到,則把任務轉交給Extension ClassLoader試圖載入,如果也沒載入到,則轉交給App ClassLoader 進行載入,如果它也沒有載入得到的話,則返回給委託的發起者,由它到指定的檔案系統或網路等待URL中載入該類。

如果它們都沒有載入到這個類時,則丟擲ClassNotFoundException異常。否則將這個找到的類生成一個類的定義,將它載入到記憶體當中,最後返回這個類在記憶體中的Class例項物件。

類載入機制:

類的載入指的是將類的.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法去內,然後在堆區建立一個java.lang.Class物件,用來封裝在方法區內的資料結構。類的載入最終是在堆區內的Class物件,Class物件封裝了類在方法區內的資料結構,並且向Java程式設計師提供了訪問方法區內的資料結構的介面。

類載入有三種方式:

1)命令列啟動應用時候由JVM初始化載入

2)通過Class.forName()方法動態載入

3)通過ClassLoader.loadClass()方法動態載入

這麼多類載入器,那麼當類在載入的時候會使用哪個載入器呢?

這個時候就要提到類載入器的雙親委派模型,流程圖如下所示:

雙親委派模型的整個工作流程非常的簡單,如下所示:

如果一個類載入器收到了載入類的請求,它不會自己立去載入類,它會先去請求父類載入器,每個層次的類加器都是如此。層層傳遞,直到傳遞到最高層的類載入器只有當 父類載入器反饋自己無法載入這個類,才會有當子類載入器去載入該類。

2、為什麼要使用雙親委託這種模型呢?

因為這樣可以避免重複載入,當父親已經載入了該類的時候,就沒有必要讓子ClassLoader再載入一次。

考慮到安全因素,我們試想一下,如果不使用這種委託模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義的型別,這樣會存在非常大的安全隱患,而雙親委託的方式,就可以避免這種情況,因為String已經在啟動時就被引導類載入器(BootstrcpClassLoader)載入,所以使用者自定義的ClassLoader永遠也無法載入一個自己寫的String,除非你改變JDK中ClassLoader搜尋類的預設演算法。

3、但是JVM在搜尋類的時候,又是如何判定兩個class是相同的呢?

JVM在判定兩個class是否相同時,不僅要判斷兩個類名否相同,而且要判斷是否由同一個類載入器例項載入的。

只有兩者同時滿足的情況下,JVM才認為這兩個class是相同的。就算兩個class是同一份class位元組碼,如果被兩個不同的ClassLoader例項所載入,JVM也會認為它們是兩個不同class。

比如網路上的一個Java類org.classloader.simple.NetClassLoaderSimple,javac編譯之後生成位元組碼檔案NetClasLoaderSimple.class,ClassLoaderA和ClassLoaderB這個類載入器並讀取了NetClassLoaderSimple.class檔案並分別定義出了java.lang.Class例項來表示這個類,對JVM來說,它們是兩個不同的例項物件,但它們確實是一份位元組碼檔案,如果試圖將這個Class例項生成具體的物件進行轉換時,就會拋執行時異常java.lang.ClassCastException,提示這是兩個不同的型別。

1.5 反射

1.5.1、說說你對Java反射的理解?

答:Java 中的反射首先是能夠獲取到Java中要反射類的位元組碼, 獲取位元組碼有三種方法:

1.Class.forName(className)

2.類名.class

3.this.getClass()。

然後將位元組碼中的方法,變數,建構函式等對映成相應的Method、Filed、Constructor等類,這些類提供了豐富的方法可以被我們所使用。

1.6 多執行緒和執行緒池

1.6.1、什麼是執行緒池,如何使用?為什麼要使用執行緒池?

答:執行緒池就是事先將多個執行緒物件放到一個容器中,使用的時候就不用new執行緒而是直接去池中拿執行緒即可,節 省了開闢子執行緒的時間,提高了程式碼執行效率。

1.6.2、Java中的執行緒池共有幾種?

Java有四種執行緒池:

第一種:newCachedThreadPool

不固定執行緒數量,且支援最大為Integer.MAX_VALUE的執行緒數量:

public static ExecutorService newCachedThreadPool() {
    // 這個執行緒池corePoolSize為0,maximumPoolSize為Integer.MAX_VALUE
    // 意思也就是說來一個任務就建立一個woker,回收時間是60s
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                60L, TimeUnit.SECONDS,
                                new SynchronousQueue<Runnable>());
}

可快取執行緒池:

1、執行緒數無限制。 2、有空閒執行緒則複用空閒執行緒,若無空閒執行緒則新建執行緒。 3、一定程式減少頻繁建立/銷燬執行緒,減少系統開銷。

第二種:newFixedThreadPool

一個固定執行緒數量的執行緒池:

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    // corePoolSize跟maximumPoolSize值一樣,同時傳入一個無界阻塞佇列
    // 該執行緒池的執行緒會維持在指定執行緒數,不會進行回收
    return new ThreadPoolExecutor(nThreads, nThreads,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory);
}

定長執行緒池:

1、可控制執行緒最大併發數(同時執行的執行緒數)。 2、超出的執行緒會在佇列中等待。

第三種:newSingleThreadExecutor

可以理解為執行緒數量為1的FixedThreadPool:

public static ExecutorService newSingleThreadExecutor() {
    // 執行緒池中只有一個執行緒進行任務執行,其他的都放入阻塞佇列
    // 外面包裝的FinalizableDelegatedExecutorService類實現了finalize方法,在JVM垃圾回收的時候會關閉執行緒池
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

單執行緒化的執行緒池:

1、有且僅有一個工作執行緒執行任務。 2、所有任務按照指定順序執行,即遵循佇列的入隊出隊規則。

第四種:newScheduledThreadPool。

支援定時以指定週期迴圈執行任務:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

注意:前三種執行緒池是ThreadPoolExecutor不同配置的例項,最後一種是ScheduledThreadPoolExecutor的例項。

1.6.3、執行緒池原理?

從資料結構的角度來看,執行緒池主要使用了阻塞佇列(BlockingQueue)和HashSet集合構成。 從任務提交的流程角度來看,對於使用執行緒池的外部來說,執行緒池的機制是這樣的:

1、如果正在執行的執行緒數 < coreSize,馬上建立核心執行緒執行該task,不排隊等待;
2、如果正在執行的執行緒數 >= coreSize,把該task放入阻塞佇列;
3、如果佇列已滿 && 正在執行的執行緒數 < maximumPoolSize,建立新的非核心執行緒執行該task;
4、如果佇列已滿 && 正在執行的執行緒數 >= maximumPoolSize,執行緒池呼叫handler的reject方法拒絕本次提交。

理解記憶:1-2-3-4對應(核心執行緒->阻塞佇列->非核心執行緒->handler拒絕提交)。

執行緒池的執行緒複用:

這裡就需要深入到原始碼addWorker():它是建立新執行緒的關鍵,也是執行緒複用的關鍵入口。最終會執行到runWoker,它取任務有兩個方式:

  • firstTask:這是指定的第一個runnable可執行任務,它會在Woker這個工作執行緒中執行執行任務run。並且置空表示這個任務已經被執行。
  • getTask():這首先是一個死迴圈過程,工作執行緒迴圈直到能夠取出Runnable物件或超時返回,這裡的取的目標就是任務佇列workQueue,對應剛才入隊的操作,有入有出。

其實就是任務在並不只執行建立時指定的firstTask第一任務,還會從任務佇列的中通過getTask()方法自己主動去取任務執行,而且是有/無時間限定的阻塞等待,保證執行緒的存活。

訊號量

semaphore 可用於程序間同步也可用於同一個程序間的執行緒同步。

可以用來保證兩個或多個關鍵程式碼段不被併發呼叫。在進入一個關鍵程式碼段之前,執行緒必須獲取一個訊號量;一旦該關鍵程式碼段完成了,那麼該執行緒必須釋放訊號量。其它想進入該關鍵程式碼段的執行緒必須等待直到第一個執行緒釋放訊號量。

1.6.4、執行緒池都有哪幾種工作佇列?

1、ArrayBlockingQueue

是一個基於陣列結構的有界阻塞佇列,此佇列按 FIFO(先進先出)原則對元素進行排序。

2、LinkedBlockingQueue

一個基於連結串列結構的阻塞佇列,此佇列按FIFO (先進先出) 排序元素,吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()和Executors.newSingleThreadExecutor使用了這個佇列。

3、SynchronousQueue

一個不儲存元素的阻塞佇列。每個插入操作必須等到另一個執行緒呼叫移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個佇列。

4、PriorityBlockingQueue

一個具有優先順序的無限阻塞佇列。

1.6.5、怎麼理解無界佇列和有界佇列?

有界佇列

1.初始的poolSize < corePoolSize,提交的runnable任務,會直接做為new一個Thread的引數,立馬執行 。 2.當提交的任務數超過了corePoolSize,會將當前的runable提交到一個block queue中。 3.有界佇列滿了之後,如果poolSize < maximumPoolsize時,會嘗試new 一個Thread的進行救急處理,立馬執行對應的runnable任務。 4.如果3中也無法處理了,就會走到第四步執行reject操作。

無界佇列

與有界佇列相比,除非系統資源耗盡,否則無界的任務佇列不存在任務入隊失敗的情況。當有新的任務到來,系統的執行緒數小於corePoolSize時,則新建執行緒執行任務。當達到corePoolSize後,就不會繼續增加,若後續仍有新的任務加入,而沒有空閒的執行緒資源,則任務直接進入佇列等待。若任務建立和處理的速度差異很大,無界佇列會保持快速增長,直到耗盡系統記憶體。 當執行緒池的任務快取佇列已滿並且執行緒池中的執行緒數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略。

6、多執行緒中的安全佇列一般通過什麼實現?

Java提供的執行緒安全的Queue可以分為阻塞佇列和非阻塞佇列,其中阻塞佇列的典型例子是BlockingQueue,非阻塞佇列的典型例子是ConcurrentLinkedQueue.

對於BlockingQueue,想要實現阻塞功能,需要呼叫put(e) take() 方法。而ConcurrentLinkedQueue是基於連結節點的、無界的、執行緒安全的非阻塞佇列。

1.7 HTTP、HTTPS、TCP/IP、Socket通訊、三次握手四次揮手過程

1.7.1、HTTP與HTTPS有什麼區別?

HTTPS是一種通過計算機網路進行安全通訊的傳輸協議。HTTPS經由HTTP進行通訊,但利用SSL/TLS來加密資料包。HTTPS開發的主要目的,是提供對網站伺服器的身份 認證,保護交換資料的隱私與完整性。

HTPPS和HTTP的概念:

HTTPS(全稱:Hypertext Transfer Protocol over Secure Socket Layer),是以安全為目標的HTTP通道,簡單講是HTTP的安全版。即HTTP下加入SSL層,HTTPS的安全基礎是SSL,因此加密的詳細內容就需要SSL。 它是一個URI scheme(抽象識別符號體系),句法類同http:體系。用於安全的HTTP資料傳輸。https:URL表明它使用了HTTP,但HTTPS存在不同於HTTP的預設埠及一個加密/身份驗證層(在HTTP與TCP之間)。這個系統的最初研發由網景公司進行,提供了身份驗證與加密通訊方法,現在它被廣泛用於全球資訊網上安全敏感的通訊,例如交易支付方面。

超文字傳輸協議 (HTTP-Hypertext transfer protocol) 是一種詳細規定了瀏覽器和全球資訊網伺服器之間互相通訊的規則,通過因特網傳送全球資訊網文件的資料傳送協議。

https協議需要到ca申請證書,一般免費證書很少,需要交費。http是超文字傳輸協議,資訊是明文傳輸,https 則是具有安全性的ssl加密傳輸協議http和https使用的是完全不同的連線方式用的埠也不一樣,前者是80,後者是443。http的連線很簡單,是無狀態的HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網路協議 要比http協議安全HTTPS解決的問題:1 . 信任主機的問題. 採用https 的server 必須從CA 申請一個用於證明伺服器用途型別的證書. 改證書只有用於對應的server 的時候,客戶度才信任次主機2 . 防止通訊過程中的資料的洩密和被竄改

如下圖所示,可以很明顯的看出兩個的區別:

注:TLS是SSL的升級替代版,具體發展歷史可以參考傳輸層安全性協議。

HTTP與HTTPS在寫法上的區別也是字首的不同,客戶端處理的方式也不同,具體說來:

如果URL的協議是HTTP,則客戶端會開啟一條到服務端埠80(預設)的連線,並向其傳送老的HTTP請求。 如果URL的協議是HTTPS,則客戶端會開啟一條到服務端埠443(預設)的連線,然後與伺服器握手,以二進位制格式與伺服器交換一些SSL的安全引數,附上加密的 HTTP請求。 所以你可以看到,HTTPS比HTTP多了一層與SSL的連線,這也就是客戶端與服務端SSL握手的過程,整個過程主要完成以下工作:

交換協議版本號 選擇一個兩端都瞭解的密碼 對兩端的身份進行認證 生成臨時的會話金鑰,以便加密通道。 SSL握手是一個相對比較複雜的過程,更多關於SSL握手的過程細節可以參考TLS/SSL握手過程

SSL/TSL的常見開源實現是OpenSSL,OpenSSL是一個開放原始碼的軟體庫包,應用程式可以使用這個包來進行安全通訊,避免竊聽,同時確認另一端連線者的身份。這個包廣泛被應用在網際網路的網頁伺服器上。 更多源於OpenSSL的技術細節可以參考OpenSSL。

1.7.2、談談對http快取的瞭解。

HTTP的快取機制也是依賴於請求和響應header裡的引數類實現的,最終響應式從快取中去,還是從服務端重新拉取,HTTP的快取機制的流程如下所示:

HTTP的快取可以分為兩種:

強制快取:需要服務端參與判斷是否繼續使用快取,當客戶端第一次請求資料是,服務端返回了快取的過期時間(Expires與Cache-Control),沒有過期就可以繼續使用快取,否則則不適用,無需再向服務端詢問。 對比快取:需要服務端參與判斷是否繼續使用快取,當客戶端第一次請求資料時,服務端會將快取標識(Last-Modified/If-Modified-Since與Etag/If-None-Match)與資料一起返回給客戶端,客戶端將兩者都備份到快取中 ,再次請求資料時,客戶端將上次備份的快取 標識傳送給服務端,服務端根據快取標識進行判斷,如果返回304,則表示通知客戶端可以繼續使用快取。 強制快取優先於對比快取。

上面提到強制快取使用的的兩個標識:

Expires:Expires的值為服務端返回的到期時間,即下一次請求時,請求時間小於服務端返回的到期時間,直接使用快取資料。到期時間是服務端生成的,客戶端和服務端的時間可能有誤差。 Cache-Control:Expires有個時間校驗的問題,所有HTTP1.1採用Cache-Control替代Expires。 Cache-Control的取值有以下幾種:

private: 客戶端可以快取。 public: 客戶端和代理伺服器都可快取。 max-age=xxx: 快取的內容將在 xxx 秒後失效 no-cache: 需要使用對比快取來驗證快取資料。 no-store: 所有內容都不會快取,強制快取,對比快取都不會觸發。 我們再來看看對比快取的兩個標識:

Last-Modified/If-Modified-Since

Last-Modified 表示資源上次修改的時間。

當客戶端傳送第一次請求時,服務端返回資源上次修改的時間:

Last-Modified: Tue, 12 Jan 2016 09:31:27 GMT

客戶端再次傳送,會在header裡攜帶If-Modified-Since。將上次服務端返回的資源時間上傳給服務端。

If-Modified-Since: Tue, 12 Jan 2016 09:31:27 GMT 

服務端接收到客戶端發來的資源修改時間,與自己當前的資源修改時間進行對比,如果自己的資源修改時間大於客戶端發來的資源修改時間,則說明資源做過修改, 則返回200表示需要重新請求資源,否則返回304表示資源沒有被修改,可以繼續使用快取。

上面是一種時間戳標記資源是否修改的方法,還有一種資源標識碼ETag的方式來標記是否修改,如果標識碼發生改變,則說明資源已經被修改,ETag優先順序高於Last-Modified。

Etag/If-None-Match

ETag是資原始檔的一種標識碼,當客戶端傳送第一次請求時,服務端會返回當前資源的標識碼:

ETag: "5694c7ef-24dc"

客戶端再次傳送,會在header裡攜帶上次服務端返回的資源標識碼:

If-None-Match:"5694c7ef-24dc" 服務端接收到客戶端發來的資源標識碼,則會與自己當前的資源嗎進行比較,如果不同,則說明資源已經被修改,則返回200,如果相同則說明資源沒有被修改,返回 304,客戶端可以繼續使用快取。

1.7.3、Https加密原理。

加密演算法的型別基本上分為了兩種:

  • 對稱加密,加密用的金鑰和解密用的金鑰是同一個,比較有代表性的就是 AES 加密演算法;
  • 非對稱加密,加密用的金鑰稱為公鑰,解密用的金鑰稱為私鑰,經常使用到的 RSA 加密演算法就是非對稱加密的;

此外,還有Hash加密演算法

HASH演算法:MD5, SHA1, SHA256

相比較對稱加密而言,非對稱加密安全性更高,但是加解密耗費的時間更長,速度慢。

想了解更多加密演算法請點選這裡

HTTPS = HTTP + SSL,HTTPS 的加密就是在 SSL 中完成的。

這就要從 CA 證書講起了。CA 證書其實就是數字證書,是由 CA 機構頒發的。至於 CA 機構的權威性,那麼是毋庸置疑的,所有人都是信任它的。CA 證書內一般會包含以下內容:

  • 證書的頒發機構、版本
  • 證書的使用者
  • 證書的公鑰
  • 證書的有效時間
  • 證書的數字簽名 Hash 值和簽名 Hash 演算法
  • ...

客戶端如何校驗 CA 證書?

CA 證書中的 Hash 值,其實是用證書的私鑰進行加密後的值(證書的私鑰不在 CA 證書中)。然後客戶端得到證書後,利用證書中的公鑰去解密該 Hash 值,得到 Hash-a ;然後再利用證書內的簽名 Hash 演算法去生成一個 Hash-b 。最後比較 Hash-a 和 Hash-b 這兩個的值。如果相等,那麼證明了該證書是對的,服務端是可以被信任的;如果不相等,那麼就說明該證書是錯誤的,可能被篡改了,瀏覽器會給出相關提示,無法建立起 HTTPS 連線。除此之外,還會校驗 CA 證書的有效時間和域名匹配等。

HTTPS 中的 SSL 握手建立過程

假設現在有客戶端 A 和伺服器 B :

  • 1、首先,客戶端 A 訪問伺服器 B ,比如我們用瀏覽器開啟一個網頁 www.baidu.com ,這時,瀏覽器就是客戶端 A ,百度的伺服器就是伺服器 B 了。這時候客戶端 A 會生成一個隨機數1,把隨機數1 、自己支援的 SSL 版本號以及加密演算法等這些資訊告訴伺服器 B 。
  • 2、伺服器 B 知道這些資訊後,然後確認一下雙方的加密演算法,然後服務端也生成一個隨機數 B ,並將隨機數 B 和 CA 頒發給自己的證書一同返回給客戶端 A 。
  • 3、客戶端 A 得到 CA 證書後,會去校驗該 CA 證書的有效性,校驗方法在上面已經說過了。校驗通過後,客戶端生成一個隨機數3 ,然後用證書中的公鑰加密隨機數3 並傳輸給服務端 B 。
  • 4、服務端 B 得到加密後的隨機數3,然後利用私鑰進行解密,得到真正的隨機數3。
  • 5、最後,客戶端 A 和服務端 B 都有隨機數1、隨機數2、隨機數3,然後雙方利用這三個隨機數生成一個對話金鑰。之後傳輸內容就是利用對話金鑰來進行加解密了。這時就是利用了對稱加密,一般用的都是 AES 演算法。
  • 6、客戶端 A 通知服務端 B ,指明後面的通訊用對話金鑰來完成,同時通知伺服器 B 客戶端 A 的握手過程結束。
  • 7、服務端 B 通知客戶端 A,指明後面的通訊用對話金鑰來完成,同時通知客戶端 A 伺服器 B 的握手過程結束。
  • 8、SSL 的握手部分結束,SSL 安全通道的資料通訊開始,客戶端 A 和伺服器 B 開始使用相同的對話金鑰進行資料通訊。

簡化如下:

  • 1、客戶端和服務端建立 SSL 握手,客戶端通過 CA 證書來確認服務端的身份;
  • 2、互相傳遞三個隨機數,之後通過這隨機數來生成一個金鑰;
  • 3、互相確認金鑰,然後握手結束;
  • 4、資料通訊開始,都使用同一個對話金鑰來加解密;

可以發現,在 HTTPS 加密原理的過程中把對稱加密和非對稱加密都利用了起來。即利用了非對稱加密安全性高的特點,又利用了對稱加密速度快,效率高的好處。

需要更深的理解請點選這裡

1.7.4、HTTPS 如何防範中間人攻擊?

什麼是中間人攻擊?

當資料傳輸發生在一個裝置(PC/手機)和網路伺服器之間時,攻擊者使用其技能和工具將自己置於兩個端點之間並截獲資料;儘管交談的兩方認為他們是在與對方交談,但是實際上他們是在與幹壞事的人交流,這便是中間人攻擊。

有幾種攻擊方式?

  • 1、嗅探:

嗅探或資料包嗅探是一種用於捕獲流進和流出系統/網路的資料包的技術。網路中的資料包嗅探就好像電話中的監聽。

  • 2、資料包注入:

在這種技術中,攻擊者會將惡意資料包注入常規資料中。這樣使用者便不會注意到檔案/惡意軟體,因為它們是合法通訊流的一部分。

  • 3、會話劫持:

在你登入進你的銀行賬戶和退出登入這一段期間便稱為一個會話。這些會話通常都是黑客的攻擊目標,因為它們包含潛在的重要資訊。在大多數案例中,黑客會潛伏在會話中,並最終控制它。

  • 4、SSL剝離:

在SSL剝離攻擊中,攻擊者使SSL/TLS連線剝落,隨之協議便從安全的HTTPS變成了不安全的HTTP。

1.7.5、為什麼tcp要經過三次握手,四次揮手?

重要標誌位

ACK : TCP協議規定,只有ACK=1時有效,也規定連線建立後所有傳送的報文的ACK必須為1

SYN(SYNchronization) : 在連線建立時用來同步序號。當SYN=1而ACK=0時,表明這是一個連線請求報文。對方若同意建立連線,則應在響應報文中使SYN=1和ACK=1. 因此, SYN置1就表示這是一個連線請求或連線接受報文。

FIN (finis)即完,終結的意思, 用來釋放一個連線。當 FIN = 1 時,表明此報文段的傳送方的資料已經發送完畢,並要求釋放連線。

三次握手、四次揮手過程

三次握手:

第一次握手:建立連線。客戶端傳送連線請求報文段,將SYN位置為1,Sequence Number為x;然後,客戶端進入SYN_SEND狀態,等待伺服器的確認;

第二次握手:伺服器收到SYN報文段。伺服器收到客戶端的SYN報文段,需要對這個SYN報文段進行確認,設定Acknowledgment Number為x+1(Sequence Number+1);同時,自己還要傳送SYN請求資訊,將SYN位置為1,Sequence Number為y;伺服器端將上述所有資訊放到一個報文段(即SYN+ACK報文段)中,一併傳送給客戶端,此時伺服器進入SYN_RECV狀態;

第三次握手:客戶端收到伺服器的SYN+ACK報文段。然後將Acknowledgment Number設定為y+1,向伺服器傳送ACK報文段,這個報文段傳送完畢以後,客戶端和伺服器端都進入ESTABLISHED狀態,完成TCP三次握手。

四次揮手:

第一次分手:主機1(可以使客戶端,也可以是伺服器端),設定Sequence Number和Acknowledgment Number,向主機2傳送一個FIN報文段;此時,主機1進入FIN_WAIT_1狀態;這表示主機1沒有資料要傳送給主機2了;

第二次分手:主機2收到了主機1傳送的FIN報文段,向主機1回一個ACK報文段,Acknowledgment Number為Sequence Number加1;主機1進入FIN_WAIT_2狀態;主機2告訴主機1,我“同意”你的關閉請求;

第三次分手:主機2向主機1傳送FIN報文段,請求關閉連線,同時主機2進入LAST_ACK狀態;

第四次分手:主機1收到主機2傳送的FIN報文段,向主機2傳送ACK報文段,然後主機1進入TIME_WAIT狀態;主機2收到主機1的ACK報文段以後,就關閉連線;此時,主機1等待2MSL後依然沒有收到回覆,則證明Server端已正常關閉,那好,主機1也可以關閉連線了。

“三次握手”的目的是“為了防止已失效的連線請求報文段突然又傳送到了服務端,因而產生錯誤”。主要目的防止server端一直等待,浪費資源。換句話說,即是為了保證服務端能收接受到客戶端的資訊並能做出正確的應答而進行前兩次(第一次和第二次)握手,為了保證客戶端能夠接收到服務端的資訊並能做出正確的應答而進行後兩次(第二次和第三次)握手。

“四次揮手”原因是因為tcp是全雙工模式,接收到FIN時意味將沒有資料再發來,但是還是可以繼續傳送資料。

1.7.6、TCP可靠傳輸原理實現(滑動視窗)。

確認和重傳:接收方收到報文後就會進行確認,傳送方一段時間沒有收到確認就會重傳。

資料校驗。

資料合理分片與排序,TCP會對資料進行分片,接收方會快取為按序到達的資料,重新排序後再提交給應用層。

流程控制:當接收方來不及接收發送的資料時,則會提示傳送方降低傳送的速度,防止包丟失。

擁塞控制:當網路發生擁塞時,減少資料的傳送。

關於滑動視窗、流量控制、擁塞控制實現原理請點選這裡

1.7.7、Tcp和Udp的區別?

1、基於連線與無連線;

2、對系統資源的要求(TCP較多,UDP少);

3、UDP程式結構較簡單;

4、流模式與資料報模式 ;

5、TCP保證資料正確性,UDP可能丟包;

6、TCP保證資料順序,UDP不保證。

1.7.8、如何設計在 UDP 上層保證 UDP 的可靠性傳輸?

傳輸層無法保證資料的可靠傳輸,只能通過應用層來實現了。實現的方式可以參照tcp可靠性傳輸的方式。如不考慮擁塞處理,可靠UDP的簡單設計如下:

  • 1、新增seq/ack機制,確保資料傳送到對端
  • 2、添加發送和接收緩衝區,主要是使用者超時重傳。
  • 3、新增超時重傳機制。

具體過程即是:送端傳送資料時,生成一個隨機seq=x,然後每一片按照資料大小分配seq。資料到達接收端後接收端放入快取,併發送一個ack=x的包,表示對方已經收到了資料。傳送端收到了ack包後,刪除緩衝區對應的資料。時間到後,定時任務檢查是否需要重傳資料。

目前有如下開源程式利用udp實現了可靠的資料傳輸。分別為RUDP、RTP、UDT:

1、RUDP(Reliable User Datagram Protocol)

RUDP 提供一組資料服務質量增強機制,如擁塞控制的改進、重發機制及淡化伺服器演算法等。

2、RTP(Real Time Protocol)

RTP為資料提供了具有實時特徵的端對端傳送服務,如在組播或單播網路服務下的互動式視訊音訊或模擬資料。

3、UDT(UDP-based Data Transfer Protocol)

UDT的主要目的是支援高速廣域網上的海量資料傳輸。

1.7.9、socket斷線重連怎麼實現,心跳機制又是怎樣實現?

socket概念

套接字(socket)是通訊的基石,是支援TCP/IP協議的網路通訊的基本操作單元。它是網路通訊過程中端點的抽象表示,包含進行網路通訊必須的五種資訊:連線使用的協議,本地主機的IP地址,本地程序的協議埠,遠地主機的IP地址,遠地程序的協議埠。

為了區別不同的應用程式程序和連線,許多計算機作業系統為應用程式與TCP/IP協議互動提供了套接字(Socket)介面。應 用層可以和傳輸層通過Socket介面,區分來自不同應用程式程序或網路連線的通訊,實現資料傳輸的併發服務。

建立socket連線

建立Socket連線至少需要一對套接字,其中一個運行於客戶端,稱為ClientSocket ,另一個運行於伺服器端,稱為ServerSocket 。

套接字之間的連線過程分為三個步驟:伺服器監聽,客戶端請求,連線確認。

  • 伺服器監聽:伺服器端套接字並不定位具體的客戶端套接字,而是處於等待連線的狀態,實時監控網路狀態,等待客戶端的連線請求。
  • 客戶端請求:指客戶端的套接字提出連線請求,要連線的目標是伺服器端的套接字。為此,客戶端的套接字必須首先描述它要連線的伺服器的套接字,指出伺服器端- - 套接字的地址和埠號,然後就向伺服器端套接字提出連線請求。

連線確認:當伺服器端套接字監聽到或者說接收到客戶端套接字的連線請求時,就響應客戶端套接字的請求,建立一個新的執行緒,把伺服器端套接字的描述發 給客戶端,一旦客戶端確認了此描述,雙方就正式建立連線。而伺服器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連線請求。

SOCKET連線與TCP連線

建立Socket連線時,可以指定使用的傳輸層協議,Socket可以支援不同的傳輸層協議(TCP或UDP),當使用TCP協議進行連線時,該Socket連線就是一個TCP連線。

Socket連線與HTTP連線

由於通常情況下Socket連線就是TCP連線,因此Socket連線一旦建立,通訊雙方即可開始相互發送資料內容,直到雙方連線斷開。但在實際網 絡應用中,客戶端到伺服器之間的通訊往往需要穿越多箇中間節點,例如路由器、閘道器、防火牆等,大部分防火牆預設會關閉長時間處於非活躍狀態的連線而導致 Socket 連線斷連,因此需要通過輪詢告訴網路,該連線處於活躍狀態。

而HTTP連線使用的是“請求—響應”的方式,不僅在請求時需要先建立連線,而且需要客戶端向伺服器發出請求後,伺服器端才能回覆資料。

很多情況下,需要伺服器端主動向客戶端推送資料,保持客戶端與伺服器資料的實時與同步。此時若雙方建立的是Socket連線,伺服器就可以直接將數 據傳送給客戶端;若雙方建立的是HTTP連線,則伺服器需要等到客戶端傳送一次請求後才能將資料傳回給客戶端,因此,客戶端定時向伺服器端傳送連線請求, 不僅可以保持線上,同時也是在“詢問”伺服器是否有新的資料,如果有就將資料傳給客戶端。TCP(Transmission Control Protocol) 傳輸控制協議

socket斷線重連實現

正常連線斷開客戶端會給服務端傳送一個fin包,服務端收到fin包後才會知道連線斷開。 而斷網斷電時客戶端無法傳送fin包給服務端,所以服務端沒辦法檢測到客戶端已經短線。 為了緩解這個問題,服務端需要有個心跳邏輯,就是服務端檢測到某個客戶端多久沒傳送任何資料過來就認為客戶端已經斷開, 這需要客戶端定時向服務端傳送心跳資料維持連線。

心跳機制實現

長連線的實現:心跳機制,應用層協議大多都有HeartBeat機制,通常是客戶端每隔一小段時間向伺服器傳送一個數據包,通知伺服器自己仍然線上。並傳輸一些可能必要的資料。使用心跳包的典型協議是IM,比如QQ/MSN/飛信等協議

1、在TCP的機制裡面,本身是存在有心跳包的機制的,也就是TCP的選項:SO_KEEPALIVE。 系統預設是設定的2小時的心跳頻率。但是它檢查不到機器斷電、網線拔出、防火牆這些斷線。 而且邏輯層處理斷線可能也不是那麼好處理。一般,如果只是用於保活還是可以的。通過使用TCP的KeepAlive機制(修改那個time引數),可以讓連線每隔一小段時間就產生一些ack包,以降低被踢掉的風險,當然,這樣的代價是額外的網路和CPU負擔。

2、應用層心跳機制實現。

Cookie與Session的作用和原理。

  • Session是在服務端儲存的一個數據結構,用來跟蹤使用者的狀態,這個資料可以儲存在叢集、資料庫、檔案中。
  • Cookie是客戶端儲存使用者資訊的一種機制,用來記錄使用者的一些資訊,也是實現Session的一種方式。
Session:

由於HTTP協議是無狀態的協議,所以服務端需要記錄使用者的狀態時,就需要用某種機制來識具體的使用者,這個機制就是Session.典型的場景比如購物車,當你點選下單按鈕時,由於HTTP協議無狀態,所以並不知道是哪個使用者操作的,所以服務端要為特定的使用者建立了特定的Session,用用於標識這個使用者,並且跟蹤使用者,這樣才知道購物車裡面有幾本書。這個Session是儲存在服務端的,有一個唯一標識。在服務端儲存Session的方法很多,記憶體、資料庫、檔案都有。叢集的時候也要考慮Session的轉移,在大型的網站,一般會有專門的Session伺服器叢集,用來儲存使用者會話,這個時候 Session 資訊都是放在記憶體的。

具體到Web中的Session指的就是使用者在瀏覽某個網站時,從進入網站到瀏覽器關閉所經過的這段時間,也就是使用者瀏覽這個網站所花費的時間。因此從上述的定義中我們可以看到,Session實際上是一個特定的時間概念。

當客戶端訪問伺服器時,伺服器根據需求設定Session,將會話資訊儲存在伺服器上,同時將標示Session的SessionId傳遞給客戶端瀏覽器,

瀏覽器將這個SessionId儲存在記憶體中,我們稱之為無過期時間的Cookie。瀏覽器關閉後,這個Cookie就會被清掉,它不會存在於使用者的Cookie臨時檔案。

以後瀏覽器每次請求都會額外加上這個引數值,伺服器會根據這個SessionId,就能取得客戶端的資料資訊。

如果客戶端瀏覽器意外關閉,伺服器儲存的Session資料不是立即釋放,此時資料還會存在,只要我們知道那個SessionId,就可以繼續通過請求獲得此Session的資訊,因為此時後臺的Session還存在,當然我們可以設定一個Session超時時間,一旦超過規定時間沒有客戶端請求時,伺服器就會清除對應SessionId的Session資訊。

Cookie是由伺服器端生成,傳送給User-Agent(一般是web瀏覽器),瀏覽器會將Cookie的key/value儲存到某個目錄下的文字檔案內,下次請求同一網站時就傳送該Cookie給伺服器(前提是瀏覽器設定為啟用Cookie)。Cookie名稱和值可以由伺服器端開發自己定義,對於JSP而言也可以直接寫入Sessionid,這樣伺服器可以知道該使用者是否合法使用者以及是否需要重新登入等。

面試資料PDF電子書

資料已經上傳在我的GitHub無償分享下載!

Java、計算機網路部分電子書(242頁)

Android部分電子書(1294頁)

資料已經上傳在我的GitHub免費下載!誠意滿滿!!!

聽說一鍵三連的粉絲都面試成功了?如果本篇部落格對你有幫助,請支援下小編哦

Android高階面試精選題、架構師進階實戰文件傳送門:我的GitHub

整理不易,覺得有幫助的朋友可以幫忙點贊分享支援一下小編~

你的支援,我的動力;祝各位前程似錦,offer不斷!!!

參考

https://juejin.cn/post/6844904079169159175

https://juejin.cn/post/6905226221357891592

https://juejin.cn/post/6888222422760488974

https://juejin.cn/post/6844904155153170439

https://juejin.cn/post/6844904087566155784