Java後臺開發工程師面試要點準備

自我介紹

自我介紹提前準備好

問面試官的問題

  1. 對新員工的培訓機制
  2. 部門業務現狀和未來規劃
  3. 技術棧
  4. 您對於職場新員工的期待/建議是什麼

Java

1. 接口和抽象類的區別是什麼?

  • 抽象類和接口都不能夠實例化,但可以定義抽象類和接口類型的引用
  • 接口可以 extends 多個接口
  • 類可以實現很多個接口,但是隻能繼承一個抽象類
  • 抽象類中可以有一個或多個抽象方法,而接口中的方法必須都是抽象方法
  • 接口中的方法定義默認爲 public abstract類型,接口中的成員變量類型默認爲public static final
  • 抽象類和方法必須使用abstract關鍵聲明爲抽象,而接口中的方法默認被修飾爲public abstract類型(默認爲公開抽象的方法)

2. Java8定義接口關鍵字
public、abstruct、static、default

public interface Test {
    public void fun1();
    
    abstruct void fun2();
    
    static void fun3() {
        System.out.println("hh");
    }
    
    default void fun4() {
        System.out.println("hh");
    }
}

3. 什麼是值傳遞和引用傳遞
值傳遞,意味着傳遞了對象的一個副本。因此,就算是改變了對象副本,也不會影響源對象的值
引用傳遞,意味着傳遞的並不是實際的對象,而是對象的引用,傳遞的是地址。因此,外部對引用對象所做的改變會反映到所有的對象上

4. HashMap和Hashtable有什麼區別?

  • HashMap允許鍵和值是null,而Hashtable不允許鍵或者值是null
  • Hashtable是同步的,而HashMap不是。因此,HashMap更適合於單線程環境,而Hashtable適合於多線程環境
  • HashMap提供了可供應用迭代的鍵的集合,因此,HashMap是快速失敗的。另一方面,Hashtable提供了對鍵的列舉

5. 說出ArrayList,Vector, LinkedList的存儲性能和特性

  • ArrayList:

    • 底層數據結構是數組,查詢快,增刪慢
    • 線程不安全,效率高
    • 按下標訪問的o(1)時間複雜度,訪問速度讀快,但增刪數據需要移動後面的所有數據,需要o(n)的時間複雜度
    • Arraylist默認大小的10,每次擴容增加1.5倍
  • Vector:

    • 底層數據結構是數組,查詢快,增刪慢
    • 線程安全,效率低
  • LinkedList:

    • 底層數據結構是鏈表,查詢慢,增刪快,由一個個Node節點構成,繼承了deque雙端隊列
    • 鏈表結構特性就是在兩端增刪節點速度快,但按照索引訪問效率低,是O(N/2),頭尾就近原則
    • 按需分配空間,不會浪費空間
    • 線程不安全,效率高

6. List、set有序無序

  • Set
    HashSet無序,LinkedHashSet(插入順序)和TreeSet(字母排序/字典序)有序
  • List
    List集合是有序的

7. HashSet和TreeSet

  • HashSet的底層是HashMap
  • TreeSet的底層是TreeMap

8. TreeSet使用比較器
在Comparable和Compartor排序比較器都存在的情況下,Compartor外部比較器優先,因爲在TreeMap獲取元素getEntry方法中,優先判斷Compartor比較器,存在則返回元素

final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }

9. Comparable和Compartor

  • Comparable
    內部比較器,在需要比較的對象中實現Comparable接口,並且重寫compareTo方法
    public class Girl implements Comparable<Object> {
    	private int age;
    
    	...
    
        @Override
        public int compareTo(Object o) {
            Girl g = (Girl)o;
            return this.age - g.getAge();
        }
    }
    
  • Compartor
    外部比較器,新建一個比較器類,實現Compartor接口,重寫compare方法
    public class GirlComparator implements Comparator<Girl> {
        @Override
        public int compare(Girl g1, Girl g2) {
            return g1.getAge() - g2.getAge();
        }
    }
    

10. Collection 和 Collections的區別

  • Collection是集合類的上級接口,繼承與他的接口主要有Set 和List
  • Collections是針對集合類的一個幫助類,他提供一系列靜態方法實現對各種集合的搜索、排序、線程安全化等操作

11. &和&&的區別
&是位運算符,表示按位與運算,&&是邏輯運算符,表示邏輯與(and)

12. final和finally、finalize的區別

  • final:關鍵字,修飾類,不能派生子類,修飾變量或方法,不能被改變
  • finally:異常處理語句塊,如果拋出一個異常,那麼相匹配的catch語句就會執行,然後控制就會進入finally塊
  • finalize:方法名,是在 Object 類中定義的,因此所有的類都繼承了它,在垃圾收集器執行的時候會調用被回收對象的此方法,可以覆蓋此方法提供垃圾收集時的其他資源回收,例如關閉文件等

13. try和finally中同時return會返回什麼
返回finally語句塊內容

14. equals ==區別
如果沒有重寫,其實是一樣的,都是比較地址。可以重寫成比較值

15. sleep() 和 wait() 有什麼區別?

  • Thread.sleep()只會讓出CPU,不會釋放對象鎖,不釋放所佔有的資源。
  • Object.wait()不僅會讓出CPU,對象調用wait方法還會導致本線程放棄對象鎖,釋放所佔有的所有資源,進入等待此對象的等待鎖定池,不能自動喚醒,只有針對此對象發出notify()方法(或notifyAll())後本線程才進入對象鎖定池準備獲得對象鎖進入運行狀態

16. Java中異常分類

  • 運行時異常:都是RuntimeException類及其子類異常,如NullPointerException(空指針異常)、IndexOutOfBoundsException(下標越界異常)等,這些異常是不檢查異常,程序中可以選擇捕獲處理,也可以不處理。這些異常一般是由程序邏輯錯誤引起的,程序應該從邏輯角度儘可能避免這類異常的發生。
    運行時異常的特點是Java編譯器不會檢查它,也就是說,當程序中可能出現這類異常,即使沒有用try-catch語句捕獲它,也沒有用throws子句聲明拋出它,也會編譯通過。
  • 非運行時異常(編譯異常):是RuntimeException以外的異常,類型上都屬於Exception類及其子類。從程序語法角度講是必須進行處理的異常,如果不處理,程序就不能編譯通過。如IOException、SQLException等以及用戶自定義的Exception異常,一般情況下不自定義檢查異常。

17. error和exception有什麼區別?

  • Error(錯誤)是系統中的錯誤,程序員是不能改變的和處理的,是在程序編譯時出現的錯誤,只能通過修改程序才能修正。一般是指與虛擬機相關的問題,如系統崩潰,虛擬機錯誤,內存空間不足,方法調用棧溢等。對於這類錯誤的導致的應用程序中斷,僅靠程序本身無法恢復和和預防,遇到這樣的錯誤,建議讓程序終止
  • Exception(異常)表示程序可以處理的異常,可以捕獲且可能恢復。遇到這類異常,應該儘可能處理異常,使程序恢復運行,而不應該隨意終止異常

18. Java中的異常處理機制的簡單原理和應用

  • 當JAVA 程序違反了JAVA的語義規則時,JAVA虛擬機就會將發生的錯誤表示爲一個異常。違反語義規則包括2種情況。一種是JAVA類庫內置的語義檢查。例如數組下標越界,會引發IndexOutOfBoundsException;訪問null的對象時會引發NullPointerException。另一種情況就是JAVA允許程序員擴展這種語義檢查,程序員可以創建自己的異常,並自由選擇在何時用throw關鍵字引發異常。
  • 所有的異常都是java.lang.Thowable的子類

19. 同步有幾種實現方法,都是什麼?

  • 同步的實現方面有兩種,分別是同步代碼塊和同步方法

20. synchronized和ReentrantLock的區別

  • synchronized:關鍵字,Java語言內置,不需要手動釋放鎖,在發生異常時,會自動釋放線程佔有的鎖,因此不會導致死鎖現象發生,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷
  • ReentrantLock:類,通過這個類可以實現同步訪問,需要用戶手動釋放鎖,並且必須在finally從句中釋放,否則可能導致死鎖,Lock可以讓等待鎖的線程響應中斷,可以提高多個線程進行讀操作的效率

21. 重載(Overload)和重寫(Override)的區別?

  • 方法的重載和重寫都是實現多態的方式,區別在於前者實現的是編譯時的多態性,而後者實現的是運行時的多態性。重載發生在一個類中,同名的方法如果有不同的參數列表(參數類型不同、參數個數不同或者二者都不同甚至是參數順序)則視爲重載;
  • 重寫發生在子類與父類之間,重寫要求子類被重寫方法與父類被重寫方法有相同的參數列表,有兼容的返回類型,比父類被重寫方法更好訪問,不能比父類被重寫方法聲明更多的異常(里氏代換原則)。重載對返回類型沒有特殊的要求,不能根據返回類型進行區分。

22. throw和throws

  • throw語句用來明確地拋出一個”異常”,寫在代碼塊中。
  • throws用來標明一個成員函數可能拋出的各種”異常”,寫在方法後

23. 垃圾回收器的基本原理是什麼?垃圾回收器可以馬上回收內存嗎?有什麼辦法主動通知虛擬機進行垃圾回收?

  • 對於GC來說,當程序員創建對象時,GC就開始監控這個對象的地址、大小以及使用情況。通常,GC採用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是”可達的”,哪些對象是”不可達的”。
  • 當GC確定一些對象爲”不可達”時,GC就有責任回收這些內存空間。可以。程序員可以手動執行System.gc(),通知GC運行,但是Java虛擬機並不保證GC一定會執行

24. 什麼是java序列化,如何實現java序列化?

  • 序列化就是一種用來處理對象流的機制,把Java對象轉換爲二進制的數據流。可以對流化後的對象進行讀寫操作,也可將流化後的對象傳輸於網絡之間。
  • 序列化的實現:將需要被序列化的類實現Serializable接口,該接口沒有需要實現的方法,implements Serializable只是爲了標註該對象是可被序列化的,然後使用一個輸出流(如:FileOutputStream)來構造一個ObjectOutputStream(對象流)對象,接着,使用ObjectOutputStream對象的writeObject(Object obj)方法就可以將參數爲obj的對象寫出(即保存其狀態),要恢復的話則用輸入流

25. 是否可以從一個static方法內部發出對非static方法的調用?

  • 不可以,如果其中包含對象的method();不能保證對象初始化

26. 使用final關鍵字修飾一個變量時,是引用不能變,還是引用的對象不能變?

  • 使用final關鍵字修飾一個變量時,是指引用地址不能變,引用變量所指向的對象中的內容還是可以改變的
    final StringBuffer a=new StringBuffer(“immutable”); 
    執行如下語句將報告編譯期錯誤: 
    a=new StringBuffer(“”); 
    但是,執行如下語句則可以通過編譯: 
    a.append(” broken!);
    

27. 請說出作用域public,private,protected,以及不寫時的區別

作用域 當前類 同一package 子類 其它package
public
protected ×
friendly × ×
private × × ×

28. StringBuffer與StringBuilder的區別

  • StringBuffer和StringBuilder類都表示內容可以被修改的字符串,StringBuilder是線程不安全的,運行效率高,如果一個字符串變量是在方法裏面定義,這種情況只可能有一個線程訪問它,不存在不安全的因素了,則用StringBuilder。如果要在類裏面定義成員變量,並且這個類的實例對象會在多線程環境下使用,那麼最好用StringBuffer

29. heap和stack有什麼區別

  • java的內存分爲兩類,一類是棧內存,一類是堆內存。棧內存是指程序進入一個方法時,會爲這個方法單獨分配一塊私屬存儲空間,用於存儲這個方法內部的局部變量,當這個方法結束時,分配給這個方法的棧會釋放,這個棧中的變量也將隨之釋放。
  • 堆是與棧作用不同的內存,一般用於存放不放在當前方法棧中的那些數據,例如,使用new創建的對象都放在堆裏,所以,它不會隨方法的結束而消失。方法中的局部變量使用final修飾後,放在堆中,而不是棧中

30. 深拷貝和淺拷貝

  • 如果在拷貝這個對象的時候,只對基本數據類型進行了拷貝,而對引用數據類型只是進行了引用的傳遞,而沒有真實的創建一個新的對象,則認爲是淺拷貝。反之,在對引用數據類型進行拷貝的時候,創建了一個新的對象,並且複製其內的成員變量,則認爲是深拷貝
  • 淺拷貝: 對基本數據類型進行值傳遞,對引用數據類型進行引用傳遞般的拷貝
  • 深拷貝: 對基本數據類型進行值傳遞,對引用數據類型,創建一個新的對象,並複製其內容
  • 總結: 淺拷貝和深拷貝只是相對的,如果一個對象內部只有基本數據類型,那用 clone() 方法獲取到的就是這個對象的深拷貝,而如果其內部還有引用數據類型,那用 clone() 方法就是一次淺拷貝的操作

31. CAS

  • cas是一種基於鎖的操作,而且是樂觀鎖機制。在java中鎖分爲樂觀鎖和悲觀鎖。悲觀鎖是將資源鎖住,等一個之前獲得鎖的線程釋放鎖之後,下一個線程纔可以訪問。而樂觀鎖採取了一種寬泛的態度,通過某種方式不加鎖來處理資源,比如通過給記錄加version來獲取數據,性能較悲觀鎖有很大的提高
  • Compare and Swap,比較並操作,指的是將預期值與當前變量的值比較(compare),如果相等則使用新值替換(swap)當前變量,否則不作操作
  • CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改爲B,否則什麼都不做

32. 構造器能否被重寫?

  • 構造器是不能被繼承的,因爲每個類的類名都不相同,而構造器名稱與類名相同,所以根本談不上繼承。
  • 又由於構造器不能繼承,所以就不能被重寫。但是,在同一個類中,構造器是可以被重載的

33. 查看死鎖

  • Jconsole
  • Jstack:Jstack -l/-F 進程號

34. 引用

  • 強引用: 只要某個對象有強引用與之關聯,JVM必定不會回收這個對象,即使在內存不足的情況下,JVM寧願拋出OutOfMemory錯誤也不會回收這種對象
  • 軟引用:只有在內存不足的時候JVM纔會回收該對象。
    軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被JVM回收,這個軟引用就會被加入到與之關聯的引用隊列中
    SoftReference<String> sr = new SoftReference<String>(new String("hello"));
    
  • 弱引用: 當JVM進行垃圾回收時,無論內存是否充足,都會回收被弱引用關聯的對象
    WeakReference<String> sr = new WeakReference<String>(new String("hello"));
    
  • 虛引用: 如果一個對象與虛引用關聯,則跟沒有引用與之關聯一樣,在任何時候都可能被垃圾回收器回收
    ReferenceQueue<String> queue = new ReferenceQueue<String>();
    PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);
    

35. 產生死鎖的四個必要條件

  • 互斥條件:一個資源每次只能被一個進程使用
  • 佔有且等待:一個進程因請求資源而阻塞時,對已獲得的資源保持不放
  • 不可搶佔:進程已獲得的資源,在末使用完之前,不能強行剝奪
  • 循環等待:若干進程之間形成一種頭尾相接的循環等待資源關係

36. 避免死鎖

  • 加鎖順序,確保所有的線程都是按照相同的順序獲得鎖,那麼死鎖就不會發生
  • 加鎖時限,嘗試獲取鎖的時候加一個超時時間,這也就意味着在嘗試獲取鎖的過程中若超過了這個時限該線程則放棄對該鎖請求。若一個線程沒有在給定的時限內成功獲得所有需要的鎖,則會進行回退並釋放所有已經獲得的鎖,然後等待一段隨機的時間再重試
  • 死鎖檢測,主要是針對那些不可能實現按序加鎖並且鎖超時也不可行的場景

37. 公平鎖、非公平鎖

  • 公平鎖(Fair):加鎖前檢查是否有排隊等待的線程,優先排隊等待的線程,先來先得
  • 非公平鎖(Nonfair):加鎖時不考慮排隊等待問題,直接嘗試獲取鎖,獲取不到自動到隊尾等待
    非公平鎖性能比公平鎖高5~10倍,因爲公平鎖需要在多核的情況下維護一個隊列
    Java中的ReentrantLock 默認的lock()方法採用的是非公平鎖

