update time 2021年7月14日11點57分 ,閱讀時間30分鐘, 文章版本:V 1.5。主要收集在面試過程中普遍問到的基礎知識(面試收集 主要來自于bilibili 嵩恒 螞蟻金服等互聯網公司)
由于總結的東西很多很亂,所以知識點并沒有深入探討,很多小標題的東西都可以寫成一篇單獨的總結,這里偷懶直接放在一起匯總了。
網絡
網絡模塊 面試主要涉及到 應用層(HTTP DNS等) 和 傳輸層 (TCP UDP)的東西,
TCP UDP
TCP 連接 :傳輸可靠;有序;面向字節流;速度慢;較重量;全雙工; 適用于文件傳輸、瀏覽器等
全雙工:A 給 B 發消息的同時,B 也能給 A 發
半雙工:A 給 B 發消息的同時,B 不能給 A 發
UDP 無連接 :傳輸不可靠;無序;面向報文;速度快;輕量; 適用于即時通訊、視頻通話等
三次握手四次揮手
- 握手:
- 建立連接,客戶端發送syn包(syn=x)到服務器,客戶端進入SYN_SENT狀態,等待服務器確認;
- 服務器收到syn包,首先確認客戶的SYN(ack=x+1),自己向客戶端發送一個SYN包(syn=y),此時服務器進入SYN_RECV狀態;
- 客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),發送完畢后,客戶端和服務器雙方進入ESTABLISHED(TCP連接成功)狀態,握手完成!
SYN:同步序列編號(Synchronize Sequence Numbers)。
在這里插入圖片描述
- 揮手:
- 客戶端進程發出連接釋放報文,并且停止發送數據。釋放數據報文首部,FIN=1,其序列號為seq=u(等于前面已經傳送過來的數據的最后一個字節的序號加1),此時,客戶端進入FIN-WAIT-1(終止等待1)狀態。 TCP規定,FIN報文段即使不攜帶數據,也要消耗一個序號。
- 服務器收到連接釋放報文,發出確認報文,ACK=1,ack=u+1,并且帶上自己的序列號seq=v,此時,服務端就進入了CLOSE-WAIT(關閉等待)狀態。TCP服務器通知高層的應用進程,客戶端向服務器的方向就釋放了,這時候處于半關閉狀態,即客戶端已經沒有數據要發送了,但是服務器若發送數據,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。
- 客戶端收到服務器的確認請求后,客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送連接釋放報文(在這之前還需要接受服務器發送的最后的數據)。
- 服務器將最后的數據發送完畢后,就向客戶端發送連接釋放報文,FIN=1,ack=u+1,由于在半關閉狀態,服務器很可能又發送了一些數據,假定此時的序列號為seq=w,此時,服務器就進入了LAST-ACK(最后確認)狀態,等待客戶端的確認。
- 客戶端收到服務器的連接釋放報文后,必須發出確認,ACK=1,ack=w+1,而自己的序列號是seq=u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。注意此時TCP連接還沒有釋放,必須經過2??MSL(最長報文段壽命)的時間后,當客戶端撤銷相應的TCB后,才進入CLOSED狀態。
- 服務器只要收到了客戶端發出的確認,立即進入CLOSED狀態。同樣,撤銷TCB后,就結束了這次的TCP連接。所以服務器結束TCP連接的時間要比客戶端早一些。
Http 緩存
- Cache-Control : 在 HTTP 響應頭中,用于指示代理和 UA 使用何種緩存策略(no-cache 為本次響應不可直接用于后續請求 、no-store 為禁止緩存、private為僅 UA 可緩存、public都可以緩存)
- Etag : Etag 響應頭字段表示資源的版本,瀏覽器在發送請求時會帶 If-None-Match 頭字段, 來詢問服務器該版本是否仍然可用
- Last-Modified : 與 Etag 類似,Last-Modified HTTP 響應頭也用來標識資源的有效性
HTTP HTTPS 區別
HTTP 是超文本傳輸協議,明文傳輸;HTTPS 使用 SSL 協議對 HTTP 傳輸數據進行了加密
HTTP 默認 80 端口;HTTPS 默認 443 端口
優點:安全
缺點:費時、SSL 證書收費,加密能力還是有限的,但是比 HTTP 更加安全,也是大勢所趨。
Get 參數放在 url 中;Post 參數放在 request Body 中
Get 可能不安全,因為參數放在 url 中,并且對于傳送的數據長度有限制。
HTTPS
HTTPS 在內容傳輸的加密上使用的是對稱加密,非對稱加密只作用在證書驗證階段。
原因:非對稱加密的加解密效率是非常低的,而 http 的應用場景中通常端與端之間存在大量的交互,非對稱加密的效率是無法接受的。在 HTTPS 的場景中只有服務端保存了私鑰,一對公私鑰只能實現單向的加解密,所以HTTPS 中內容傳輸加密采取的是對稱加密,而不是非對稱加密
Java基礎
內存模型
- 棧:儲存局部變量 (線程私有 使用完畢就會釋放)
- 堆:儲存 new 出來的東西、static類型變量 成員方法等 (線程共享 使用完畢 等待gc清理)
- 方法區: 對象的運行過程 (線程共享)
- 本地方法區:為系統方法使用 (線程私有)
- 寄存器:為CPU提供
- 程序計數器:指向當前線程正在執行的指令的地址,確保多線程下正常運行(線程私有)
類加載過程
- 加載:獲取類的二進制字節流;生成方法區的運行時存儲結構;在內存中生成 Class 對象
- 驗證:確保該 Class 字節流符合虛擬機要求
- 準備:初始化靜態變量(基本類型 、引用類型初始值null、final static值)
- 解析:將常量池的符號引用替換為直接引用 (類方法、變量直接指向內存引用地址或偏移量)
- 初始化:執行靜態塊代碼、類變量賦值 (new 類型)
- 使用
- 卸載
線程/進程
進程:進程是系統進行資源分配和調度的一個獨立單位 (擁有獨立內存空間),一個app就是一個進程,進程包含線程。
線程:是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統資源,只擁有一些在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。
sleep wait 等 區別
wait(): 當一個線程執行到wait()方法時,它就進入到一個等待池中,同時釋放對象鎖,使得其他線程可以訪問。用戶可以使用notify,notifyAll或者指定睡眠時間來喚醒當前等待池中的線程。
sleep():該函數是Thread的靜態函數,作用是使調用線程進入阻塞狀態(blocke)。因為sleep()是Thread類的Static方法,因為它不能改變對象的機制。所以,調用sleep方法時,線程雖然休眠了,但是對象的機制并沒有被釋放,其他線程無法訪問這個對象
注意:wait方法,notify方法,notifyAll方法必須放在synchronized block中,否則會拋出異常。
join():等待目標線程執行完成之后再繼續執行
yield():線程禮讓,線程進入就緒狀態(ready)。目標線程由運行狀態轉換為就緒狀態,也就是讓出執行權限,讓其他線程得以優先執行,但其他線程能否優先執行是未知的。
多線程
ThreadLocal
ThreadLocal提供了線程本地變量存儲集合 Map 內部Entry使用弱引用持有。它可以保證訪問到的變量屬于當前線程,每個線程都保存有一個變量副本,每個線程的變量都不同。ThreadLocal相當于提供了一種線程隔離,將變量與線程相綁定。
ThreadLocal 參考文章
多線程安全
線程池
線程池拒絕策略
synchronized 原理
Synchronized可以把任何一個非null對象作為"鎖",其中都有一個監視器鎖:monitor。Synchronized的語義底層是通過一個monitor的對象來完成。
- 同步代碼塊(synchronize(this)的方式)會執行 monitorenter 開始,motnitorexit 結束,當線程進入monitor,如果為0 則改為1 并分配使用,如果為1 則需要掛起等待。其實wait/notify等方法也依賴于monitor對象,這就是為什么只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。
- 同步代碼塊采用( synchronized void method()的方式)調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標志是否被設置,如果設置了,執行線程將先獲取monitor,獲取成功之后才能執行方法體,方法執行完后再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。
兩種同步方式本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需通過字節碼來完成。兩個指令的執行是JVM通過調用操作系統的互斥原語mutex來實現,被阻塞的線程會被掛起、等待重新調度,會導致“用戶態和內核態”兩個態之間來回切換,對性能有較大影響。
synchronized 在JDK 1.6以后的優化
自適應自旋鎖 :指一個線程嘗試獲取某個鎖時,如果該鎖已被其他線程占用,就一直循環檢測鎖是否被釋放(自旋的次數不再是固定的,它是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定),而不是進入線程掛起或睡眠狀態(因為為了很短的等待時間就去掛起喚醒會 更低效)。
鎖消除:但是在有些情況下,JVM檢測到不存在共享數據競爭,JVM會對這些同步鎖進行鎖消除。
鎖粗化 :就是將多個連續的加鎖、解鎖操作連接在一起,擴展成一個范圍更大的鎖。
偏向鎖、輕量級鎖:輕量級鎖是為了在線程交替執行同步塊時提高性能,而偏向鎖則是在只有一個線程執行同步塊時通過標識,避免再走各種加鎖/解鎖流程,達到進一步提高性能。
synchronized 添加在非靜態方法上為 對象鎖,如果在靜態方法上為類鎖。
鎖問題
可重入鎖 :已經獲取到鎖后,再次調用同步代碼塊/嘗試獲取鎖時不必重新去申請鎖,可以直接執行相關代碼。 ReentrantLock 和 synchronized 都是可重入鎖
公平鎖 : 等待時間最久的線程會優先獲得鎖 , synchronized lock 默認都為非公平鎖
鎖主要分為:悲觀鎖 (線程一旦得到鎖,其他線程就掛起等待。用于寫入頻繁的 synchronized)和 樂觀鎖(假設沒有沖突,不加鎖,更新數據時判斷該數據是否過期,過期的話則不進行數據更新,適用于讀取操作頻繁的場景,比如 AtomicInteger、AtomicLong、AtomicBoolean)
鎖的狀態依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態。鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖。但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級。
volatile :只能用來修飾變量,適用修飾可能被多線程同時訪問的變量,相當于輕量級的synchronized,保證有序性和 可見性(變量位于主內存中,每個線程還有自己的工作內存,變量在自己線程的工作內存中有份拷貝,線程直接操作的是這個拷貝被 volatile 修飾的變量改變后會立即同步到主內存)。
synchronized :是java 的關鍵字(悲觀鎖),自動釋放鎖,并且無法中斷鎖。保證 原子性、可見性 和 有序性。
Lock : java 中的一個接口,lock 需要手動釋放,所以需要寫到 try catch 塊中并在 finally 中釋放鎖,可以手動中斷鎖。
雙重檢查單例加volatile的原因 : 將instance =newInstance(); 創建實例分為三個過程 ,1.分配內存 2.初始化 3.將instance指向分配的內存空。但是如果 在另一個線程中想要使用instance,發現instance!=null,但是實際上instance還未初始化完畢這個問題。
Jvm 調用方法過程
- 在運行前確認方法中的 局部變量的數量、操作棧大小 和 方法參數數量
- JVM在執行方法中 會將 局部變量,操作數,方法返回地址等信息 保存在 棧幀中,一個線程中 只有位于頂部的棧幀處于活動中
GC清理算法
GC管理的主要區域是Java堆,一般情況下只針對堆進行垃圾回收。方法區、棧和本地方法區不被GC所管理,因而選擇這些區域內的對象作為GC roots,被GC roots引用的對象不被GC回收。
- 程序計數法
- 程序可達性
清理算法
- 復制回收算法 :內存使用率50%,只使用一半內存, 地址連貫,多用于新生代。
- 標記清除算法:使用率100%,地址碎片化,多用于老年代
- 標記整理算法:在標記清除基礎上,對于存活對象進行整理,多用于碎片較多的老年代。
JAVA反射
反射就是在運行時才知道要操作的類是什么,并且可以在運行時獲取類的完整構造,并調用對應的方法。
反射過程:獲取反射的 Class 對象、通過反射創建類對象、通過反射獲取類屬性方法及構造器。
獲取類的 Class 對象實例
Class clz = Class.forName("com.Apple");
根據 Class 對象實例獲取 Constructor 對象
Constructor appleConstructor = clz.getConstructor();
使用 Constructor 對象的 newInstance 方法獲取反射類對象
Object appleObj = appleConstructor.newInstance();
而如果要調用某一個方法,則需要經過下面的步驟:
獲取方法的 Method 對象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
利用 invoke 方法調用方法
setPriceMethod.invoke(appleObj, 14);
JAVA泛型
泛型有三種使用方式,分別為:泛型類、泛型接口、泛型方法
- 泛型類: 如List、Set、Map。
- 泛型接口:如interface Generator<T>,主要用于生產器
- 泛型方法:如public <T> T showKeyName(Generic<T> container)
數據結構
ArrayList 、LinkedList
ArrayList :基于動態數組實現,所以查找快(O1) 增刪慢 (On),如果創建方法無參數,默認數組長度為10,添加元素如果需要擴容則是新創建一個 數組,進行數據的轉移拷貝。刪除的時候 如果刪除成功,后續元素需要通過System.arraycopy 進行元素移動(相當的低效)。改查 則比較迅速。
LinkedList : 底層基于雙向鏈表,維持了 last 和 next 兩個節點,所以 查找慢 (On) 增刪快(O1),鏈表的查找是循環的(不過查找的過程是 先判斷index是靠近哪一段 然后再進行查找 可以理解為 O(n/2)),但是速度還是慢。在添加和刪除中,因為添加是直接放到鏈表尾部 但是刪除存在 先循環一遍,然后刪除的情況,不過 相對于ArrayList的復制要好的很多了。
HashMap SparseArray HashTable TreeMap LinkedHashMap ArrayMap
- HashMap : jdk8后由數組、鏈表和紅黑樹組成,基于散列表實現。當數組中的數據出現 hash沖突的時候啟動鏈表,當鏈表中的 個數超過8個 將鏈表轉為紅黑樹。允許 key val 為NULL,key和 val存儲數據可以為任何類型(非基本類型 會自動裝箱)。HashMap并不是線程安全的,可以通過 Collections.synchronizedMap(new HashMap)的方式獲得線程的Hahsmap,或者使用 下邊的ConcurrentHashMap。
由于HashCode沖突、自動裝箱和Entry類型多余元素問題,數據量小的時候推薦使用 SparseArray 和 ArrayMap
HashMap 源碼參考文章
SparseArray : key 只能為整數型,內部也是兩個數組,一個存key的hashcode,一個存 key和val 交叉保存, 添加的時候 key 不用裝箱,val則需要如果是基本類型,查找key還是用的二分法查找。也就是說它的時間復雜度還是O(logN)
ArrayMap :key val 可以為 任意類型(非基本類型,會自動裝箱),中有兩個數組,一個存儲key 的hash值,另外一個交叉存儲 key val 數據(key val key val .... 形式存儲)
TreeMap : 相比較于HashMap,TreeMap實現SortedMap接口,所以TreeMap是有序的!HashMap是無序的。
HashTable : 其實主體和 HashMap類似,但是寫入和 讀取方法 添加了 synchronize 可以做到 線程安全,key 和val 不能為null,但是效率沒有HashMap高。
- LinkedHashMap: LinkedHashMap保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的.也可以在構造時用帶參數,按照應用次數排序。在遍歷的時候會比HashMap慢,不過有種情況例外,當HashMap容量很大,實際數據較少時,遍歷起來可能會比LinkedHashMap慢,因為LinkedHashMap的遍歷速度只和實際數據有關,和容量無關,而HashMap的遍歷速度和他的容量有關。
- ConcurrentHashMap :ConcurrentHashMap由多個segment 組成,每個segment 包含一個Entity 的數組。這里比HashMap 多了一個segment 類。該類繼承了ReentrantLock 類,所以本身是一個鎖。當多線程對ConcurrentHashMap 操作時,不是完全鎖住map, 而是鎖住相應的segment 。這樣提高了并發效率。缺點:當遍歷ConcurrentMap中的元素時,需要獲取所有的segment 的鎖,使用遍歷時慢。鎖的增多,占用了系統的資源。使得對整個集合進行操作的一些方法
ConcurrentHashMap 和 HashMap不多的博客推薦 傳送門
常用設計模式及源碼使用
-
單例模式
初始化比較復雜,并且程序中只需要一個。避免重復創建消耗內存
Android中 獲取WindowManager服務引用 WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);
l另外一種不錯實現單例的方式 使用 eunm,
public class Singleton {
private static volatile Singleton s;
private Singleton(){};
public static Singleton getInstance() {
if(s == null) {
synchronized (Singleton.class) {
if(s == null) {
s = new Singleton();
}
}
}
return s;
}
}
-
創建者模式
創建某對象時,需要設定很多的參數(通過setter方法),但是這些參數必須按照某個順序設定
Android 中 創建所有的 Dialog 中使用的
public class TestClient {
private int index;
private String name;
public TestClient() {
this(new Builder());
}
public TestClient(Builder builder){
this.index = builder.index;
this.name = builder.name;
}
public static final class Builder {
private int index;
private String name;
public Builder() {
this.index = 1;
this.name = "xxx";
}
public Builder(TestClient testClient){
this.index = testClient.index;
this.name = testClient.name;
}
public Builder setIndex(int index) {
this.index = index;
return this;
}
public Builder setName(String name) {
this.name = name;
return this;
}
public TestClient build(){
return new TestClient(this);
}
}
}
- 原型模式
-
工廠模式
定義一個創建對象的工廠,根據不同傳參 創建不同的對象。
Android 中 BitmapFactory 和 Iterator 根據循環對象不同返回不同的對象 -
策略模式
有一系列的算法,將算法封裝起來(每個算法可以封裝到不同的類中),各個算法之間可以替換,策略模式讓算法獨立于使用它的客戶而獨立變化
Android 中的 時間插值器,可以使用不同的 加速 減速 或者自定義加速器 展示不同的動畫效果 -
責任鏈模式
使多個對象都有機會處理請求,從而避免請求的發送者和接受者直接的耦合關系,將這些對象連成一條鏈,并沿這條鏈傳遞該請求,直到有對象處理它為止。
Android 中有 View 點擊事件分發 或者 第三方庫 OKHttp 中的攔截器 -
命令模式
命令模式將每個請求封裝成一個對象,從而讓用戶使用不同的請求把客戶端參數化;將請求進行排隊或者記錄請求日志,以及支持可撤銷操作。
Android 事件機制中,底層邏輯對事件的轉發處理。每次的按鍵事件會被封裝成NotifyKeyArgs對象,通過InputDispatcher封裝具體的事件操作 / Runable實現中封裝我們需要的實現 -
觀察者模式
Java的Observable類和Observer接口就是實現了觀察者模式。一個Observer對象監視著一個Observable對象的變化,當Observable對象發生變化時,Observer得到通知,就可以進行相應的工作。 -
中介者模式
在Binder機制中,即ServiceManager持有各種系統服務的引用 ,當我們需要獲取系統的Service時,首先是向ServiceManager查詢指定標示符對應的Binder,再由ServiceManager返回Binder的引用。并且客戶端和服務端之間的通信是通過Binder驅動來實現,這里的ServiceManager和Binder驅動就是中介者。 -
代理模式
給某一個對象提供一個代理,并由代理對象控制對原對象的引用 (,靜態代理 和 動態代理) -
適配器模式
把一個類的接口變換成客戶端所期待的另一個接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。