38. Synchronized實現原理

  • 顯示同步,monitorenter指令指向同步代碼塊的開始位置,monitorexit指令則指明同步代碼塊的結束位置,當執行monitorenter指令時,當前線程將試圖獲取 objectref(即對象鎖) 所對應的 monitor 的持有權,當 objectref 的 monitor 的進入計數器爲 0,那線程可以成功取得 monitor,並將計數器值設置爲 1,取鎖成功。如果當前線程已經擁有 objectref 的 monitor 的持有權,那它可以重入這個 monitor (關於重入性稍後會分析),重入時計數器的值也會加 1。倘若其他線程已經擁有 objectref 的 monitor 的所有權,那當前線程將被阻塞,直到正在執行線程執行完畢,即monitorexit指令被執行,執行線程將釋放 monitor(鎖)並設置計數器置爲0 ,其他線程將有機會持有 monitor 。值得注意的是編譯器將會確保無論方法通過何種方式完成,方法中調用過的每條 monitorenter 指令都有執行其對應 monitorexit 指令,而無論這個方法是正常結束還是異常結束。爲了保證在方法異常完成時 monitorenter 和 monitorexit 指令依然可以正確配對執行,編譯器會自動產生一個異常處理器,這個異常處理器聲明可處理所有的異常,它的目的就是用來執行 monitorexit 指令。從字節碼中也可以看出多了一個monitorexit指令,它就是異常結束時被執行的釋放monitor 的指令
  • 修飾同步方法: 隱式同步,不是由 monitorenter 和 monitorexit 指令來實現同步的,而是由方法調用指令讀取運行時常量池中方法的 ACC_SYNCHRONIZED 標誌來隱式實現的,該標識指明瞭該方法是一個同步方法,JVM通過該ACC_SYNCHRONIZED訪問標誌來辨別一個方法是否聲明爲同步方法,從而執行相應的同步調用
  • notify/notifyAll和wait方法,在使用這3個方法時,必須處於synchronized代碼塊或者synchronized方法中,否則就會拋出IllegalMonitorStateException異常,這是因爲調用這幾個方法前必須拿到當前對象的監視器monitor對象,也就是說notify/notifyAll和wait方法依賴於monitor對象,在前面的分析中,我們知道monitor 存在於對象頭的Mark Word 中(存儲monitor引用指針),而synchronized關鍵字可以獲取 monitor ,這也就是爲什麼notify/notifyAll和wait方法必須在synchronized代碼塊或者synchronized方法調用的原因.

39. 使⽤ synchronized 修飾靜態⽅法和⾮靜態⽅法有什麼區別

  • 當修飾靜態方法的時候,鎖住的是當前類的Class
  • 當修飾非靜態方法的時候,鎖住的是當前的實例對象this
  • 當修飾一個obj對象的時候,鎖住的就是obj對象的修改本身

40. 鎖的狀態
鎖的狀態總共有四種,無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨着鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖,但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級

  • 無鎖
  • 偏向鎖,如果一個線程獲得了鎖,那麼鎖就進入偏向模式,此時Mark Word 的結構也變爲偏向鎖結構,當這個線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操作,從而也就提供程序的性能
  • 輕量級鎖,倘若偏向鎖失敗,虛擬機並不會立即升級爲重量級鎖,它還會嘗試使用一種稱爲輕量級鎖的優化手段(1.6之後加入的),此時Mark Word 的結構也變爲輕量級鎖的結構。輕量級鎖能夠提升程序性能的依據是“對絕大部分的鎖,在整個同步週期內都不存在競爭”,注意這是經驗數據。需要了解的是,輕量級鎖所適應的場景是線程交替執行同步塊的場合,如果存在同一時間訪問同一鎖的場合,就會導致輕量級鎖膨脹爲重量級鎖
  • 自旋鎖,輕量級鎖失敗後,虛擬機爲了避免線程真實地在操作系統層面掛起,還會進行一項稱爲自旋鎖的優化手段。這是基於在大多數情況下,線程持有鎖的時間都不會太長,如果直接掛起操作系統層面的線程可能會得不償失,畢竟操作系統實現線程之間的切換時需要從用戶態轉換到核心態,這個狀態之間的轉換需要相對比較長的時間,時間成本相對較高,因此自旋鎖會假設在不久將來,當前的線程可以獲得鎖,因此虛擬機會讓當前想要獲取鎖的線程做幾個空循環(這也是稱爲自旋的原因),一般不會太久,可能是50個循環或100循環,在經過若干次循環後,如果得到鎖,就順利進入臨界區。如果還不能獲得鎖,那就會將線程在操作系統層面掛起,這就是自旋鎖的優化方式,這種方式確實也是可以提升效率的。最後沒辦法也就只能升級爲重量級鎖了
  • 重量級鎖

41. 談談volatile的作用?實現原理以及使用場景

  • ①volatile只能保證多線程三大特性中的可見性有序性
    1)可見性:每個線程都有一個自己的本地內存,對於共享變量,線程每次讀取和寫入的都是共享變量在本地內存中的副本,然後在某個時間點將本地內存和主內存的值進行同步。而當修改volatile修飾的變量後,強制把對變量的修改同步到主內存。而其它線程在讀取自己的本地內存中的值的時候,發現是valotile修飾的且已經被修改了,會把自己本地內存中的值置爲無效,然後從主內存中讀取。
    2)有序性:在執行程序時,爲了提高性能,處理器和編譯器常常會對指令進行重排序,這種重排序一般只能保證單線程下執行結果不被改變。當被volatile修飾的變量後,將會禁止重排序。

  • ②代碼層面實現:通過內存屏障來實現的。所謂的內存屏障,是在某些指令中插入屏障指令。虛擬機讀取到這些屏障指令時主動將本地內存的變量值刷新到內存,或直接從主內存中讀取變量的值。通過屏障指令會禁止屏障前的操作命令和屏障後的命令進行重排序。系統層面實現:在多處理器下,保證各個處理器的緩存是一致的,每個處理器通過嗅探在總線上傳播的數據來檢查自己緩存的值是不是過期了,當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態。

  • ③1:多線程間狀態標識;2:單例模式中雙重檢查鎖的寫法;3:定期觀察成員變量狀態的方法

42. 線程與進程

  • 進程是系統進行資源調度和分配的一個基本單位,線程是進程的實體,是CPU調度和分派的基本單位
  • 一個進程可以有多個線程,多個線程也可以併發執行
  • 線程不能看做獨立應用,而進程可看做獨立應用
  • 進程有獨立的地址空間,相互不影響,線程只是進程的不同執行路徑,沒有獨立的地址空間,多進程的程序比多線程的程序健壯
  • 進程的切換比線程切換開銷大

43. 如何停止線程?

  • 主線程提供volatile boolean flag,線程內while判斷flag
  • 線程內while(!this.isInterrupted),主線程裏調用interrupt()方法,通知線程應該中斷了
  • 將一個線程設置爲守護線程後,當進程中沒有非守護線程後,守護線程自動結束

44. 線程實現方式

  • extends Thread
    public class ThreadCreateTest {
        public static void main(String[] args) {
            new MyThread().start();
        }
    
        class MyThread extends Thread {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
            }
        }
    }
    
  • implements Runnable,創建Thread類對象傳遞參數
    public class RunableCreateTest {
        public static void main(String[] args) {
            MyRunnable runnable = new MyRunnable();
            new Thread(runnable).start();
        }
    
        class MyRunnable implements Runnable {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
            }
        }
    }
    
  • 通過Callable和Future創建線程
    public class CallableCreateTest {
        public static void main(String[] args) throws Exception {
        	 // 將Callable包裝成FutureTask,FutureTask也是一種Runnable
            MyCallable callable = new MyCallable();
            FutureTask<Integer> futureTask = new FutureTask<>(callable);
            new Thread(futureTask).start();
    
            // get方法會阻塞調用的線程
            Integer sum = futureTask.get();
            System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
        }
    
    
        class MyCallable implements Callable<Integer> {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
        
                int sum = 0;
                for (int i = 0; i <= 100000; i++) {
                    sum += i;
                }
                Thread.sleep(5000);
        
                System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
                return sum;
            }
        }
    }
    

45. 開啓線程寫法

Thread t = new Thread(new Runnable() {
    @Override
    public void run() {

    }
});
t.start();

new Thread(new Runnable() {
    @Override
    public void run() {

    }
}).start();

// lamboda表達式,可以不用重寫run方法
Thread t1 = new Thread(() -> {
    System.out.println("hh");
});
t1.start();

new Thread(() -> {
    System.out.println("hh");
}).start();

46 同一線程多次調用start方法
線程首先會運行一次,然後拋出java.lang.IllegalThreadStateException異常

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

第一次運行start()方法時,threadStatus是0,此時if條件不滿足,繼續執行,會將當前線程添加到線程組中去執行。第二次運行start()方法時,threadStatus變成了2,if條件滿足,於是拋出了java.lang.IllegalThreadStateException異常

47. Java自動裝箱與拆箱

//boolean原生類型自動裝箱成Boolean
public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

//byte原生類型自動裝箱成Byte
public static Byte valueOf(byte b) {
    final int offset = 128;
    return ByteCache.cache[(int)b + offset];
}

//byte原生類型自動裝箱成Byte
public static Short valueOf(short s) {
    final int offset = 128;
    int sAsInt = s;
    if (sAsInt >= -128 && sAsInt <= 127) { // must cache
        return ShortCache.cache[sAsInt + offset];
    }
    return new Short(s);
}

//char原生類型自動裝箱成Character
public static Character valueOf(char c) {
    if (c <= 127) { // must cache
        return CharacterCache.cache[(int)c];
    }
    return new Character(c);
}

//int原生類型自動裝箱成Integer
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

//long原生類型自動裝箱成Long
public static Long valueOf(long l) {
    final int offset = 128;
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

//double原生類型自動裝箱成Double
public static Double valueOf(double d) {
    return new Double(d);
}

//float原生類型自動裝箱成Float
public static Float valueOf(float f) {
    return new Float(f);
}

Integer b=3:3是int類型,如果要把int類型賦值給Integer包裝類,就需要自動裝箱,相當於Integer b=Integer.ValueOf(3)

48. 基本數據類型的特點,最大值和最小值
基本類型:int 二進制位數:32
包裝類:java.lang.Integer
最小值:Integer.MIN_VALUE= -2147483648 (-2的31次方)
最大值:Integer.MAX_VALUE= 2147483647 (2的31次方-1)

注意: 除double 和float 兩種類型以外,其他基本類型入Integer值 在 -128 ~ 127之間時不會新建一個Integer 對象而是從緩存中獲取。所以在做 == 判斷時 要注意值的大小,如果超過範圍,則兩個值即使一樣但 == 比較的結果還是false

49. 什麼是反射

  • Java反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能成爲Java語言的反射機制

50. 反射的方法

  • getDeclaredMethod:獲取類的所有方法,不能獲取繼承方法
  • getMethod:只能獲取公有方法,但包括繼承的方法

51. loadClass和forName的區別

  • CLass.forName得到的class是已經初始化完成的。eg:加載MySQL驅動需要執行驅動中的static代碼
  • CLassLoader.loadClass得到的class是還沒有鏈接,還沒有初始化

52. 談談如何通過反射創建對象

  1. 通過Class字節碼對象newInstance();-+通過類對象調用newInstance()方法,例如:String.class.newInstance()
  2. 通過類對象的getConstructor()或getDeclaredConstructor()方法獲得構造器(Constructor)對象並調用其newInstance()方法創建對象,例如:String.class.getConstructor(String.class).newInstance(“Hello”)

53. ArrayList是否會越界?

  • 會,在多線程下

54. 爲什麼集合類沒有實現Cloneable和Serializable接口?

  • 克隆(cloning)或者序列化(serialization)的語義和含義是跟具體的實現相關的。因此應該由集合類的具體實現類來決定如何被克隆或者序列化

55. Iterator和ListIterator的區別是什麼?

  • Iterator可用來遍歷Set和List集合,但是ListIterator只能用來遍歷List
  • Iterator對集合只能是順序向後遍歷,ListIterator既可以向前也可以向後
  • ListIterator實現了Iterator接口,幷包含其他的功能,比如:增加元素,替換元素,獲取前一個和後一個元素的索引,等等

56. 快速失敗(fail-fast)和安全失敗(fail-safe)的區別是什麼?

  • 快速失敗(fail-fast)
    在使用迭代器對集合對象進行遍歷的時候,如果A線程對集合進行遍歷,正好B線程對集合進行修改(增加、刪除、修改)則A線程會拋出ConcurrentModificationException異常
    **原理:**迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個 modCount 變量。集合在被遍歷期間如果內容發生變化,就會改變modCount的值。每當迭代器使用hashNext()/next()遍歷下一個元素之前,都會檢測modCount變量是否爲expectedmodCount值,是的話就返回遍歷;否則拋出異常,終止遍歷
  • 安全失敗(fail-safe)
    採用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先複製原有集合內容,在拷貝的集合上進行遍歷
  • 原理: 由於迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改並不能被迭代器檢測到,所以不會觸發Concurrent Modification Exception,例如CopyOnWriteArrayList

線程創建有很大開銷,怎麼優化?

  • 使用線程池

57. Java中有幾種線程池?
java裏面的線程池的頂級接口是Executor,Executor並不是一個線程池,而只是一個執行線程的工具,而真正的線程池是ExecutorService

  • newCachedThreadPool 緩存型線程池,在覈心線程達到最大值(Interger. MAX_VALUE)之前,有任務進來就會創建新的核心線程,並加入核心線程池,即時有空閒的線程,也不會複用
  • newFixedThreadPool 建立一個線程數量固定的線程池,規定的最大線程數量,超過這個數量之後進來的任務,會放到等待隊列中,如果有空閒線程,則在等待隊列中獲取,遵循先進先出原則
  • newScheduledThreadPool 計劃型線程池,可以設置固定時間的延時或者定期執行任務,同樣是看線程池中有沒有空閒線程,如果有,直接拿來使用,如果沒有,則新建線程加入池
  • newSingleThreadExecutor 建立一個只有一個線程的線程池,如果有超過一個任務進來,只有一個可以執行,其餘的都會放到等待隊列中,如果有空閒線程,則在等待隊列中獲取,遵循先進先出原則

58. 線程池參數

  • corePoolSize:核心線程數
  • maximumPoolSize:最大線程數
  • keepAliveTime:空閒的線程保留的時間
  • TimeUnit:空閒線程的保留時間單位
  • BlockingQueue:阻塞隊列,存儲等待執行的任務
  • ThreadFactory:線程工廠,用來創建線程
  • RejectedExecutionHandler:隊列已滿,而且任務量大於最大線程的異常處理策略
  • allowCoreThreadTimeout:是否允許核心線程空閒退出,默認值爲false

59. 線程池按以下行爲執行任務

  1. 當線程數小於核心線程數時,創建線程
  2. 當線程數大於等於核心線程數,且任務隊列未滿時,將任務放入任務隊列。
  3. 當線程數大於等於核心線程數,且任務隊列已滿
    ① 若線程數小於最大線程數,創建線程
    ② 若線程數等於最大線程數,拋出異常,拒絕任務

60. 概括的解釋下線程的幾種可用狀態

  • NEW:新建,線程剛創建,尚未啓動
  • RUNNABLE:運行,在JVM中正在運行的線程,其中運行狀態可以有運行中RUNNING和READY兩種狀態,由系統調度進行狀態改變
  • WAITING:無限期等待,不會被CPU分配執行時間,需要顯示喚醒
  • TIME_WAITING:限期等待,在一定時間後會有系統自動喚醒
  • TERMINATED:結束,線程執行完畢,已經退出

61. 講一下非公平鎖和公平鎖在reetrantlock裏的實現

  • 非公平鎖: 當線程爭奪鎖的過程中,會先進行一次CAS嘗試獲取鎖,若失敗,則進入acquire(1)函數,進行一次tryAcquire再次嘗試獲取鎖,若再次失敗,那麼就通過addWaiter將當前線程封裝成node結點加入到Sync隊列,這時候該線程只能乖乖等前面的線程執行完再輪到自己了。
  • 公平鎖: 當線程在獲取鎖的時候,會先判斷Sync隊列中是否有在等待獲取資源的線程。若沒有,則嘗試獲取鎖,若有,那麼就那麼就通過addWaiter將當前線程封裝成node結點加入到Sync隊列中

62. 反射的實現與作用

  • 通過獲取類的字節碼,將字節碼加載到內存中,在程序運行時生成一個該類的實例
  • 作用: 得到一個對象所屬的類,運行時創建一個類,獲取一個類的所有成員變量與方法,在運行時調用對象的方法

63. 給我一個你最常見到的runtime exception
NullPointerExceptio 空指針異常
SecurityException 安全異常
IndexOutOfBoundsException 下標越界異常
ClassNotFoundException 類沒找到時,拋出該異常
ClassCastException 類型強制轉換異常

64. Java元註解

  • @Retention
    表明該註解的生命週期
  • @Target
    表明該註解可以應用的java元素類型(方法、變量…)
  • @Document
    說明該註解將被包含在javadoc中
  • @Inherited
    表明使用了@Inherited註解的註解,所標記的類的子類也會擁有這個註解

65. Serializable中的ID有什麼作用

  • 反序列化的時候使用,若不指定serialVersionUID,編譯器會根據類的內部實現計算一個,反序列化時,會把對象UID和類UID比較,相同,可以反序列化,不同,不能序列化

66. this, super

  • this()函數主要應用於同一類中從某個構造函數調用另一個重載版的構造函數。this()只能用在構造函數中,並且也只能在第一行。所以在同一個構造函數中this()和super()不能同時出現
  • super()函數在子類構造函數中調用父類的構造函數時使用,而且必須要在構造函數的第一行

67. String爲什麼要用final修飾

  • 使用final修飾實現字符串池,節約heap空間,不同的字符串變量指向同一字符串
  • 線程安全,同一個字符串可以被多個線程共享,不會因爲線程安全問題使用同步
  • 安全性,加載時不被改變
  • 因爲字符串不可變,在創建的時候hashcode就被緩存

68. Java8新特性

  • Lambda表達式
  • 新的時間API
  • 函數式編程
  • default關鍵字

69. Java中是否可以覆蓋(override)一個private或者是static的方法?

  • Java中static方法不能被覆蓋,因爲方法覆蓋是基於運行時動態綁定的,而static方法是編譯時靜態綁定的。static方法跟類的任何實例都不相關,所以概念上不適用

70. 當一個對象被當作參數傳遞到一個方法後,此方法可改變這個對象的屬性,並可返回變化後的結果,那麼這裏到底是值傳遞還是引用傳遞?

  • 值傳遞。Java語言的方法調用只支持參數的值傳遞。基本數據類型傳遞副本,引用數據類型傳遞棧中地址的值,真正的對象存放在堆內存中

71. String s = new String(“xyz”);創建了幾個字符串對象?

  • 兩個對象,一個是常量池的"xyz",一個是用new創建在堆上的對象

72. 在 Java 中,如何跳出當前的多重嵌套循環

  • 定義一個標號,break + 標號
    ok:
        for(int i=0;i<10;i++) {
            for(int j=0;j<10;j++)  {
                System.out.println(“i=+ i +,j=+ j);
                if(j == 5) break ok;
            }
        } 
    
  • 讓外層的循環條件表達式的結果可以受到裏層循環體代碼的控制
    for(int i=0;i<arr.length && !found;i++)    {
            for(int j=0;j<arr[i].length;j++){
                System.out.println(“i=+ i +,j=+ j);
                if(arr[i][j]  == 5) {
                    found = true;
                    break;
                }
            }
    }
    

73. Java中創建對象的五種方式

  1. 使用new關鍵字

    Employee emp1 = new Employee();
    
  2. 使用Class類的newInstance方法
    這個newInstance方法調用無參的構造函數創建對象

    Employee emp2 = (Employee) Class.forName("org.programming.mitra.exercises.Employee").newInstance();
    Employee emp2 = Employee.class.newInstance();
    
  3. 使用Constructor類的newInstance方法
    和Class類的newInstance方法很像, java.lang.reflect.Constructor類裏也有一個newInstance方法可以創建對象。我們可以通過這個newInstance方法調用有參數的和私有的構造函數

    Constructor<Employee> constructor = Employee.class.getConstructor();
    Employee emp3 = constructor.newInstance();
    
  4. 使用clone方法
    無論何時我們調用一個對象的clone方法,jvm就會創建一個新的對象,將前面對象的內容全部拷貝進去。用clone方法創建對象並不會調用任何構造函數。
    要使用clone方法,我們需要先實現Cloneable接口並實現其定義的clone方法。

  5. 使用反序列化

    ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
    Employee emp5 = (Employee) in.readObject();
    

74. 內存泄露問題

  • 靜態集合類: 如 HashMap、Vector 等集合類的靜態使用最容易出現內存泄露,因爲這些靜態變量的生命週期和應用程序一致,所有的對象Object也不能被釋放
  • 各種資源連接包括數據庫連接、網絡連接、IO連接等沒有顯式調用close關閉
  • 監聽器的使用,在釋放對象的同時沒有相應刪除監聽器的時候也可能導致內存泄露

75. ThreadLocal內存泄露?
由於ThreadLocalMap的key是弱引用,而Value是強引用。這就導致了一個問題,ThreadLocal在沒有外部對象強引用時,發生GC時弱引用Key會被回收,而Value不會回收,如果創建ThreadLocal的線程一直持續運行,那麼這個Entry對象中的value就有可能一直得不到回收,發生內存泄露

76. ThreadLocal內存泄漏根源
由於ThreadLocalMap的生命週期跟Thread一樣長,如果沒有手動刪除對應key的value就會導致內存泄漏,而不是因爲弱引用

77. ThreadLocal內存泄露解決辦法
每次使用完ThreadLocal,都調用它的remove()方法,清除數據

78. 線程池調優?

  • 設置最大線程數,防止線程資源耗盡
  • 使用有界隊列,從而增加系統的穩定性和預警能力(飽和策略)
  • 根據任務的性質設置線程池大小:CPU密集型任務(CPU個數個線程),IO密集型任務(CPU個數兩倍的線程),混合型任務(拆分)

HashMap

1. 原理

  • HashMap最多隻允許一條Entry的鍵爲Null(多條會覆蓋),但允許多條Entry的值爲Null
  • 若負載因子越大,那麼對空間的利用更充分,但查找效率的也就越低;若負載因子越小,那麼哈希表的數據將越稀疏,對空間造成的浪費也就越嚴重。系統默認負載因子0.75
  • 調用put方法存值時,HashMap首先會調用Key的hashCode方法,然後基於此獲取Key哈希碼,通過哈希碼快速找到某個桶,這個位置可以被稱之爲bucketIndex.如果兩個對象的hashCode不同,那麼equals一定爲false;否則,如果其hashCode相同,equals也不一定爲 true。所以,理論上,hashCode可能存在碰撞的情況,當碰撞發生時,這時會取出bucketIndex桶內已存儲的元素,並通過hashCode() 和 equals()來逐個比較以判斷Key是否已存在。如果已存在,則使用新Value值替換舊Value值,並返回舊Value值;如果不存在,則存放新的鍵值對<Key, Value>到桶中。因此,在 HashMap中,equals() 方法只有在哈希碼碰撞時纔會被用到
  • jdk1.8中,首先,對key進行hash運算,爲空,hash值爲0,如果table[0]沒有數據,直接插入,如果有數據,則進行數據的更新;若不爲空,則先計算key的hash值,然後和table.length -1 & 運算找到在數組中的索引位置,如果table數組在該位置處有元素,則查找是否存在相同的key,若存在則覆蓋原來key的value,否則將該元素保存在鏈尾。此外,若table在該處沒有元素,則直接保存

2. hash()和indexFor()

static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
    return h & (length-1);  // 作用等價於取模運算,但這種方式效率更高
}
  • hash() 方法用於對Key的hashCode進行重新計算,而 indexFor()方法用於生成這個Entry對象的插入位置。當計算出來的hash值與hashMap的(length-1)做了&運算後,會得到位於區間[0,length-1]的一個值。特別地,這個值分佈的越均勻,HashMap 的空間利用率也就越高,存取效率也就越好,保證元素均勻分佈到table的每個桶中以便充分利用空間
  • hash():使用hash()方法對一個對象的hashCode進行重新計算是爲了防止質量低下的hashCode()函數實現。由於hashMap的支撐數組長度總是2 的冪次,通過右移可以使低位的數據儘量的不同,從而使hash值的分佈儘量均勻
  • indexFor():保證元素均勻分佈到table的每個桶中; 當length爲2的n次方時,h&(length -1)就相當於對length取模,而且速度比直接取模要快得多,這是HashMap在速度上的一個優化

3. 擴容resize()和重哈希transfer()

  • 當已使用75%的容量時會進行擴容,0.75是擴容因子。擴容因子大,則空間利用率高,但容易發生hash地址衝突;擴容因子小,浪費空間
  • 如果e的hash值與老表的容量進行與運算爲0,則擴容後的索引位置跟老表的索引位置相同
  • 如果e的hash值與老表的容量進行與運算爲1,則擴容後的索引位置爲:老表的索引位置+oldCap
  • 爲了保證HashMap的效率,系統必須要在某個臨界點進行擴容處理,該臨界點就是HashMap中元素的數量在數值上等於threshold(table數組長度*加載因子)
  • 重哈希的主要是一個重新計算原HashMap中的元素在新table數組中的位置並進行復制處理的過程

4. HashMap的底層數組長度爲何總是2的n次方

  • 當底層數組的length爲2的n次方時, h&(length - 1) 就相當於對length取模,而且速度比直接取模快得多,這是HashMap在速度上的一個優化
  • 不同的hash值發生碰撞的概率比較小,這樣就會使得數據在table數組中分佈較均勻,也就是說碰撞的機率小,空間利用率較高,查詢速度也較快

5. hashmap負載因子爲什麼是0.75
若加載因子越大,填滿的元素越多,好處是,空間利用率高了,但衝突的機會加大了,鏈表長度會越來越長,查找效率降低。
反之,加載因子越小,填滿的元素越少,好處是:衝突的機會減小了,但:空間浪費多了.表中的數據將過於稀疏(很多空間還沒用,就開始擴容了)

衝突的機會越大,則查找的成本越高。因此,必須在 "衝突的機會"與"空間利用率"之間尋找一種平衡與折衷.

Hashtable

Hashtable的默認容量(數組大小)爲11,默認的負載因子爲0.75

1. put

public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    addEntry(hash, key, value, index);
    return null;
    }
  • hashtable之所以是線程安全的,原因爲在put和get方法上使用synchronized關鍵字進行修飾
  • 限制了value不能爲null
  • 由於直接使用key.hashcode(),而沒有向hashmap一樣先判斷key是否爲null,所以key爲null時,調用key.hashcode()會出錯,所以hashtable中key不能爲null
  • 遍歷table[index]所連接的鏈表,查找是否已經存在key與需要插入的key值相同的節點,如果存在則直接更新value,並返回舊的value
  • 如果table[index]所連接的鏈表上不存在相同的key,則通過addEntry()方法將新節點加載鏈表的開頭

2. Hashtable和hashmap的區別

  • hashmap中key和value均可以爲null,但是hashtable中key和value均不能爲null
  • hashmap採用的是數組(桶位)+鏈表+紅黑樹結構實現,而hashtable中採用的是數組(桶位)+鏈表實現
  • hashmap中數組容量的大小要求是2的n次方,如果初始化時不符合要求會進行調整,而hashtable中數組容量的大小可以爲任意正整數
  • hashmap中的尋址方法採用的是位運算按位與,而hashtable中尋址方式採用的是求餘數
  • hashmap不是線程安全的,而hashtable是線程安全的
  • hashmap中默認容量的大小是16,而hashtable中默認數組容量是11
  • hash衝突時,hashmap尾插法,hashtable頭插法

ConcurrentHashMap

1. put(), get()

  • 不允許key值爲null,也不允許value值爲null
  • HashTable 和由同步包裝器包裝的HashMap每次只能有一個線程執行讀或寫操作,ConcurrentHashMap 在併發訪問性能上有了質的提高。在理想狀態下,ConcurrentHashMap 可以支持 16 個線程執行併發寫操作(如果併發級別設置爲 16),及任意數量線程的讀操作

2. ConcurrentHashMap是如何保證線程安全的
併發控制使用SynchronizedCAS來操作
cas是一種基於鎖的操作,而且是樂觀鎖。在java中鎖分爲樂觀鎖和悲觀鎖。悲觀鎖是將資源鎖住,等一個之前獲得鎖的線程釋放鎖之後,下一個線程纔可以訪問。而樂觀鎖採取了一種寬泛的態度,通過某種方式不加鎖來處理資源,比如通過給記錄加version來獲取數據,性能較悲觀鎖有很大的提高
如果數組沒有值的話,使用封裝的原子操作插入值,如果數組有值需要操作鏈表,使用Synchronized對需要操作的節點Node上鎖

數據庫

MySQL

1. 主鍵,唯一索引區別

  • 主鍵一定會創建一個唯一索引,但是有唯一索引的列不一定是主鍵
  • 主鍵不允許爲空值,唯一索引列允許空值;
  • 一個表只能有一個主鍵,但是可以有多個唯一索引;
  • 主鍵可以被其他表引用爲外鍵,唯一索引列不可以;
  • 主鍵是一種約束,而唯一索引是一種索引,是表的冗餘數據結構,兩者有本質的差別

2. 索引失效

  • 字符串不加單引號
  • 在 where 子句中對字段進行 null 值判斷
  • like查詢以%開頭
  • 對於多列索引,不是使用的第一部分,則不會使用索引
  • where 子句中使用 or 來連接條件
  • 使用mysql內部函數導致索引失效
  • where 子句中的“=”左邊進行函數、算術運算或其他表達式運算

3. Hash索引

  • 不支持範圍查詢,僅支持精確查詢
  • 只支持建索引時使用到字段的查詢
  • 無法運用索引的數據排序
  • 無法利用部分索引鍵查詢
  • 不能避免全表掃描

4. 紅黑樹爲什麼不用在MySQl

  • IO影響,使用紅黑樹會造成過多的磁盤IO,每一次取4k,浪費磁盤空間

5. HashMap爲什麼使用紅黑樹

  • 放在內存中,運算很快

6. drop delete truncate區別

  • DELETE語句執行刪除的過程是每次從表中刪除一行,並且同時將該行的刪除操作作爲事務記錄在日誌中保存以便進行進行回滾操作
  • TRUNCATE TABLE
    則一次性地從表中刪除所有的數據並不把單獨的刪除操作記錄記入日誌保存,刪除行是不能恢復的。並且在刪除的過程中不會激活與表有關的刪除觸發器。執行速度快
  • TRUNCATE 和DELETE只刪除數據, DROP則刪除整個表(結構和數據)

7. 索引種類

  • 密集索引(主鍵索引和數據存放在一起): 主鍵索引
  • 稀疏索引(主鍵和數據分開存放,需要找兩次): 普通索引,唯一索引,組合索引,全文索引

8. 添加索引
ALTER TABLE

-- 1.添加PRIMARY KEY(主鍵索引) 
ALTER TABLE table_name ADD PRIMARY KEY (column) ;
-- 2.添加UNIQUE(唯一索引) 
ALTER TABLE table_name ADD UNIQUE (column);
-- 3.添加INDEX(普通索引) 
ALTER TABLE table_name ADD INDEX index_name (column);
-- 4.添加FULLTEXT(全文索引) 
ALTER TABLE table_name ADD FULLTEXT (column);
-- 5.添加多列索引 
ALTER TABLE table_name ADD INDEX index_name (column1, column2, column3);

CREATE INDEX

CREATE INDEX index_name ON table_name (column_list)
CREATE UNIQUE INDEX index_name ON table_name (column_list)

9. 查看已創建索引

SHOW INDEX FROM table_name;

10. 刪除索引

DROP INDEX index_name ON table_name;ALTER TABLE table_name DROP INDEX index

11. mysql的輔助索引
輔助索引就是B+樹的非葉子節點

12. Innodb和MyISAM

  • Innodb
    • 支持事務
    • 支持行級鎖
    • 默認主鍵索引爲聚密集索引
  • MyISAM
    • 讀多寫少
    • 表級鎖
    • 不支持外鍵
    • myisam在磁盤存儲上有三個文件,每個文件名以表名開頭,擴展名指出文件類型,.frm 用於存儲表的定義,.MYD 用於存放數據,.MYI 用於存放表索引

13. 共享鎖,排他鎖

  • InnoDB普通 select 語句默認不加鎖(快照讀,MYISAM會加鎖),而CUD操作默認加排他鎖
  • MySQL InnoDB存儲引擎,實現的是基於多版本的併發控制協議——MVCC (Multi-Version Concurrency Control) (注:與MVCC相對的,是基於鎖的併發控制,Lock-Based Concurrency Control)。MVCC最大的好處,相信也是耳熟能詳:讀不加鎖,讀寫不衝突
  • 多版本併發控制(MVCC)是一種用來解決讀-寫衝突的無鎖併發控制,也就是爲事務分配單向增長的時間戳,爲每個修改保存一個版本,版本與事務時間戳關聯,讀操作只讀該事務開始前的數據庫的快照。 這樣在讀操作不用阻塞寫操作,寫操作不用阻塞讀操作的同時,避免了髒讀和不可重複讀.
  • 在MVCC併發控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當前讀 (current read)。快照讀,讀取的是記錄的可見版本(有可能是歷史版本),不用加鎖。當前讀,讀取的是記錄的最新版本,並且,當前讀返回的記錄,都會加上鎖,保證其他事務不會再併發修改這條記錄
  • SELECT … LOCK IN SHARE MODE :共享鎖(S鎖,share locks)。其他事務可以讀取數據,但不能對該數據進行修改,直到所有的共享鎖被釋放
  • SELECT … FOR UPDATE:排他鎖(X鎖,exclusive locks)。如果事務對數據加上排他鎖之後,則其他事務不能對該數據加任何的鎖。獲取排他鎖的事務既能讀取數據,也能修改數據。
  • InnoDB默認隔離級別 可重複讀(Repeated Read)
  • 操作字段未加索引(主鍵索引、普通索引等)時,使用表鎖
  • InnoDB行級鎖基於索引實現
  • 索引數據重複率太高會導致全表掃描:當表中索引字段數據重複率太高,則MySQL可能會忽略索引,進行全表掃描,此時使用表鎖。可使用 force index 強制使用索引

14. 悲觀鎖、樂觀鎖

  • 悲觀鎖:
    每次去拿資源的時候都認爲別人會修改,所以每次在拿資源的時候都會上鎖,這樣別人想拿這個資源就會block直到它拿到鎖。傳統的關係型數據庫裏邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖
  • 樂觀鎖:
    每次去拿數據的時候都認爲別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類似於write_condition機制的其實都是提供的樂觀鎖
    使用版本號時,可以在數據初始化時指定一個版本號,每次對數據的更新操作都對版本號執行+1操作。並判斷當前版本號是不是該數據的最新的版本號
    當讀取數據時,將版本標識的值一同讀出,數據每更新一次,同時對版本標識進行更新。當我們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的版本標識進行比對,如果數據庫表當前版本號與第一次取出來的版本標識值相等,則予以更新,否則認爲是過期數據

15. 隔離級別

  • Read Uncommitted(未提交讀): 在該隔離級別,所有事務都可以看到其他未提交事務的執行結果。本隔離級別很少用於實際應用,因爲它的性能也不比其他級別好多少。讀取事務未提交的數據,也被稱之爲髒讀
  • Read Committed(已提交讀): 這是大多數數據庫系統的默認隔離級別(但不是MySQL默認的)。它滿足了隔離的簡單定義:一個事務只能看見已經提交事務所做的改變。這種隔離級別會造成不可重複讀,同一事物讀取的結果可能會不一樣,因爲同一事務的其他實例在該實例處理其間可能會有新的提交修改,偏修改
  • Repeatable Read(可重複讀): MySQL的默認事務隔離級別,它確保同一事務的多個實例在併發讀取數據時,會看到同樣的數據行。不過理論上,這會導致另一個問題:幻讀。簡單的說,幻讀指當用戶讀取某一範圍的數據行時,另一個事務又在該範圍內插入了新行,當用戶再讀取該範圍的數據行時,會發現有新的“幻影” 行,偏插入
  • Serializable(串行化): 這是最高的隔離級別,它通過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。簡言之,它是在每個讀的數據行上加上共享鎖。這個級別,可能導致大量的超時現象和鎖競爭

16. InnoDB和MyISAM的區別?

  • InnoDB支持事務,MyISAM不支持,對於InnoDB每一條SQL語言都默認封裝成事務,自動提交,這樣會影響速度,所以最好把多條SQL語言放在begin和commit之間,組成一個事務
  • InnoDB支持外鍵,而MyISAM不支持。對一個包含外鍵的InnoDB錶轉爲MYISAM會失敗
  • InnoDB是密集索引,數據文件是和索引綁在一起的,必須要有主鍵,通過主鍵索引效率很高。但是輔助索引需要兩次查詢,先查詢到主鍵,然後再通過主鍵查詢到數據。因此,主鍵不應該過大,因爲主鍵太大,其他索引也都會很大。而MyISAM是非聚集索引,數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的
  • InnoDB不保存表的具體行數,執行select count(*) from table時需要全表掃描。而MyISAM用一個變量保存了整個表的行數,執行上述語句時只需要讀出該變量即可,速度很快
  • Innodb不支持全文索引,而MyISAM支持全文索引,查詢效率上MyISAM要高,但是innodb可以使用sphinx插件支持全文索引,並且效果更好

17. 三範式
第一範式:一個關係模式中所有屬性都是不可分的
第二範式:滿足第一範式,且非主屬性完全依賴主鍵
第三範式:滿足第二範式,屬性不依賴於其它非主屬性,消除傳遞依賴

18. ACID
原子性,一個事務中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節
一致性,在一個事務執行之前和執行之後數據庫都必須處於一致性狀態。如果事務成功地完成,那麼系統中所有變化將正確地應用,系統處於有效狀態。如果在事務中出現錯誤,那麼系統中的所有變化將自動地回滾,系統返回到原始狀態
隔離性,一個事務所做的修改在最終提交前,對其他事務是不可見的
持久性,一旦事物提交,則其所做的修改就會永遠保存在數據庫中

19. 數據庫鎖的分類

  1. 鎖的粒度:表級鎖、行級鎖、頁級鎖
  2. 鎖級別:共享鎖、排它鎖
  3. 加鎖方式:自動鎖、顯示鎖
  4. 操作劃分:DML鎖(操作數據:增刪改查)、DDL鎖(變更表結構)
  5. 使用方式:樂觀鎖、悲觀鎖(不光是數據庫,程序中也存在)

20. 當前讀和快照讀

  • 當前讀:select…lock in share mode、select…for update
  • 當前讀:update、insert、delete
  • 快照讀:不加鎖的非阻塞讀,select

21. group by 語法
使用group by,select不能出現使用group by以外的列,除非使用函數,對同一張表成立

select id, count(score) from ... group by id     √
select id, count(score) course_id from ... group by id    ×

group by 裏出現的某個列,select裏要麼是group by裏出現的列,要麼是別的表的列或者帶有函數的列

22. Having

  • 常與group by字句一起使用,用於在group by後指定過濾的條件,如果省略group by,having字句和where功能一樣,語法和where一樣
  • where過濾行,having過濾組
  • 出現在同一sql的順序:where > group by > having
  • 在having中使用count(*)計算的是每個組的個數

23. char和varchar區別

  • CHAR的長度是固定的,而VARCHAR的長度是可以變化的, 比如,存儲字符串“abc",對於CHAR (10),表示你存儲的字符將佔10個字節(包括7個空字符),而同樣的VARCHAR2 (10)則只佔用3個字節的長度,10只是最大值,當你存儲的字符小於10時,按實際長度存儲

Redis

1. Redis的數據結構

  • String、Hash、List、Set、Sorted Set

2. Sorted Set

  • 在有序集中,用於排序的值叫做score,實際存儲的值叫做member

3. Hash底層結構

  • redis的哈希對象的底層存儲可以使用ziplist(壓縮列表)和hashtable

4. 持久化

  • RDB持久化
    是指把當前進程數據生成快照保存到硬盤,實際操作過程是fork一個子進程,先將數據集寫入臨時文件,寫入成功後,再替換之前的文件,用二進制壓縮存儲。觸發RDB持久化過程分爲手動觸發自動觸發
    **優點:**代表Redis在某一個時間點上的數據快照,適合全量同步;恢復數據快於AOF
    **缺點:**無法做到實時持久化/秒級持久化
    犧牲性能,緩存一直
  • AOF持久化
    以日誌的形式記錄服務器所處理的每一個寫、刪除操作,查詢操作不會記錄,以文本的方式記錄。工作流程操作:命令寫入(append)、文件同步(sync)、文件重寫(rewrite)、重啓加載(load)
    **優點:**實時同步
    **缺點:**效率慢,文件大

5. 講一下redis的主從複製怎麼做的?

  • 全量同步: 一般發生在Slave初始化階段,這時Slave需要將Master上的所有數據都複製一份
  • 增量同步: Slave初始化後開始正常工作時主服務器發生的寫操作同步到從服務器的過程。主要是主服務器每執行一個寫命令就會向從服務器發送相同的寫命令,從服務器接收並執行收到的寫命令

6. Redis主從同步策略

  • 主從剛剛連接的時候,進行全量同步;全同步結束後,進行增量同步。當然,如果有需要,slave 在任何時候都可以發起全量同步。redis 策略是,無論如何,首先會嘗試進行增量同步,如不成功,要求從機進行全量同步

7. redis爲什麼讀寫速率快性能好?

  • 存內存操作。數據存在內存中,執行效率高
  • 單線程操作。避免了頻繁的上下文切換
  • 採用非阻塞IO多路複用機制。內部實現採用epoll,採用了epoll+自己實現的簡單的事件框架(事件分離器),內部採用非阻塞的執行方式,吞吐能力比較大。epoll中的讀、寫、關閉、連接都轉化成了事件,然後利用epoll的多路複用特性,絕不在io上浪費一點時間
  • 數據結構簡單。使用特殊數據結構,對數據存儲進行了優化

8. redis爲什麼是單線程?

  • 爲了不讓cpu成爲redis的瓶頸,而單線程方便管理、容易實現,且不用鎖,所以使用單線程,利用隊列技術將併發訪問變爲串行訪問

9. 緩存的優點?

  • 減少數據庫讀操作,降低數據庫壓力;數據從內存中讀取,加快響應速度

10. 緩存雪崩

  • 在某一個時間段,緩存集中過期失效,而查詢數據量巨大,引起數據庫壓力過大甚至宕機;緩存雪崩是不同數據都過期了,很多數據都查不到從而查數據庫
  • 解決方法: 分散過期時間

11. 緩存穿透

  • 概念:不走緩存直接查詢數據庫(查詢一個數據庫一定不存在的數據)
  • eg:傳入的主鍵參數爲-1,會是怎麼樣?這個-1,就是一定不存在的對象。就會每次都去查詢數據庫,而每次查詢都是空,每次又都不會進行緩存。假如有惡意攻擊,就可以利用這個漏洞,對數據庫造成壓力,甚至壓垮數據庫。即便是採用UUID,也是很容易找到一個不存在的KEY,進行攻擊。
  • 解決方案: 對某一個key,緩存空值value,時間設置短一點

12. 緩存擊穿

  • 指緩存中沒有但數據庫中有的數據(一般是緩存時間到期),這時由於併發用戶特別多,同時讀緩存沒讀到數據,又同時去數據庫去取數據,引起數據庫壓力瞬間增大,造成過大壓力;緩存擊穿指併發查同一條數據
  • 解決方案:
    • 獲取數據步驟據加上互斥鎖

13. Redis數據的恢復

Created with Raphaël 2.2.0Start存在AOF?加載AOF啓動成功/失敗End存在RDB?加載RDByesnoyesno

14. 如何通過Redis實現分佈式鎖
set key value [Ex seconds][Px millseconds][NX|XX]
set lock 12345 ex 10 nx

也可以使用SENTX和EXPIRE組合使用,但是這兩個步驟不能保證原子性,不推薦使用

計網

HTTP

1. 說一說四種會話跟蹤技術

  • URL重寫
  • 隱藏表單域
  • cookie
  • session

2. 瀏覽器輸入網址後發生了什麼?

  • 第一步: 在瀏覽器中輸入url後,應用層會使用DNS解析域名,如果本地存有對應的IP,則使用;如果沒有,則會向上級DNS服務器請求幫助,直至獲得IP,應用層將請求的信息裝載入HTTP請求報文,然後應用層將發起HTTP請求。
  • 第二步: 傳輸層接收到應用層傳遞下來的數據,把HTTP會話請求分成報文段,添加源和目的端口,併爲它們編號,方便服務器接收時能準確地還原報文信息。通過三次握手和目標端口建立安全通信。
  • 第三步: 網絡層接收傳輸層傳遞的數據,根據IP通過ARP協議獲得目標計算機物理地址—MAC。當通信的雙方不在同一個局域網時,需要多次中轉才能到達最終的目標,在中轉的過程中需要通過下一個中轉站的MAC地址來搜索下一個中轉目標。
  • **第四步:**找到目標MAC地址以後,就將數據發送到數據鏈路層,這時開始真正的傳輸請求信息,傳輸完成以後請求結束
  • 第五步: 服務器接收數據後,從下到上層層將數據解包,直到應用層
  • 第六步: 服務器接收到客戶端發送的HTTP請求後,查找客戶端請求的資源,將數據裝載入響應報文並返回,響應報文中包括一個重要的信息——狀態碼,如200,404,500

3. 常見狀態碼

狀態碼 英文描述 中文描述
100 繼續
101 協議升級
200 OK 請求成功
301 永久性轉移。該狀態碼錶示請求的資源已經重新分配 URI,以後應該使用資源現有的 URI
302 臨時移動。該狀態碼錶示請求的資源已被分配了新的 URI,希望用戶(本次)能使用新的 URI 訪問
304 請求資源未修改
307 臨時重定向
400 Bad Request 客戶端請求語法錯誤
401 要求認證
403 拒絕請求
404 沒有找到客戶端請求資源
500 服務器內部錯誤
502 Bad Gateway 錯誤網關,代理服務器連接不到服務器
503 服務不可用
504 Gateway Time-out 請求超時,代理服務器請求服務器超時

4. sessionStorage 、localStorage 和 cookie 之間的區別

  • 共同點: 都是保存在瀏覽器端,且同源的
  • 區別:
    HTML5 提供
    • cookie數據始終在同源的http請求中攜帶(即使不需要),即cookie在瀏覽器和服務器間來回傳遞;cookie數據還有路徑(path)的概念,可以限制cookie只屬於某個路徑下。存儲大小限制也不同,cookie數據不能超過4k,同時因爲每次http請求都會攜帶cookie,所以cookie只適合保存很小的數據,如會話標識
    • 而sessionStorage和localStorage不會自動把數據發給服務器,僅在本地保存。sessionStorage和localStorage 雖然也有存儲大小的限制,但比cookie大得多,可以達到5M或更大
    • 數據有效期不同,sessionStorage:僅在當前瀏覽器窗口關閉前有效,自然也就不可能持久保持;localStorage:始終有效,窗口或瀏覽器關閉也一直保存,因此用作持久數據;cookie只在設置的cookie過期時間之前一直有效,即使窗口或瀏覽器關閉
    • 作用域不同,sessionStorage不在不同的瀏覽器窗口中共享,即使是同一個頁面;localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。Web Storage 支持事件通知機制,可以將數據更新的通知發送給監聽者。Web Storage 的 api 接口使用更方便

5. Cookie,Session區別

  • cookie存放在瀏覽器上,session在服務器上,都是存儲訪問者信息
  • cookie容易被截獲不安全,session在服務器安全。cookie容量小,session容量大
  • session會在一定時間內保存在服務器上。當訪問增多,會比較佔用你服務器的性能,考慮到減輕服務器性能方面,應當使用cookie
  • 如果我們需要經常登錄一個站點時,最好用cookie來保存信息 ;如果對於需要安全性高的站點以及控制數據的能力時需要用session

6. TCP三次握手

  • 第一次: 客戶端向服務器發出連接請求報文,這時報文首部中的同部位SYN=1,同時隨機生成初始序列號 seq=x,此時,TCP客戶端進程進入了 SYN-SENT(同步已發送狀態)狀態。TCP規定,SYN報文段(SYN=1的報文段)不能攜帶數據,但需要消耗掉一個序號。這個三次握手中的開始。表示客戶端想要和服務端建立連接。

  • 第二次: TCP服務器收到請求報文後,如果同意連接,則發出確認報文。確認報文中應該 ACK=1,SYN=1,確認號是ack=x+1,同時也要爲自己隨機初始化一個序列號 seq=y,此
    時,TCP服務器進程進入了SYN-RCVD(同步收到)狀態。這個報文也不能攜帶數據,但是同樣要消耗一個序號。這個報文帶有SYN(建立連接)和ACK(確認)標誌,詢問客戶端
    是否準備好。

  • 第三次: 客戶端收到請求,發送確認,將確認信號ACK置爲1,確認號爲y+1,隨機號seq爲x+1。
    TCP客戶進程收到確認後,還要向服務器給出確認。確認報文的確認信號ACK=1,ack=y+1,此時,TCP連接建立,客戶端進入ESTABLISHED(已建立連接)狀態。
    TCP規定,ACK報文段可以攜帶數據,但是如果不攜帶數據則不消耗序號。這裏客戶端表示我已經準備好。

7. TCP四次揮手

  • 第一次揮手:主動關閉方發送一個FIN,用來關閉主動方到被動關閉方的數據傳送,也就是主動關閉方告訴被動關閉方:我已經不 會再給你發數據了(當然,在fin包之前發送出去的數據,如果沒有收到對應的ack確認報文,主動關閉方依然會重發這些數據),但是,此時主動關閉方還可 以接受數據。
  • 第二次揮手:被動關閉方收到FIN包後,發送一個ACK給對方,確認序號爲收到序號+1。
  • 第三次揮手:被動關閉方發送一個FIN,用來關閉被動關閉方到主動關閉方的數據傳送,也就是告訴主動關閉方,我的數據也發送完了,不會再給你發數據了。
  • 第四次揮手:主動關閉方收到FIN後,發送一個ACK給被動關閉方,確認序號爲收到序號+1,至此,完成四次揮手
    8. 爲什麼是三次握手不是兩次握手
  • 防止出現請求超時導致髒連接。如果是兩次,一個超時的連接請求到達服務器,服務器會以爲是客戶端創建的連接請求,然後同意創建連接,而客戶端不是等待確認狀態,丟棄服務器確認數據,導致服務器單方面創建連接。如果是三次,服務器沒有收到客戶端的確認信息,最終超時導致連接創建失敗,因此不會出現髒連接

9. 爲什麼是四次揮手

  • TCP是全雙工通信,服務器和客戶端都可以發送和接受數據,客戶端告訴服務器斷開連接,等待服務器確認,兩次握手,服務器處理完數據後,向客戶端發送斷開連接信號,等待客戶端的確認,四次握手

10. 如果已經建立了連接,但是客戶端突然出現故障了怎麼辦?

  • TCP還設有一個保活計時器,客戶端如果出現故障,服務器不能一直等下去。服務器每收到一次客戶端的請求後都會重新復位這個計時器,時間通常是設置爲2小時,若兩小時還沒有收到客戶端的任何數據,服務器就會發送一個探測報文段,以後每隔75秒鐘發送一次。若一連發送10個探測報文仍然沒反應,服務器就認爲客戶端出了故障,接着就關閉連接。

11. 爲什麼TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?

  • 雖然按道理,四個報文都發送完畢,我們可以直接進入CLOSE狀態了,但是我們必須假象網絡是不可靠的,有可以最後一個ACK丟失。所以TIME_WAIT狀態就是用來重發可能丟失的ACK報文。在Client發送出最後的ACK回覆,但該ACK可能丟失。Server如果沒有收到ACK,將不斷重複發送FIN片段。所以Client不能立即關閉,它必須確認Server接收到了該ACK。Client會在發送出ACK之後進入到TIME_WAIT狀態。Client會設置一個計時器,等待2MSL的時間。如果在該時間內再次收到FIN,那麼Client會重發ACK並再次等待2MSL。所謂的2MSL是兩倍的MSL(Maximum Segment Lifetime)。MSL指一個片段在網絡中最大的存活時間,2MSL就是一個發送和一個回覆所需的最大時間。如果直到2MSL,Client都沒有再次收到FIN,那麼Client推斷ACK已經被成功接收,則結束TCP連接。

12. 單工、半雙工、全雙工

  • 單工:只能有一個方向的通信而沒有反方向的交互
  • 半雙工:通信雙方都可以發送信息,但不能同時發送
  • 全雙工:通信雙方都可以同時發送和接收信息

13. 傳輸層和網絡層的區別

  • 網絡層爲不同的主機提供通信服務,傳輸層爲不同應用進程提供通信服務。
  • 網絡層只對報文頭部進行差錯檢測,而傳輸層對整個報文進行差錯檢測。

14. 端口的作用和理解

  • 在網絡技術中,端口大致有兩種意思:
    一是物理意義上的端口,比如,ADSL Modem、集線器、交換機、路由器用於連接其他網絡設備的接口。
  • 二是邏輯意義上的端口,一般是指TCP/IP協議中的端口,端口號的範圍從0到65535,比如用於瀏覽網頁服務的80端口,用於FTP服務的21端口等等。

主機通過端口來爲外界提供服務,各個不同的應用通過IP + 不同的端口號來區分不同的服務

15. 廣播 單播 組播

  • 單播(unicast):是指封包在計算機網絡的傳輸中,目的地址爲單一目標的一種傳輸方式。它是現今網絡應用最爲廣泛,通常所使用的網絡協議或服務大多采用單播傳輸,例如一切基於TCP的協議。
  • 組播(multicast):也叫多播,多點廣播或羣播。指把信息同時傳遞給一組目的地址。它使用策略是最高效的,因爲消息在每條網絡鏈路上只需傳遞一次,而且只有在鏈路分叉的時候,消息纔會被複制。
  • 廣播(broadcast):是指封包在計算機網絡中傳輸時,目的地址爲網絡中所有設備的一種傳輸方式。實際上,這裏所說的“所有設備”也是限定在一個範圍之中,稱爲“廣播域”。

16. UDP(User Data Protocol)用戶數據報協議

  • 無連接
  • 不可靠(不能保證都送達)
  • 面向報文(UDP數據傳輸單位是報文,不會對數據進行拆分和拼接操作,只是給上層傳來的數據加個UDP頭或者給下層來的數據去掉UDP頭)
  • 沒有擁塞控制,始終以恆定速率發送數據
  • 支持一對一、一對多、多對多、多對一
  • 數據包報文首部開銷小,只有8字節

17. TCP(Transmission Control Protocol)傳輸控制協議

  • 有連接
  • 可靠的
  • 面向字節
  • 全雙工通信,TCP兩端既可以作爲發送端也可以作爲接收端
  • 連接的兩端只能是兩個端點,即一對一,不能一對多
  • 至少20個字節,比UDP大的多

18. TCP/IP每一層對應的協議

  • 應用層:FTP(文件傳送協議)、Telnet(遠程登錄協議)、DNS(域名解析協議)、SMTP(郵件傳送協議)、POP3(郵局協議)、HTTP、NFS
  • 傳輸層:UDP、TCP
  • 網絡層:IP、ICMP、ARP、RARP、OSFP
  • 數據鏈路層:PPP、FR、HDLC、VLAN、MAC
  • 物理層:RJ45、CLOCK、IEEE802.3

19. TCP/UDP對應協議

  • TCP對應協議:FTP(文件傳輸協議)、Telnet(遠程登錄)、SMTP(發送郵件)、POP3(接收郵件)、HTTP
  • UDP對應協議:DNS(域名解析服務)、SNMP(簡單網絡管理協議)、TFTP(簡單文件傳輸協議)

20. 面向連接和非面向連接的服務的特點是什麼?

  • 面向連接的服務,通信雙方在進行通信之前,要先在雙方建立起一個完整的可以彼此溝通的通道,在通信過程中,整個連接的情況一直可以被實時地監控和管理。
  • 非面向連接的服務,不需要預先建立一個聯絡兩個通信節點的連接,需要通信的時候,發送節點就可以往網絡上發送信息,讓信息自主地在網絡上去傳,一般在傳輸的過程中不再加以監控。

21. ARP協議工作

  • (1)首先,每個主機都會在自己的ARP緩衝區中建立一個ARP列表,以表示P地址和MAC地址之間的對應關係
  • (2)當源主機要發送數據時,首先檢查ARP列表中是否有對應P地址的目的主機的MAC地址,如果有,則直接發送數據,如果沒有,就向本網段的所有主機發送ARP數據包,該數據包包括的內容有:源主機P地址,源主機MAC地址,目的主機的P地址
  • (3)當本網絡的所有主機收到該ARP數據包時,首先檢査數據包中的P地址是否是的P地址,如果不是,則忽略該數據包,如果是,則首先從數據包中取出源主機的P和MAC地址寫入到ARP列表中,如果已經存在,則覆蓋,然後將自己的MAC地址寫入ARP響應包中,告訴源主機自己是它想要找的MAC地址
  • (4)源主機收到ARP響應包後。將目的主機的P和MAC地址寫入ARP列表,並利用此信息發送數據。如果源主機一直沒有收到ARP響應數據包,表示ARP査詢失敗廣播發送ARP請求,單播發送ARP響應

22. 進程和線程的區別:
線程是指進程內的一個執行單元,也是進程內的可調度實體,與進程的區別:

  • (1)調度線程作爲調度和分配的基本單位,進程作爲擁有資源的基本單位。
  • (2)併發性:不僅進程之間可以併發執行,同一個進程的多個線程之間也可併發執行。
  • (3)擁有資源:進程是擁有資源的一個獨立單位,線程不擁有系統資源,但可以訪問隸屬於進程的資源
  • (4)系統開銷在創建或撤消進程時,由於系統都要爲之分配和回收資源,導致系統的開銷明顯大於創建或撤消線程時的開銷

23. 進程間通信方式

  • 管道、信號、信號量、socket、共享內存、消息隊列,信號是使用信號處理器進行、信號量使用P、V操作

24. 進程進入等待狀態(就緒狀態)有哪幾種方式?

  • CPU調度給優先級更高的 Thread(線程),原先Thread進入Waiting(等待)狀態。阻寨的Thread獲得資源或者信號,進入Waiting狀態。在時間片輪轉的情況下,如果時間片到了,也將進入等待狀態。

25. GET、POST區別

  • Http報文層面:GET請求將請求信息放在URL,POST放在報文體中,GET請求本身沒有url長度限制,但瀏覽器有,所以有數據大小限制,而post沒有
  • 數據庫層面:GET符合冪等性和安全性(同樣請求結果一樣,不會改變數據),POST不符合
  • 其他層面:GET請求可以被緩存、存儲,GET請求可以保存在瀏覽器的瀏覽記錄中後者保存爲書籤,POST不行

26. TCP、UDP區別

  • TCP是面向連接的,UDP是無連接的
  • TCP是可靠的,UDP是不可靠的
  • TCP只支持點對點通信,UDP支持一對一、一對多、多對一、多對多的通信模式
  • TCP是面向字節流的,UDP是面向報文的
  • TCP有擁塞控制機制;UDP沒有擁塞控制,適合媒體通信
  • TCP首部開銷(20個字節)比UDP的首部開銷(8個字節)要大

27. TCP爲什麼可靠

  • 確認和重傳機制: 確認丟失、確認遲到、超時重傳(停止等待協議)
  • 校驗和: TCP 將保持它首部和數據的檢驗和。這是一個端到端的檢驗和,目的是檢測數據在傳輸過程中的任何變化。如果收到段的檢驗和有差錯,TCP 將丟棄這個報文段和不確認收到此報文段
  • 流量控制: TCP 連接的每一方都有固定大小的緩衝空間,TCP的接收端只允許發送端發送接收端緩衝區能接納的數據。當接收方來不及處理髮送方的數據,能提示發送方降低發送的速率,防止包丟失。TCP 使用的流量控制協議是可變大小的滑動窗口協議。 (TCP 利用滑動窗口實現流量控制和亂序重排)
  • 擁塞控制: 當網絡擁塞時,減少數據的發送
  • 有序: 未按序到達的數據確認會重新發送

28. forward和redirect區別

  • 請求轉發(Forward),客戶端器只發出一次請求,Servlet、HTML、JSP或其它信息資源,由第二個信息資源響應該請求,在請求對象request中,轉發頁面和轉發到的頁面可以共享request裏面的數據

    是服務器內部的動作,服務器直接轉到其他url,使用其中的資源,這個過程客戶端是不可見的,客戶端請求的url不會發生變化。過程:客戶端發起http請求-->服務器接受http請求--->服務器內部調用方法完成請求處理和轉發--->將請求的資源發送給客戶端。轉發時,服務器只能轉發到同一web容器下的url,中間傳遞的自己容器的request

  • 請求重定向(Redirect) 實際是兩次HTTP請求,服務器端在響應第一次請求的時候,讓瀏覽器再向另外一個URL發出請求,從而達到轉發的目的,狀態碼302

    是客戶端的操作,服務器返回目標地址給客戶端,客戶端去訪問,那麼客戶端一開始的請求url就會發生變化。過程:客戶端發起http請求-->服務器接受http請求返回302狀態碼--->客戶端看見是302狀態碼,重新發起http請求--->服務器處理請求給客戶端響應

29. OSI五層

  • 應用層-HTTP
  • (表示層)
  • (會話層)
  • 傳輸層-TCP/UDP
  • 網絡層-IP
  • 數據鏈路層
  • 物理層

HTTP2

  • 二進制傳送。 使用的是二進制傳送,二進制傳送的單位是幀和流。幀組成了流,同時流還有流ID標示
  • 多路複用。有流ID,所以通過同一個http請求實現多個http請求傳輸變成了可能,可以通過流ID來標示究竟是哪個流從而定位到是哪個http請求
  • 頭部壓縮。 通過gzip和compress壓縮頭部然後再發送,同時客戶端和服務器端同時維護一張頭信息表,所有字段都記錄在這張表中,這樣後面每次傳輸只需要傳輸表裏面的索引Id就行,通過索引ID就可以知道表頭的值了
  • 服務器推送。 在客戶端未經請求許可的情況下,主動向客戶端推送內容

簡述http1與http2的特點與各自的優劣
http1:
文本傳輸
長連接(HTTP/1.1)
管道機制:同一個TCP連接中,客戶端可以同時發送多個請求

缺點:1.1版允許複用TCP連接,但是同一個TCP連接裏面,所有的數據通信是按次序進行的。服務器只有處理完一個迴應,纔會進行下一個迴應。要是前面的迴應特別慢,後面就會有許多請求排隊等着
優點:採用文本傳輸,實現簡單

http2:
二進制傳輸: 和http1傳輸文本不同,http2使用的是二進制傳送,二進制傳送的單位是幀和流,幀組成了流,流用流ID標示,二進制協議解析更高效,錯誤更少
多路複用: 多個http請求使用可以使用同一個http連接,通過流ID來標示究竟是哪個流從而定位到是哪個http請求,這樣就不用每次請求都去建立http連接,提高了請求的效率,在一個連接裏,客戶端和瀏覽器都可以同時發送多個請求或迴應,而且不用按照順序一一對應
頭部壓縮:減少每次請求的請求頭大小,提升了性能,節約開銷
服務器推送: 支持在客戶端未經請求許可的情況下,主動向客戶端推送內容,可以不需要客戶端請求就將資源發送給客戶端,減少請求次數,節約時間

缺點:改動http應用層,而TCP傳輸層已經被廣泛應用,改動代價太大

HTTPS

設計模式

所謂設計模式,就是一套被反覆使用的代碼設計經驗的總結(情境中一個問題經過證實的一個解決方案)。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。設計模式使人們可以更加簡單方便的複用成功的設計和體系結構。將已證實的技術表述成設計模式也會使新系統開發者更加容易理解其設計思路
創建型模式(5種)
1. 單例模式
懶漢式

public class Singleton {
    private static Singleton instance; 

    //構造函數私有
    private Singleton (){
    }  

    public static synchronized Singleton getInstance() { 
         if (instance == null) {  
             instance = new Singleton();  
         }  
         return instance;  
    }  
}

餓漢式

public class HungrySingleton {
    private static final HungrySingleton mHungrySingleton = new HungrySingleton();

    private HungrySingleton() {
        System.out.println("Singleton is create");
    }

    public static HungrySingleton getHungrySingleton() {
        return mHungrySingleton;
    }
}

DCL

public class Singleton {
    private volatile static Singleton singleton;  

    private Singleton (){
    }  

    public static Singleton getSingleton() {  

        if (singleton == null) {  
            synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
          }  
        }  
        return singleton;  
    }  
}
  • 第一個if (instance == null),只有instance爲null的時候,才進入synchronized 第二個if
    (instance == null),是爲了防止可能出現多個實例的情況
  • volatile: 主要在於singleton = new Singleton()這句,這並非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情。
      1. 給 singleton 分配內存
      2. 調用 Singleton 的構造函數來初始化成員變量,形成實例
      3. 將singleton對象指向分配的內存空間(執行完這步 singleton纔是非 null 了)但是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序可能是 1-2-3 也可能是 1-3-2。如果是後者,則在 3 執行完畢、2 未執行之前,被線程二搶佔了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然後使用,然後順理成章地報錯

靜態內部類

public class Singleton {  

    private Singleton (){
    }  

    public static final Singleton getInstance() {  
          return SingletonHolder.INSTANCE;  
    }  

    private static class SingletonHolder {  
         private static final Singleton INSTANCE = new Singleton();  
    }
}   

枚舉類

public enum  EnumSingleton {

    //定義一個枚舉的元素,它就是Singleton的一個實例
    INSTANCE;

    public void doSomething() {
    }
}

2. 工廠方法模式
3. 抽象工廠模式
4. 建造者模式

結構型模式(7種)
1. 適配器模式
3. 裝飾者模式
Java IO流的設計

行爲型模式 ( 11種 )
4. 觀察者模式
7. 解釋器模式
9. 策略模式

Spring

1. Spring兩大特性

  • IOC控制反轉
  • AOP

2. Spring是如何啓動的?

  • 加載配置文件,容器在初始化時,contextLoaderListener會監聽到這個事件,其contextInitialized方法會被調用,在這個方法中,spring會初始化一個啓動上下文WebApplicationContext,再次,contextLoaderListener監聽器初始化完畢後,開始初始化web.xml中配置的Servlet,這裏是DispatcherServlet,這個servlet實際上是一個標準的前端控制器,用以轉發、匹配、處理每個servlet請求

3. Spring七大模塊

  • 核心容器(Spring core)
    核心容器提供Spring框架的基本功能。Spring以bean的方式組織和管理Java應用中的各個組件及其關係。Spring使用BeanFactory來產生和管理Bean,它是工廠模式的實現。BeanFactory使用控制反轉(IoC)模式將應用的配置和依賴性規範與實際的應用程序代碼分開。BeanFactory使用依賴注入的方式提供給組件依賴
  • Spring上下文(Spring context)
    Spring上下文是一個配置文件,向Spring框架提供上下文信息。Spring上下文包括企業服務,如JNDI、EJB、電子郵件、國際化、校驗和調度功能
  • Spring面向切面編程(Spring AOP)
  • Spring DAO模塊
    DAO模式主要目的是將持久層相關問題與一般的的業務規則和工作流隔離開來。Spring 中的DAO提供一致的方式訪問數據庫,不管採用何種持久化技術,Spring都提供一直的編程模型。Spring還對不同的持久層技術提供一致的DAO方式的異常層次結構
  • Spring ORM模塊
    Spring 與所有的主要的ORM映射框架都集成的很好,包括Hibernate、JDO實現、TopLink和IBatis SQL Map等。Spring爲所有的這些框架提供了模板之類的輔助類,達成了一致的編程風格
  • Spring Web模塊
    Web上下文模塊建立在應用程序上下文模塊之上,爲基於Web的應用程序提供了上下文。Web層使用Web層框架,可選的,可以是Spring自己的MVC框架,或者提供的Web框架,如Struts、Webwork、tapestry和jsf
  • Spring MVC框架(Spring WebMVC)
    MVC框架是一個全功能的構建Web應用程序的MVC實現。通過策略接口,MVC框架變成爲高度可配置的。Spring的MVC框架提供清晰的角色劃分:控制器、驗證器、命令對象、表單對象和模型對象、分發器、處理器映射和視圖解析器。Spring支持多種視圖技術

4. Spring三大組件

  • Bean組件
    組件作用:Bean組件在Spring中的Beans包下,爲了解決三件事。Bean的創建,Bean的定義,Bean的解析。最關心的就是Bean的創建
    ①Bean的創建:
    工廠模式的實現,頂層接口是:BeanFactory
    雖然最終實現類是DefaultListableBeanFactory,但是其上層接口都是爲了區分在Spring內部對象的傳遞和轉換的過程,對對象的數據訪問所做的限制。
    ListableBeanFactory:可列表的
    HierarchicalBeanFactory:可繼承的
    AutowriteCapableBeanFactory:可自動裝配的
    這四個接口,共同定義了Bean的集合,Bean之間的關係,Bean的行爲
    ②Bean的定義
    Bean的定義完整的描述在Spring配置文件中節點中,包括子節點等
    在Spring內部它被轉換成BeanDefinition對象,後續操作都是對這個對象操作
    主要是BeanDefinition來描述
    ③Bean的解析
    BeanDefinitionReader
    Bean的解析就是對Spring配置文件以及對Tag的解析

  • Context組件
    組件作用: 在Spring中的context包下,爲Spring提供運行環境,用以保存各個對象狀態
    Context作爲Spring的IOC容器,整合了大部分功能或說大部分功能的基礎,完成了以下幾件事:
      1、標識一個應用環境
      2、利用BeanFactory創建Bean對象
      3、保存對象關係表
      4、能夠捕獲各種事件
    ApplicationContext是context的頂級父類,除了能標識一個應用的基本信息外,還繼承了五個接口,擴展了Context的功能。並且繼承了BeanFactory,說明Spring容器中運行的主體對象是Bean,另外還繼承了ResourceLoader,可以讓ApplicationContext可以訪問任何外部資源

  • Core組件
    訪問資源
    1、它包含了很多關鍵類,一個重要的組成部分就是定義的資源的訪問方式,這種把所有資源都抽象成了一個接口的方式很值得學習。
    2、Resource接口封裝了各種可能的資源類型,繼承了InputStreamSource接口。
    加載資源的問題,也就是資源加載者的統一,由ResourceLoader接口來完成。
    默認實現是:DefaultResourceLoader

5. spring 的 controller 是單例還是多例,怎麼保證併發的安全

  • Spring 多線程請求過來調用的Controller對象都是一個,而不是一個請求過來就創建一個Controller對象

  • @Scope(“prototype”),這樣每次請求調用的類都是重新生成的(每次生成會影響效率)
    使用ThreadLocal來保存類變量

6. Spring 框架中都用到了哪些設計模式?

  • 代理模式—在AOP和remoting中被用的比較多
  • 單例模式—在spring配置文件中定義的bean默認爲單例模式
  • 模板方法—用來解決代碼重複的問題。eg:RestTemplate、 JmsTemplate、JpaTemplate
  • 工廠模式—BeanFactory用來創建對象的實例

7. ApplicationContext Bean生命週期

  • 1. 對scope爲singleton且非懶加載的bean進行實例化,按照配置信息設置屬性
  • 2. 如果Bean實現了Aware接口,注入BeanID、BeanFactory、ApplicationContext(在Bean中操作容器,例如得到實例名)
  • 3. 如果實現BeanPostProcessor,執行則會回調該接口的postProcessBeforeInitialzation前置方法(在實例化完成後,對Bean添加自定義處理邏輯)
  • 4. 如果實現InitializingBean接口,執行afterPropertiesSet()方法(實現接口執行方法,做一些屬性自定義的事)
  • 5. 如果Bean配置了init-method方法,則會執行init-method配置的方法(調用Bean自身的初始化方法)
  • 6. 執行如果實現BeanPostProcessor接口的postProcessAfterInitialization()後置方法(Bean實例化之後的工作)
  • 7. 初始化完成
  • 8. 如果Bean實現了DisposableBean接口,則會回調該接口的destroy()方法
  • 9. 如果Bean配置了destroy-method屬性,則會執行destroy-method配置的方法,至此,整個Bean的生命週期結束

8. Spring的BeanFactory和ApplicationContext的區別?

  • ApplicationContext是實現類,繼承ListableBeanFactory(繼承BeanFactory),功能更多
    ApplicationContext默認立即加載,BeanFactory延遲加載
  • beanFactory主要是面對與 spring 框架的基礎設施,面對 spring 自己。而 Applicationcontex 主要面對與 spring 使用的開發者

9. Spring IOC 怎麼注入類,怎麼實例化對象
實例化

  1. Spring IoC容器需要根據Bean定義裏的配置元數據使用反射機制來創建Bean
  2. 使用構造器實例化Bean 有參/無參;使用靜態工廠實例化Bean;使用實例工廠實例化Bean
  3. 使用@Autowire註解注入的時機則是容器剛啓動的時候就開始注入;注入之前要先初始化bean;ApplicationContext 的初始化和BeanFactory 有一個重大的區別:BeanFactory在初始化容器時,並未實例化Bean,直到第一次訪問某個Bean 時才實例化目標Bean;而ApplicationContext 則在初始化應用上下文時就實例化所有單實例的Bean

10. 談談Spring MVC的工作原理是怎樣的?

  1. 用戶發送請求至前端控制器DispatcherServlet
  2. DispatcherServlet接收到客戶端請求,找到對應的處理器映射器HandlerMapping,根據映射規則,找到對應的處理器(Handler)
  3. DispatcherServlet調用HandlerAdapter處理器適配器執行Handler的處理方法,執行完成會返回一個ModelAndView
  4. DispatcherServlet根據得到的ModelAndView中的視圖對象,找到一個合適的ViewResolver(視圖解析器),根據視圖解析器的配置,DispatcherServlet將要顯示的數據傳給對應的視圖,最後顯示給用戶

11. Spring注入

  • 接口注入、setter注入、構造器注入

12. Spring bean 範圍

  • singleton 單例模式,在整個Spring IoC容器中,使用singleton定義的Bean將只有一個實例
  • prototype IOC容器可以創建多個Bean實例,每次返回的都是一個新的實例
  • request 該屬性僅對HTTP請求產生作用,每次HTTP請求都將產生一個新實例。只有在Web應用中使用Spring時,該作用域纔有效
  • session 同一個Session共享一個Bean實例。不同Session使用不同的實例
  • global session 所有的Session共享一個Bean實例

13. 談談Spring中自動裝配的方式有哪些?

  • no: 不進行自動裝配,手動設置Bean的依賴關係
  • byName: 根據Bean的名字進行自動裝配
  • byType: 根據Bean的類型進行自動裝配
  • constructor: 類似於byType,不過是應用於構造器的參數,如果正好有一個Bean與構造器的參數類型相同則可以自動裝配,否則會導致錯誤
  • autodetect: 如果有默認的構造器,則通過constructor的方式進行自動裝配,否則使用byType的方式進行自動裝配【在Spring3.0以後的版本被廢棄,已經不再合法了】

14. aop的應用場景?

  • 日誌
  • 數據源切換
  • 事務

15. AOP的原理是什麼?

  • AspectJ:靜態代理,即用一種AspectJ支持的特定語言編寫切面,通過一個命令來編譯,生成一個新的代理類,該代理類增強了業務類,這是在編譯時增強,相對於下面說的運行時增強,編譯時增強的性能更好
  • Spring AOP:動態代理,在運行期間對業務方法進行增強,所以不會生成新類,對於動態代理技術,Spring AOP提供了對JDK動態代理的支持以及CGLib的支持

16. JDK動態代理只能爲接口創建動態代理實例,而不能對類創建動態代理

  • 需要獲得被代理目標類的接口信息(應用Java的反射技術),生成一個實現了代理接口的動態代理類(字節碼),再通過反射機制獲得動態代理類的構造函數,利用構造函數生成動態代理類的實例對象,在調用具體方法前調用InvokeHandler方法來處理

17. 你如何理解AOP中的連接點(Joinpoint)、切點(Pointcut)、增強(Advice)、織入(Weaving)、切面(Aspect)這些概念?

  1. 連接點(Joinpoint):可以作爲被切入點的機會,所有方法都可以作爲切入點。Spring僅支持方法的連接點 eg:hi();
  2. 切點(Pointcut):讓連接點能被spring框架找到並且,正則表達式
  3. 增強(Advice):類裏的方法以及這個方法如何織入到目標方法的方式(註解 + 方法),Aspect是類,Advice是類裏的方法
  4. 織入(Weaving):Aop的實現過程
  5. 切面(Aspect):通用功能的代碼實現,切面類

18. Spring支持的事務管理類型有哪些?你在項目中使用哪種方式?

  • 編程式事務: 在代碼中顯式調用開啓事務、提交事務、回滾事務的相關方法,基於 <tx><aop> 命名空間的聲明式事務管理是目前推薦的方式,其最大特點是與 Spring AOP 結合緊密,可以充分利用切點表達式的強大支持,使得管理事務更加靈活
  • 聲明式事務: 底層是建立在 AOP 的基礎之上。其本質是對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務,基於 @Transactional 的方式將聲明式事務管理簡化到了極致

19. Servlet的生命週期
= 分爲init,service,destory三個流程,當第一個請求/服務器啓動時init,然後會給所有進來的請求分配一個線程,執行doGet\doPost方法,最後執行destory,被GC回收

MyBatis

1. #{}和${}的區別是什麼?

  • #{}是預編譯處理,提前對SQL語句進行預編譯,而其後注入的參數將不會再進行SQL編譯,mybatis在處理#{}時,會將sql中的#{}替換爲?號,調用PreparedStatement的set方法來賦值,最後注入進去是帶引號的,${}是字符串替換,mybatis在處理${}時,就是把${}替換成變量的值
  • 使用#{}可以有效的防止SQL注入,提高系統安全性

2. 當實體類中的屬性名和表中的字段名不一樣 ,怎麼辦 ?
1.通過在查詢的sql語句中定義字段名的別名,讓字段名的別名和實體類的屬性名一致

<select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”> 
       select order_id id, order_no orderno ,order_price price form orders where order_id=#{id}; 
</select>

2.通過<resultMap>來映射字段名和實體類屬性名的一一對應的關係

<select id="getOrder" parameterType="int" resultMap="orderresultmap">
     select * from orders where order_id=#{id}
</select>
<resultMap type=”me.gacl.domain.order” id=”orderresultmap”> 
     <!–用id屬性來映射主鍵字段–> 
     <id property=”id” column=”order_id”> 
     <!–用result屬性來映射非主鍵字段,property爲實體類屬性名,column爲數據表中的屬性–> 
     <result property = “orderno” column =”order_no”/> 
     <result property=”price” column=”order_price” /> 
 </reslutMap>

3. 如何獲取自動生成的(主)鍵值?
通過LAST_INSERT_ID()獲取剛插入記錄的自增主鍵值,在insert語句執行後,執行select LAST_INSERT_ID()就可以獲取自增主鍵

<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
        <selectKey keyProperty="id" order="AFTER" resultType="int">
            select LAST_INSERT_ID()
        </selectKey>
        INSERT INTO USER(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})
</insert>

4. 在mapper中如何傳遞多個參數?
1.使用佔位符的思想
#{0}#{1}方式

//對應的xml,#{0}代表接收的是dao層中的第一個參數,#{1}代表dao層中第二參數,更多參數一致往後加即可。

<select id="selectUser"resultMap="BaseResultMap">  
    select *  fromuser_user_t   whereuser_name = #{0} anduser_area=#{1}  
</select>  

@param註解方式

public interface usermapper { 
    user selectuser(@param(“username”) string username, @param(“hashedpassword”) string hashedpassword); 
}

<select id=”selectuser” resulttype=”user”> 
     select id, username, hashedpassword 
     from some_table 
     where username = #{username} 
     and hashedpassword = #{hashedpassword} 
</select>

2.使用Map集合作爲參數來裝載

<select id=”selectuser” resulttype=”user”> 
     select id, username, hashedpassword 
     from some_table 
     where username = #{username} 
     and hashedpassword = #{hashedpassword} 
</select>

<!--分頁查詢-->
<select id="pagination" parameterType="map" resultMap="studentMap">

    /*根據key自動找到對應Map集合的value*/
    select * from students limit #{start},#{end};
</select>

5. mybatis緩存機制
mybatis提供了緩存機制減輕數據庫壓力,提高數據庫性能
緩存分爲兩級:一級緩存、二級緩存

  • 一級緩存:
    一級緩存是SqlSession級別的緩存。在操作數據庫時需要構造 sqlSession對象,在對象中有一個(內存區域)數據結構(HashMap)用於存儲緩存數據。不同的sqlSession之間的緩存數據區域(HashMap)是互相不影響的
    一級緩存的作用域是同一個SqlSession,在同一個sqlSession中兩次執行相同的sql語句,第一次執行完畢會將數據庫中查詢的數據寫到緩存(內存),第二次會從緩存中獲取數據將不再從數據庫查詢,從而提高查詢效率。當一個sqlSession結束後該sqlSession中的一級緩存也就不存在了。Mybatis默認開啓一級緩存
      mybatis的一級緩存是SqlSession級別的緩存,在操作數據庫的時候需要先創建SqlSession會話對象,在對象中有一個HashMap用於存儲緩存數據,此HashMap是當前會話對象私有的,別的SqlSession會話對象無法訪問

  • 二級緩存:
    二級緩存是mapper級別的緩存,多個SqlSession去操作同一個Mapper的sql語句,多個SqlSession去操作數據庫得到數據會存在二級緩存區域,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。
    二級緩存是多個SqlSession共享的,其作用域是mapper的同一個namespace,不同的sqlSession兩次執行相同namespace下的sql語句且向sql中傳遞參數也相同即最終執行相同的sql語句,第一次執行完畢會將數據庫中查詢的數據寫到緩存(內存),第二次會從緩存中獲取數據將不再從數據庫查詢,從而提高查詢效率
      二級緩存是mapper級別的緩存,也就是同一個namespace的mappe.xml,當多個SqlSession使用同一個Mapper操作數據庫的時候,得到的數據會緩存在同一個二級緩存區域
      
    二級緩存默認是沒有開啓的。需要在setting全局參數中配置開啓二級緩存

    conf.xml:

    <settings>
            <setting name="cacheEnabled" value="true"/>默認是false:關閉二級緩存
    <settings>
    

    在userMapper.xml中配置:

    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>當前mapper下所有語句開啓二級緩存
    

    若想禁用當前select語句的二級緩存,添加useCache="false"修改如下:

    <select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">
    

6. Mybatis動態sql是做什麼的?都有哪些動態sql?能簡述一下動態sql的執行原理不?

  • Mybatis動態sql可以讓我們在Xml映射文件內,以標籤的形式編寫動態sql,完成邏輯判斷和動態拼接sql的功能
  • Mybatis提供了9種動態sql標籤:trimwheresetforeachifchoosewhenotherwisebind
  • 其執行原理爲,使用OGNL從sql參數對象中計算表達式的值,根據表達式的值動態拼接sql,以此來完成動態sql的功能

7. Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重複?

  • 如果配置了namespace是可以重複的,因爲Statement實際上就是namespace + id
  • 如果沒有配置namespace的話,那麼相同的id就會導致覆蓋了

8. 爲什麼說Mybatis是半自動ORM映射工具?它與全自動的區別在哪裏?

  • Hibernate屬於全自動ORM映射工具,使用Hibernate查詢關聯對象或者關聯集合對象時,可以根據對象關係模型直接獲取,所以它是全自動的
  • 而Mybatis在查詢關聯對象或關聯集合對象時,需要手動編寫sql來完成,所以,稱之爲半自動ORM映射工具

9. 通常一個Xml映射文件,都會寫一個Dao接口與之對應,請問,這個Dao接口的工作原理是什麼?Dao接口裏的方法,參數不同時,方法能重載嗎?

  • Dao接口,就是人們常說的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法內的參數,就是傳遞給sql的參數

  • Mapper接口是沒有實現類的,當調用接口方法時,接口全限名+方法名拼接字符串作爲key值,可唯一定位一個MappedStatement

    com.mybatis3.mappers.StudentDao.findStudentById
    
    可以唯一找到namespace爲com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一個<select><insert><update><delete>標籤,都會被解析爲一個MappedStatement對象。
    
  • Dao接口裏的方法,是不能重載的,因爲是全限名+方法名的保存和尋找策略

  • Dao接口的工作原理是JDK動態代理,Mybatis運行時會使用JDK動態代理爲接口生成代理對象,代理對象proxy會攔截接口方法,轉而執行MappedStatement所代表的sql,然後將sql執行結果返回

10. 接口綁定有幾種實現方式,分別是怎麼實現的?

  • 一種是通過註解綁定,就是在接口的方法上面加上@Select@Update等註解裏面包含Sql語句來綁定
  • 另外一種就是通過xml裏面寫SQL來綁定,在這種情況下,要指定 xml映射文件裏面的namespace必須爲接口的全路徑名

11. Mybatis是如何進行分頁的?分頁插件的原理是什麼?
Mybatis使用RowBounds對象進行分頁,它是針對ResultSet結果集執行的內存分頁,而非物理分頁,可以在sql內直接書寫帶有物理分頁的參數來完成物理分頁功能,也可以使用分頁插件來完成物理分頁
分頁插件的基本原理是使用Mybatis提供的插件接口,實現自定義插件,在插件的攔截方法內攔截待執行的sql,然後重寫sql,根據dialect方言,添加對應的物理分頁語句和物理分頁參數
select * from student,攔截sql後重寫爲:select t.* from (select * from student)t limit 0,10

12. Mybatis是否支持延遲加載?如果支持,它的實現原理是什麼?

  • Mybatis僅支持association關聯對象和collection關聯集合對象的延遲加載,association指的就是一對一,collection指的就是一對多查詢。在Mybatis配置文件中,可以配置是否啓用延遲加載lazyLoadingEnabled=true|false
  • 它的原理是,使用CGLIB創建目標對象的代理對象,當調用目標方法時,進入攔截器方法,比如調用a.getB().getName(),攔截器invoke()方法發現a.getB()是null值,那麼就會單獨發送事先保存好的查詢關聯B對象的sql,把B查詢上來,然後調用a.setB(b),於是a的對象b屬性就有值了,接着完成a.getB().getName()方法的調用。這就是延遲加載的基本原理。
  • 當然了,不光是Mybatis,幾乎所有的包括Hibernate,支持延遲加載的原理都是一樣的

13. MyBatis與Hibernate有哪些不同?

  • Mybatis和hibernate不同,它不完全是一個ORM框架,因爲MyBatis需要程序員自己編寫Sql語句,不過mybatis可以通過XML或註解方式靈活配置要運行的sql語句,並將java對象和sql語句映射生成最終執行的sql,最後將sql執行的結果再映射生成java對象。

  • Mybatis學習門檻低,簡單易學,程序員直接編寫原生態sql,可嚴格控制sql執行性能,靈活度高,非常適合對關係數據模型要求不高的軟件開發,例如互聯網軟件、企業運營類軟件等,因爲這類軟件需求變化頻繁,一但需求變化要求成果輸出迅速。但是靈活的前提是mybatis無法做到數據庫無關性,如果需要實現支持多種數據庫的軟件則需要自定義多套sql映射文件,工作量大

  • Hibernate對象/關係映射能力強,數據庫無關性好,對於關係模型要求高的軟件(例如需求固定的定製化軟件)如果用hibernate開發可以節省很多代碼,提高效率。但是Hibernate的缺點是學習門檻高,要精通門檻更高,而且怎麼設計O/R映射,在性能和對象模型之間如何權衡,以及怎樣用好Hibernate需要具有很強的經驗和能力才行。

JVM

JVM內存劃分

1. 方法區:

  • 又叫永久代,方法區與Java堆一樣,也是線程共享的並且不需要連續的內存,其用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據
  • 運行時常量池:是方法區的一部分,用於存放編譯期生成的各種字面量和符號引用. 字面量比較接近Java語言層次的常量概念,如文本字符串、被聲明爲final的常量值. 符號引用:包括以下三類常量:類和接口的全限定名、字段的名稱和描述符和方法的名稱和描述符
  • JDK1.8撤銷方法區,增加元空間
  • 線程共享

2. 堆:

  • Java 堆的唯一目的就是存放對象實例,幾乎所有的對象實例(和數組)都在這裏分配內存
  • Java堆可以處於物理上不連續的內存空間中,只要邏輯上是連續的即可。而且,Java堆在實現時,既可以是固定大小的,也可以是可拓展的,並且主流虛擬機都是按可擴展來實現的(通過-Xmx(最大堆容量)和 -Xms(最小堆容量)控制)。如果在堆中沒有內存完成實例分配,並且堆也無法再拓展時,將會拋出 OutOfMemoryError 異常
  • TLAB (線程私有分配緩衝區) : 虛擬機爲新生對象分配內存時,需要考慮修改指針 (該指針用於劃分內存使用空間和空閒空間)時的線程安全問題,因爲存在可能出現正在給對象A分配內存,指針還未修改,對象B又同時使用原來的指針分配內存的情況。TLAB的存在就是爲了解決這個問題:每個線程在Java堆中預先分配一小塊內存
    TLAB,哪個線程需要分配內存就在自己的TLAB上進行分配,若TLAB用完並分配新的TLAB時,再加同步鎖定,這樣就大大提升了對象內存分配的效率
  • 線程共享

3. 程序計數器:

  • 在多線程情況下,當線程數超過CPU數量或CPU內核數量時,線程之間就要根據時間片輪詢搶奪CPU時間資源。也就是說,在任何一個確定的時刻,一個處理器都只會執行一條線程中的指令。因此,爲了線程切換後能夠恢復到正確的執行位置,每條線程都需要一個獨立的程序計數器去記錄其正在執行的字節碼指令地址
  • 線程私有

4. 虛擬機棧:

  • 每個方法從調用直至完成的過程,對應一個棧幀在虛擬機棧中入棧到出棧的過程
  • 線程私有

5. 本地方法棧:

  • 本地方法棧與Java虛擬機棧非常相似,也是線程私有的,區別是虛擬機棧爲虛擬機執行Java方法服務,而本地方法棧爲虛擬機執行Native 方法服務。與虛擬機棧一樣,本地方法棧區域也會拋出 StackOverflowError 和 OutOfMemoryError異常
  • 線程私有

GC 算法

1. 引用計數法

  • 循環引用

2. 可達性分析算法

  • 通過一系列的名爲“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連(用圖論的話來說就是從 GC Roots 到這個對象不可達)時,則證明此對象是不可用

3. 標記清除算法

  • 標記-清除算法分爲標記和清除兩個階段。該算法首先從根集合進行掃描,對存活的對象對象標記,標記完畢後,再掃描整個空間中未被標記的對象並進行回收
  • 效率問題:標記和清除兩個過程的效率都不高
  • 空間問題:標記-清除算法不需要進行對象的移動,並且僅對不存活的對象進行處理,因此標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致以後在程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作

4. 複製算法

  • 複製算法將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。這種算法適用於對象存活率低的場景,比如新生代。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效
  • 實踐中會將新生代內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活着的對象一次地複製到另外一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是 8:1,也就是每次新生代中可用內存空間爲整個新生代容量的90% ( 80%+10% ),只有10% 的內存會被“浪費”

5. 標記整理算法

  • 標記整理算法的標記過程類似標記清除算法,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存,類似於磁盤整理的過程,該垃圾回收算法適用於對象存活率高的場景(老年代)
  • 無內存碎片

垃圾收集器

1. CMS

  • 一種以獲取最短回收停頓時間爲目標的收集器
  • 老年代收集器
  • 標記-清除算法
  • 初始標記
  • 併發標記
  • 重新標記
  • 併發清除
  • 整個過程中耗時最長的併發標記和併發清除過程收集器線程都可以與用戶線程一起工作,所以,從總體上來說,CMS收集器的內存回收過程是與用戶線程一起併發執行

缺點:

  • CMS收集器對CPU資源非常敏感。在併發階段,它雖然不會導致用戶線程停頓,但是會因爲佔用了一部分線程而導致應用程序變慢,總吞吐量會降低

  • CMS收集器無法處理浮動垃圾,可能會出現併發模式故障失敗而導致Full GC產生

    • 浮動垃圾:由於CMS併發清理階段用戶線程還在運行着,伴隨着程序運行自然就會有新的垃圾不斷產生,這部分垃圾出現的標記過程之後,CMS無法在當次收集中處理掉它們,只好留待下一次GC中再清理。這些垃圾就是“浮動垃圾”
  • CMS是一款“標記-清除”算法實現的收集器,容易出現大量空間碎片。當空間碎片過多,將會給大對象分配帶來很大的麻煩,往往會出現老年代還有很大空間剩餘,但是無法找到足夠大的連續空間來分配當前對象,不得不提前觸發一次Full GC

2. G1

  • 面向服務端的垃圾收集器
  • 當今收集器技術發展的最前沿成果之一
  • 標記-整理+複製算法
  • 橫跨整個堆內存: G1回收的範圍是整個Java堆(包括新生代,老年代),將整個Java堆劃分爲多個大小相等的獨立區域(Region),雖然還保留新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,而都是一部分Region(不需要連續)的集合
  • 可預測的停頓: G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region
  • 避免全堆掃描——Remembered Set: 爲了避免全堆掃描的發生,虛擬機爲G1中每個Region維護了一個與之對應的Remembered Set。虛擬機發現程序在對Reference類型的數據進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用的對象是否處於不同的Region之中(在分代的例子中就是檢查是否老年代中的對象引用了新生代中的對象),如果是,便通過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set之中。當進行內存回收時,在GC根節點的枚舉範圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏
  • 初始標記
  • 併發標記,從GC Root 開始對堆中對象進行可達性分析,找到存活對象,此階段耗時較長,但可與用戶程序併發執行
  • 最終標記
  • 篩選回收,首先對各個Region中的回收價值和成本進行排序,根據用戶所期望的GC 停頓是時間來制定回收計劃。此階段其實也可以做到與用戶程序一起併發執行,但是因爲只回收一部分Region,時間是用戶可控制的,而且停頓用戶線程將大幅度提高收集效率

JVM相關知識點

1. 方法區的回收

  • 主要是針對 常量池的回收(判斷引用)和對類型的卸載
  • 回收類:
  1. 該類所有的實例都已經被回收,也就是Java堆中不存在該類的任何實例加載
  2. 該類的ClassLoader已經被回收
  3. 該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法

2. 類加載過程

  • 加載:獲取二進制字節流、存儲結構轉化爲運行時數據結構、生成Class對象
  • 驗證:確保Class文件的字節流中包含的信息是否符合當前虛擬機的要求,並且不會危害虛擬機自身的安全
  • 準備:爲類變量分配內存並設置類變量初始值,僅包括類變量
  • 解析:將虛擬機常量池內的符號引用替換爲直接引用
  • 初始化:執行類中定義的Java程序代碼

3. 類變量初值

  • 布爾型(boolean)變量: false
  • byte、short、int、long: 0
  • 字符型:’\u0000’(空字符)
  • 浮點型(float double): 0.0
  • 引用類型(String): null

4. OOM

  • Java堆溢出
    內存泄漏
    內存溢出
  • 虛擬機棧和本地方法棧溢出
    建立過多線程
  • 方法區和運行時常量池溢出
    存放太多Class信息、常量
  • 本機直接內存溢出

5. 堆溢出和棧溢出

  • 堆溢出:不斷創建對象
  • 棧溢出:死循環或者是遞歸太深

6. 什麼時候會發生FullGC

  • System.gc()
  • 老年代空間不足
  • 永久代空間不足
  • Minor GC前判斷晉升到老年代的對象平均大小大於老年代剩餘空間
  • CMS GC時出現promotion failed(擔保:s1s2–old)和concurrent mode failure
  • 堆中分配很大的對象

7. JVM調優參數

  • 堆設置
    -Xms:初始堆大小
    -Xmx:最大堆大小
    -Xss:每個線程虛擬機棧的大小
    -XX:NewSize=n:設置年輕代大小
    -XX:NewRatio=n:設置年輕代和年老代的比值。如:爲3,表示年輕代與年老代比值爲1:3,年輕代佔整個年輕代年老代和的1/4
    -XX:SurvivorRatio=n:年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區佔整個年輕代的1/5
    -XX:MaxPermSize=n:設置持久代大小

  • 收集器設置
    -XX:+UseSerialGC:設置串行收集器
    -XX:+UseParallelGC:設置並行收集器
    -XX:+UseParalledlOldGC:設置並行年老代收集器
    -XX:+UseConcMarkSweepGC:設置併發收集器

  • 垃圾回收統計信息
    -XX:+PrintGC
    -XX:+PrintGCDetails
    -XX:+PrintGCTimeStamps
    -Xloggc:filename

  • 並行收集器設置
    -XX:ParallelGCThreads=n:設置並行收集器收集時使用的CPU數。並行收集線程數。
    -XX:MaxGCPauseMillis=n:設置並行收集最大暫停時間
    -XX:GCTimeRatio=n:設置垃圾回收時間佔程序運行時間的百分比。公式爲1/(1+n)

  • 併發收集器設置
    -XX:+CMSIncrementalMode:設置爲增量模式。適用於單CPU情況。
    -XX:ParallelGCThreads=n:設置併發收集器年輕代收集方式爲並行收集時,使用的CPU數。並行收集線程數

8. 爲什麼分代收集
不同的對象的生命週期(存活情況)是不一樣的,而不同生命週期的對象位於堆中不同的區域,因此對堆內存不同區域採用不同的策略進行回收可以提高 JVM 的執行效率

9. 新生代進入老生代的情況

  • 對象優先在Eden分配,當Eden區沒有足夠空間進行分配時,虛擬機將發起一次MinorGC。現在的商業虛擬機一般都採用複製算法來回收新生代,將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。 當進行垃圾回收時,將Eden和Survivor中還存活的對象一次性地複製到另外一塊Survivor空間上,最後處理掉Eden和剛纔的Survivor空間。(HotSpot虛擬機默認Eden和Survivor的大小比例是8:1)當Survivor空間不夠用時,需要依賴老年代進行分配擔保
  • 大對象直接進入老年代。所謂的大對象是指,需要大量連續內存空間的Java對象,最典型的大對象就是那種很長的字符串以及數組
  • 長期存活的對象(-XX:MaxTenuringThreshold)將進入老年代。當對象在新生代中經歷過一定次數(默認爲15)的Minor GC後,就會被晉升到老年代中
  • 動態對象年齡判定。爲了更好地適應不同程序的內存狀況,虛擬機並不是永遠地要求對象年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡

10. 空間分配擔保

  • 如果老年代的剩餘空間 < 之前轉入老年代的對象的平均大小,則觸發Full GC
  • 如果老年代的剩餘空間 > 之前轉入老年代的對象的平均大小,並且允許擔保失敗,則直接Minor GC,不需要做Full GC
  • 如果老年代的剩餘空間 > 之前轉入老年代的對象的平均大小,並且不允許擔保失敗,則觸發Full GC

11. 類加載器

  • 啓動類加載器
  • 擴展類加載器
  • 應用程序類加載器

12. JAVA虛擬機的作用?

  • 將java字節碼解釋爲具體平臺的具體指令,做到跨平臺,也進行Java對象生命週期的管理,實現內存管理

13. JAVA虛擬機中,哪些可作爲ROOT對象?

  • 虛擬機棧中的引用對象
  • 本地方法棧中的引用對象
  • 方法區中類靜態變量引用的對象
  • 方法區中常量引用的對象

14. minor gc如果運行的很慢,可能是什麼原因引起的?

  • 新生代太大,導致gc時有太多對象需要回收

服務器

1. 反向代理是什麼?
反向代理(Reverse Proxy)方式是指以代理服務器來接受internet上的連接請求,然後將請求轉發給內部網絡上的服務器,並將從服務器上得到的結果返回給internet上請求連接的客戶端,此時代理服務器對外就表現爲一個反向代理服務器。客戶端只會得知反向代理的IP地址,而不知道在代理服務器後面的服務器簇的存在

2. 負載均衡是什麼?
負載均衡(Load balancing)是一種計算機技術,用來在多個計算機(計算機集羣)、網絡連接、CPU、磁盤驅動器或其他資源中分配負載,以達到最優化資源使用、最大化吞吐率、最小化響應時間、同時避免過載的目的。 使用帶有負載均衡的多個服務器組件,取代單一的組件,可以通過冗餘提高可靠性。負載平衡服務通常是由專用軟件和硬件來完成。主要作用是將大量作業合理地分攤到多個操作單元上進行執行,用於解決互聯網架構中的高併發和高可用的問題

Linux

1. 常用命令

  • 查找文件

    find path -name "name"
    - find ~ -name "target*" 模糊查找
    - find ~ -iname "target*" 忽略大小寫
    
  • 查找包含某個字段的文件
    grep "文件內容" 文件名
    |:管道操作符,將前一個命令的正確輸出作爲下一個命令的輸入
    ps:查看進程
    grep:搜索過濾
    ps -ef | grep tomcat | grep -v "grep"
    grep -v:過濾掉相關內容字符串
    top:顯示進程狀態
    free:查看內存
    df:查看linux系統磁盤空間
    du:顯示每個文件和目錄的磁盤使用空間
    tail:查看文件尾部內容
    cat:顯示整個文件、創建文件、合併文件

算法

1. 紅黑樹

  • 旋轉和顏色變化規則:所有插入的點默認爲紅色
    1. 變顏色的情況:當前節點的父親是紅色,且它的祖父節點的另一個節點也是紅色(叔叔節點)
      (1)把父節點設爲黑色
      (2)把叔叔節點也設爲黑色
      (3)把祖父也就是父親的父親設爲紅色(爺爺)
      (4)把指針定義到祖父節點設爲當前要操作的節點
    2. 左旋:當前父節點是紅色,叔叔是黑色的時候,且當前的結點是右子樹。左旋以父節點作爲左旋
    3. 當前父節點是紅色,叔叔是黑色的時候,且當前的節點是左子樹
      (1)把父節點變爲黑色
      (2)把祖父節點變爲紅色(爺爺)
      (3)以祖父節點旋轉

2. 不能加密消息的算法
MD5是生成消息摘要的加密算法,因爲其無祕鑰,因此不用於文本加密,主要用於數字簽名等用途,而其他算法則均爲典型的文本加密算法

3. 開放地址法和鏈地址法

  • 開放地址法:當發生地址衝突時,按照某種方法繼續探測哈希表中的其他存儲單元,直到找到空位置爲止
  • 鏈地址法:如果哈希表空間爲 0 ~ m - 1 ,設置一個由 m 個指針分量組成的一維數組 ST[ m ], 凡哈希地址爲 i 的數據元素都插入到頭指針爲 ST[ i ] 的鏈表中,類似HashMap

4. 線性表
邏輯結構上相鄰的數據在實際的物理存儲中有兩種形式:分散存儲和集中存儲

  • 數據元素在內存中集中存儲,採用順序表示結構,簡稱“順序存儲”
  • 數據元素在內存中分散存儲,採用鏈式表示結構,簡稱“鏈式存儲”

5. 給你一個未知長度的鏈表,怎麼找到中間的那個節點?
快慢指針,快指針走兩步,慢指針走一步,快指針到尾了,慢指針走到一半

6. 用Java實現一個二分查找

public static void main(String[] args) {
    int srcArray[] = {3,5,11,17,21,23,28,30,32,50,64,78,81,95,101};
    System.out.println("下標爲-1表示未找到!");
    System.out.println("下標:"+ binSearch(srcArray, 0, srcArray.length - 1, 81));
    System.out.println("下標:"+ binSearch(srcArray, 100));
 }

 // 二分查找遞歸實現   
 public static int binSearch(int srcArray[], int start, int end, int key) {
     int mid = (end - start) / 2 + start;
     if (srcArray[mid] == key) {
         return mid;
     }   

     if (start >= end) {
         return -1;
     } else if (key > srcArray[mid]) {
         return binSearch(srcArray, mid + 1, end, key);
     } else if (key < srcArray[mid]) {
         return binSearch(srcArray, start, mid - 1, key);
     }
     return -1;   

 }

 // 二分查找普通循環實現   
 public static int binSearch(int srcArray[], int key) {
     int mid = srcArray.length / 2;
     if (key == srcArray[mid]) {
         return mid;
     }   

     int start = 0;
     int end = srcArray.length - 1;
     while (start <= end) {
         mid = (end - start) / 2 + start;
         if (key < srcArray[mid]) {
            end = mid - 1;
         } else if (key > srcArray[mid]) {
             start = mid + 1;
         } else {
             return mid;
         }
     }
     return -1;
 } 

操作系統

1. 進程間通訊方式

  • 共享內存
    共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信。

    • 低級方式:基於數據結構的共享
    • 高級方式:基於存儲區的共享
  • 消息隊列
    消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點

    • 直接通信方式:直接把消息掛到接收進程的消息隊列
    • 間接通信方式:掛到某個中間實體,接收進程找實體接收消息,類似電子郵件
  • 管道
    管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關係的進程間使用。進程的親緣關係通常是指父子進程關係

    • 利用一種特殊的pipe文件連接兩個進程
    • 有名管道 (named pipe) : 有名管道也是半雙工的通信方式,但是它允許無親緣關係進程間的通信
  • 信號量
    信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。

  • socket(套接字)
    套解字也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同機器間的進程通信
    文件

2. 線程通信方式
鎖機制
信號量機制
信號機制

3. 線程同步方式

  • 臨界區:通過對多線程的串行化來訪問公共資源或者一段代碼,速度快,適合控制數據訪問
  • 互斥量:採用互斥對象機制,只有擁有互斥對象的線程纔有訪問公共資源的權限,因爲互斥對象只有一個,所以可以保證公共資源不會同時被多個線程訪問
  • 信號量:它允許多個線程同一時刻訪問同一資源,但是需要限制同一時刻訪問此資源的最大線程數目。信號量對象對線程的同步方式與前面幾種方法不同,信號允許多個線程同時使用共享資源,這與操作系統中PV操作相似
  • 事件(信號):通過通知操作的方式來保持多線程的同步,還可以方便的實現多線程的優先級比較的操作

4. 調度

  • 作業調度(高級調度):選擇處於後備狀態的作業分配資源,發送頻率低;調度對象是作業,主要用於多道批處理系統中,而在分時和實時系統中不設置高級調度
  • 內存調度(中級調度):選擇暫時不能允許的進程調出內存,發送頻率中等;調度對象是進程,在多道批處理,分時和實時三種類型的OS中,都必須配置這級調度
  • 進程調度(低級調度):選擇就緒隊列中合適的進程分配處理機,發生頻率高;內存調度,引入中級調度的主要目的是,提高內存利用率和系統吞吐量,中級調度實際上就是存儲器管理中的對換功能

5. 進程組成部分
程序、數據集合、進程控制塊(PCB)

  • 進程控制塊是進程存在的唯一標識

6. 進程調度方式

  • 剝奪式:有更爲重要或緊迫的進程需要使用處理機,立即分配
  • 非剝奪式:有更爲重要或緊迫的進程需要使用處理機,仍讓當前進程繼續執行

7. 進程調度算法法

  • 先來先服務調度算法(FCFS):選擇最先進入隊列的;既可以作爲作業調度算法也可以作爲進程調度算法;按作業或者進程到達的先後順序依次調度;因此對於長作業比較有利;
  • 短作業優先調度算法(SJF):作業調度算法,算法從就緒隊列中選擇估計時間最短的作業進行處理,直到得出結果或者無法繼續執行;缺點:不利於長作業;未考慮作業的重要性;運行時間是預估的,並不靠譜
  • 優先級調度算法(PSA):基於作業的緊迫程度,由外部賦予作業相應的優先級,調度算法是根據該優先級進行調度的
  • 高相應比優先調度算法:選擇響應比最高的 響應比Rp = (等待時間+要求服務時間) / 要求服務時間
  • 時間片輪轉調度:總數選擇就緒隊列中的第一個進程,但僅能運行一個時間片
  • 多級反饋隊列調度算法:目前公認較好的調度算法;設置多個就緒隊列併爲每個隊列設置不同的優先級,第一個隊列優先級最高,其餘依次遞減。優先級越高的隊列分配的時間片越短,進程到達之後按FCFS放入第一個隊列,如果調度執行後沒有完成,那麼放到第二個隊列尾部等待調度,如果第二次調度仍然沒有完成,放入第三隊列尾部…。只有當前一個隊列爲空的時候纔會去調度下一個隊列的進程

8. 虛擬內存
如果存在一個程序,所需內存空間超過了計算機可以提供的實際內存,那麼由於該程序無法裝入內存所以也就無法運行。單純的增加物理內存只能解決一部分問題,但是仍然會出現無法裝入單個或者無法同時裝入多個程序的問題。但是可以從邏輯的角度擴充內存容量,即可解決上述兩種問題。
基於局部性原理,在程序裝入時,可以將程序的一部分裝入內存,而將其餘部分留在外存,就可以啓動程序執行。在程序執行過程中,當所訪問的信息不在內存時,由操作系統將所需要的部分調入內存,然後繼續執行程序。另一方面,操作系統將內存中暫時不使用的內容換出到外存上,從而騰出空間存放將要調入內存的信息。這樣,系統好像爲用戶提供了一個比實際內存大得多的存儲器,稱爲虛擬存儲器。

虛擬存儲器的特徵:

  1. 多次性:一個作業可以分多次被調入內存。多次性是虛擬存儲特有的屬性
  2. 對換性:作業運行過程中存在換進換出的過程(換出暫時不用的數據換入需要的數據)
  3. 虛擬性:虛擬性體現在其從邏輯上擴充了內存的容量(可以運行實際內存需求比物理內存大的應用程序)。虛擬性是虛擬存儲器的最重要特徵也是其最終目標。虛擬性建立在多次性和對換性的基礎上行,多次性和對換性又建立在離散分配的基礎上

虛擬存儲器的實現方法:分頁請求系統,請求分段系統

9. 頁面置換算法

  • 最佳(Optimal)置換算法:只具有理論意義的算法,用來評價其他頁面置換算法。置換策略是將當前頁面中在未來最長時間內不會被訪問的頁置換出去。
  • 先進先出(FIFO)置換算法:簡單粗暴的一種置換算法,沒有考慮頁面訪問頻率信息。每次淘汰最早調入的頁面。
  • 最近最久未使用LRU(Least Recently Used)置換算法:算法賦予每個頁面一個訪問字段,用來記錄上次頁面被訪問到現在所經歷的時間t,每次置換的時候把t值最大的頁面置換出去(實現方面可以採用寄存器或者棧的方式實現)。
  • 最少使用LFU(Least Frequently Used)置換算法:設置寄存器記錄頁面被訪問次數,每次置換的時候置換當前訪問次數最少的。
  • 改進型Clock算法:在Clock算法的基礎上添加一個修改位,替換時根究訪問位和修改位綜合判斷。優先替換訪問位和修改位都是0的頁面,其次是訪問位爲0修改位爲1的頁面。
  • 頁面緩衝算法(Page Buffering Algorithm,PBA)

死鎖

產生原因:非剝奪資源的競爭和進程的不恰當推進順序
定義:多個進程因競爭資源而造成的一種僵局(互相等待),若無外力作用,這些進程都將無法向前推進

解決方案

預防死鎖

  • 破壞互斥條件
  • 破壞不可剝奪條件
  • 破壞請求和保持條件
  • 破壞循環等待條件

避免死鎖
銀行家算法:採用預分配策略檢查分配完成時系統是否處於安全狀態

  • 安全狀態:能找到一個分配資源的序列能讓所有進程都順利完成

**檢測死鎖:**利用死鎖定理化簡資源分配圖以檢測死鎖的存在
解除死鎖:

  • 資源剝奪法:掛起某些死鎖進程並搶奪它的資源,以便讓其他進程繼續推進
  • 撤銷進程法:強制撤銷部分、甚至全部死鎖進程並剝奪這些進程的資源
  • 進程回退法:讓進程回退到足以迴避死鎖的地步

UML

UML中有哪些常用的圖?
用例圖、時序圖、活動圖、狀態圖

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