[Java面試]java筆試題總結

java筆試題總結

這篇博客是自己平時看到的一些或者有新知識點,或者舊知識重新學習之後有啓發的一些內容,總結在這裏,也是爲了方便日後回顧,共勉。因爲大部分知識點閱讀起來需要些基礎,所以初步讀起來可能會有些困難,可根據此處的知識點再去搜索引擎做更詳細的搜索。

1. JVM總結

@轉自 牛客網:菜鳥葫蘆娃 的回答
在這裏插入圖片描述
在這裏插入圖片描述
參考鏈接

  • 線程共享:
    • 方法區(method area):存儲了每個類的信息(包括類的名稱、方法信息、字段信息)、靜態變量、常量以及編譯器編譯後的代碼等
    • 堆(heap):存儲對象信息,包括對象的屬性、方法
  • 非線程共享
    • 程序計數器(Program Counter Register)
    • 虛擬機棧(VM Stack)
    • 本地方法棧 (Native Method Stack)

    大多數 JVM 將內存區域劃分爲 Method Area(Non-Heap)(方法區) ,Heap(堆) , Program Counter Register(程序計數器) , VM Stack(虛擬機棧,也有翻譯成JAVA 方法棧的),Native Method Stack ( 本地方法棧 ),其中Method Area 和 Heap 是線程共享的 ,VM Stack,Native Method Stack 和Program Counter Register 是非線程共享的。爲什麼分爲 線程共享和非線程共享的呢?請繼續往下看。
     首先我們熟悉一下一個一般性的 Java 程序的工作過程。一個 Java 源程序文件,會被編譯爲字節碼文件(以 class 爲擴展名),每個java程序都需要運行在自己的JVM上,然後告知 JVM 程序的運行入口,再被 JVM 通過字節碼解釋器加載運行。那麼程序開始運行後,都是如何涉及到各內存區域的呢?
     概括地說來,JVM初始運行的時候都會分配好 Method Area(方法區) 和Heap(堆) ,而JVM 每遇到一個線程,就爲其分配一個 Program Counter Register(程序計數器) , VM Stack(虛擬機棧)和Native Method Stack (本地方法棧), 當線程終止時,三者(虛擬機棧,本地方法棧和程序計數器)所佔用的內存空間也會被釋放掉。這也是爲什麼我把內存區域分爲線程共享和非線程共享的原因,非線程共享的那三個區域的生命週期與所屬線程相同,而線程共享的區域與JAVA程序運行的生命週期相同,所以這也是系統垃圾回收的場所只發生在線程共享的區域(實際上對大部分虛擬機來說知發生在Heap上)的原因。

2. Statement、PreparedStatement和CallableStatement

  1. Statement、PreparedStatement和CallableStatement都是接口(interface)。
  2. Statement繼承自Wrapper、PreparedStatement繼承自Statement、CallableStatement繼承自PreparedStatement。
  3. Statement接口提供了執行語句和獲取結果的基本方法;
    PreparedStatement接口添加了處理 IN 參數的方法;
    CallableStatement接口添加了處理 OUT 參數的方法。
  4. Statement:
    • Statement: 普通的不帶參的查詢SQL;支持批量更新,批量刪除;
    • PreparedStatement:
      可變參數的SQL,編譯一次,執行多次,效率高;
      安全性好,有效防止Sql注入等問題;
      支持批量更新,批量刪除;
    • CallableStatement:
      繼承自PreparedStatement,支持帶參數的SQL操作;
      支持調用存儲過程,提供了對輸出和輸入/輸出參數(INOUT)的支持;
      Statement每次執行sql語句,數據庫都要執行sql語句的編譯 ,
      最好用於僅執行一次查詢並返回結果的情形,效率高於PreparedStatement。

PreparedStatement是預編譯的,使用PreparedStatement有幾個好處

  1. 在執行可變參數的一條SQL時,PreparedStatement比Statement的效率高,因爲DBMS預編譯一條SQL當然會比多次編譯一條SQL的效率要高。
  2. 安全性好,有效防止Sql注入等問題。
  3. 對於多次重複執行的語句,使用PreparedStament效率會更高一點,並且在這種情況下也比較適合使用batch;
  4. 代碼的可讀性和可維護性。

3. doGet和doPost

  • doget/dopost與Http協議有關,是在 javax.servlet.http.HttpServlet 中實現的。
  • GenericServlet 抽象類 給出了設計 servlet 的一些骨架,定義了 servlet 生命週期,還有一些得到名字、配置、初始化參數的方法,其設計的是和應用層協議無關的
    在這裏插入圖片描述

4. Servlet的生命週期

  1. 加載:容器通過類加載器使用Servlet類對應的文件來加載Servlet
  2. 創建:通過調用Servlet的構造函數來創建一個Servlet實例
  3. 初始化:通過調用Servlet的 init() 方法來完成初始化工作,這個方法是在Servlet已經被創建,但在向客戶端提供服務之前調用。
  4. 處理客戶請求:Servlet創建後就可以處理請求,當有新的客戶端請求時,Web容器都會創建一個新的線程來處理該請求。接着調用Servlet的 Service() 方法來響應客戶端請求(Service方法會根據請求的method屬性來調用doGet()和doPost()
  5. 卸載:容器在卸載Servlet之前需要調用destroy()方法,讓Servlet釋放其佔用的資源。

5. Servlet是否是進程安全的

servlet在多線程下其本身並不是線程安全的。
如果在類中定義成員變量,而在service中根據不同的線程對該成員變量進行更改,那麼在併發的時候就會引起錯誤。最好是在方法中,定義局部變量,而不是類變量或者對象的成員變量。由於方法中的局部變量是在棧中,彼此各自都擁有獨立的運行空間而不會互相干擾,因此才做到線程安全。

6. Struts1和Struts2的區別和對比:

  • Action 類:
    • Struts1要求Action類繼承一個抽象基類。Struts1的一個普遍問題是使用抽象類編程而不是接口,而struts2的Action是接口。
    • Struts 2 Action類可以實現一個Action接口,也可實現其他接口,使可選和定製的服務成爲可能。Struts2提供一個ActionSupport基類去 實現 常用的接口。Action接口不是必須的,任何有execute標識的POJO對象都可以用作Struts2的Action對象。

  • 線程模式:
    • Struts1 Action是單例模式並且必須是線程安全的,因爲僅有Action的一個實例來處理所有的請求。單例策略限制了Struts1 Action能作的事,並且要在開發時特別小心。Action資源必須是線程安全的或同步的。
    • Struts2 Action對象爲每一個請求產生一個實例,因此沒有線程安全問題。(實際上,servlet容器給每個請求產生許多可丟棄的對象,並且不會導致性能和垃圾回收問題)

  • Servlet 依賴:
    • Struts1 Action 依賴於Servlet API ,因爲當一個Action被調用時HttpServletRequest 和 HttpServletResponse 被傳遞給execute方法。
    • Struts 2 Action不依賴於容器,允許Action脫離容器單獨被測試。如果需要,Struts2 Action仍然可以訪問初始的request和response。但是,其他的元素減少或者消除了直接訪問HttpServetRequest 和 HttpServletResponse的必要性。

  • 可測性:
    • 測試Struts1 Action的一個主要問題是execute方法暴露了servlet API(這使得測試要依賴於容器)。一個第三方擴展--Struts TestCase--提供了一套Struts1的模擬對象(來進行測試)。
    • Struts 2 Action可以通過初始化、設置屬性、調用方法來測試,“依賴注入”支持也使測試更容易。

  • 捕獲輸入:
    • Struts1 使用ActionForm對象捕獲輸入。所有的ActionForm必須繼承一個基類。因爲其他JavaBean不能用作ActionForm,開發者經常創建多餘的類捕獲輸入。動態Bean(DynaBeans)可以作爲創建傳統ActionForm的選擇,但是,開發者可能是在重新描述(創建)已經存 在的JavaBean(仍然會導致有冗餘的javabean)。
    • Struts 2直接使用Action屬性作爲輸入屬性,消除了對第二個輸入對象的需求。輸入屬性可能是有自己(子)屬性的rich對象類型。Action屬性能夠通過 web頁面上的taglibs訪問。Struts2也支持ActionForm模式。rich對象類型,包括業務對象,能夠用作輸入/輸出對象。這種 ModelDriven 特性簡化了taglib對POJO輸入對象的引用。

  • 表達式語言:
    • Struts1 整合了JSTL,因此使用JSTL EL。這種EL有基本對象圖遍歷,但是對集合和索引屬性的支持很弱。
    • Struts2可以使用JSTL,但是也支持一個更強大和靈活的表達式語言--“Object Graph Notation Language” (OGNL).

7.封裝

封裝是把對象的屬性和操作結合在一起,組成一個新的獨立的對象,其內部信息對外界是隱藏的,外界只能通過有限的接口域對象發生關係

8. final關鍵字

  • final修飾的類:叫做最終類,不可被繼承
  • final修飾的方法:不能被重寫
  • final修飾的變量:final修飾的變量叫做常量,必須要初始化,且無法被修改

9. 類的初始化順序

類的初始化順序遵循兩個規則:(方便記憶)
一、父類先於子類
二、靜態先於非靜態

我們根據這兩個規則:有初始化順序–>

  1. 基類靜態代碼塊,基類靜態成員字段 (並列優先級,按代碼中出現先後順序執行)(只有第一次加載類時執行)
  2. 派生類靜態代碼塊,派生類靜態成員字段 (並列優先級,按代碼中出現先後順序執行)(只有第一次加載類時執行)
  3. 基類普通代碼塊,基類普通成員字段 (並列優先級,按代碼中出現先後順序執行)
  4. 基類構造函數
  5. 派生類普通代碼塊,派生類普通成員字段 (並列優先級,按代碼中出現先後順序執行)
  6. 派生類構造函數

10. Java多線程

  1. sleep()、wait()的區別
    sleep()方法不釋放鎖,常被用於暫停執行、在sleep()執行完畢後線程繼續執行。
    wait()釋放線程鎖、常被用於線程間的交互/通信,wait()後線程不會自動甦醒,需要其餘線程執行notify()方法或者notifyAll()
  2. start()方法和run()方法解析
    運行start()方法會啓動一個新的線程,並使該線程進入就緒態,等待cpu分配時間片,新線程會對run()方法進行調用。
    直接運行run方法則會把它當做main下普通的方法進行調用,沒有啓動新的線程
  3. 虛擬機棧和本地方法棧爲什麼是私有的?程序計數器爲什麼是私有的?
    程序計數器需要記錄當前線程執行的位置,在cpu重新分配時間片後可從該位置繼續向下執行。
    棧用於保證本線程的局部變臉的不被其餘的線程訪問到。

11. SSM核心知識

在這裏插入圖片描述

  • IOC依賴注入,基於反射機制
  • AOP面向切面,基於動態代理(jdk,cglib)

12. 依賴倒置原則、IOC、DI、IOC容器的關係

在這裏插入圖片描述

**解釋:**其中依賴倒置原則是一種設計思想,該思想的核心理念是高層的代碼不應該依賴於底層的代碼。並基於此思想設計出了IOC控制反轉,IOC的實現需要通過依賴注入的方法實現,即DI是實現IOC的一種方法。

**優勢:**避免在各處使用new來創建對象,從而實現對象的統一維護
創建實例的時候不必瞭解其中的實現細節,通過接口進行實現。

IOC與普通創建對象過程上的差異:
在這裏插入圖片描述

13. Spring IOC 容器

在這裏插入圖片描述

Spring IOC容器的核心接口:

  • BeanFactory: 提供IOC的配置機制,包含Bean的各種定義,便於實例化Bean;建立Bean之間的依賴關係;控制Bean的生命週期
  • ApplicationContext :繼承多個接口
    在這裏插入圖片描述
  • BeanDefinition: 用來描述Bean’的定義
  • BeanDefinitionRegistry: 提供向IOC容器註冊BeanDefinition對象的方法

Spring源碼 getBean方法的調用邏輯

  1. 轉換beanName
  2. 從緩存中加載實例
  3. 初始化bean
  4. 檢查parentBeanFactory
  5. 初始化依賴的bean
  6. 創建bean

Spring Bean的作用域

  • singleton: 默認作用域,容器裏擁有唯一的bean實例(適合無狀態的Bean)
    在Spring IoC容器中僅存在一個Bean的示例,Bean以單實例的方式存在,單實例模式是重要的設計模式之一,在Spring中對此實現了超越,可以對那些非線程安全的對象採用單實例模式。
  • prototype:針對每一個getBean請求,容器都會創建一個bean實例 (適合有狀態的Bean)

當用戶使用Spring的WebApplicationContext時,還可以使用另外3種Bean的作用域,即request,session和globalSession。

  • request:爲每個http請求創建一個bean實例
  • session:爲每個session創建一個bean實例
  • globalSession:爲每個全局HttpSession創建一個Bean實例,該作用域僅對Portlet有效

Spring Bean的生命週期

在這裏插入圖片描述

理解Spring AOP

面向切面編程
關注點分離,不同的問題交給不同的部分去解決。
分離業務功能代碼和切面代碼,架構將變變得高內聚低耦合。爲確保功能的完整性,切面最終要通過Weave被合併到業務中。
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

AOP的實現

  • JDKProxy:通過Java內部的反射機制進行實現
  • Cglib:藉助ASM進行實現
    區別:
  • JDK反射機制在生成類的過程中比較高效
  • ASM在生成類之後的執行過程比較高效
    設計模式: 代理模式==》接口+ 真實實現類+代理類
    getBean方法返回的實際上是Proxy的實例

在這裏插入圖片描述

14. Mysql數據庫採用b+樹較b樹的優勢

  1. 查詢效率更加穩定。(b+樹的具體信息都存儲在葉子節點,每次查詢都要遍歷到葉子節點才能查找到數據,而b樹要查找的數據可能存儲在根節點,也可能存儲在葉子節點上)
  2. b+樹的磁盤讀寫代價更低。 同上面的敘述,非葉節點不存儲具體數據數據,只存儲查找所用的key值,所有的具體數據都存儲在葉子節點上。因此在非葉節點上能夠存儲更多的key值和分支信息,這種存儲結構能夠有效減少磁盤的io次數,減小磁盤讀寫代價。

https://www.cnblogs.com/nullzx/p/8978177.html

-----------------------------------------------------------------2019.6.2---------------------------------------------------------------

15. switch語句帶來的性能損耗問題

switch語句不加break會產生擊穿現象,順序執行case後面的內容,帶來性能的損耗。
在switch語句對case判斷成功後,不加break,其後的內容將會順序執行,帶來性能問題。

16 . Try catch以及throws異常的機制

  • 降低代碼量。
    使用trycatch接收異常信息,就不需要在下層書寫和傳遞過多的判斷變量,優化代碼的邏輯和代碼量。
  • 不影響線程的繼續執行
    若系統產生的異常交給java虛擬機進行處理,當前線程的執行會終止,其後的內容便無法執行。

17. 採用getter setter方法進行傳值的分析

對於同時使用了getter和setter方法的域屬性,除在

18. equals方法

Object類的equals方法
如上圖是Object類的equals方法, 比較的是對象的引用地址。
String類重寫的equals方法,比較字符串的值

默認情況下也就是從超類Object繼承而來的equals方法與‘==’是完全等價的,比較的都是對象的內存地址,但我們可以重寫equals方法,使其按照我們的需求的方式進行比較,如String類重寫了equals方法,使其比較的是字符的序列,而不再是內存地址。

19.StringBuilder和String類型在效率上的具體效率

20. 反射的理解

根據類的路徑獲取類的具體信息,並可通過獲取的類型信息進行對象實例化

  • 對Class類的理解:
    網上對Class有各種各樣的說法,其中有一大部分稱之爲 類類型 ,個人認爲這種說法並不友好,也很難理解。根據反射的概念,我們通過反射獲取到的是類的信息,如Class Student = Class.forName(“類的路徑”);
    此時Class中包含的就是這個路徑所代表的類的所有信息,我們還可通過Filed,Method分別獲取類的域和方法信息。

21.各種內存區域

  • final修飾的數據的存儲地址與是否final修飾無關,加final僅限定不可變,具體的存儲地址與變量的類型有關。
  • 基本數據類型:棧
  • 對象:堆
  • 常量池:
    Java6和6之前,常量池是存放在方法區中的。
    Java7,將常量池是存放到了堆中,常量池就相當於是在永久代中,所以永久代存放在堆中。
    Java8之後,取消了整個永久代區域,取而代之的是元空間。沒有再對常量池進行調整。

String a = “aaa”+“bbb”;
String b = “aaabbb”;
----->比較結果爲true

String a= “aaa”;
String b = “bbb”;
String c = “aaabbb”;
System.out.println((a+b) == c);
----->比較結果爲false
subString方法採用的是new的方式產生子串

22. 反射的性能問題

如對數組進行拷貝的時候使用for循環進行拷貝,和使用Arrays.copyof()方法進行數組拷貝哪個效率更高呢?在這裏插入圖片描述
我們可以看到copyof方法使用了反射的機制進行數組拷貝,而使用反射會帶來極大的性能問題,因爲反射需要獲取類的信息。所以對於引用數據類型,使用for循環進行拷貝是一種更好的選擇。

23. 類繼承

子類繼承父類,使用多態進行實現,Person person = new Student()
若想調用子類的方法,需要將person強轉爲Student類型

24. 抽象類和接口在設計上的區別

抽象類是對一種事物的抽象,即對類抽象,而接口是對行爲的抽象。抽象類是對整個類整體進行抽象,包括屬性、行爲,但是接口卻是對類局部(行爲)進行抽象。

舉個簡單的例子,飛機和鳥是不同類的事物,但是它們都有一個共性,就是都會飛。那麼在設計的時候,可以將飛機設計爲一個類Airplane,將鳥設計爲一個類Bird,但是不能將 飛行 這個特性也設計爲類,因此它只是一個行爲特性,並不是對一類事物的抽象描述。此時可以將 飛行 設計爲一個接口Fly,包含方法fly( ),然後Airplane和Bird分別根據自己的需要實現Fly這個接口。然後至於有不同種類的飛機,比如戰鬥機、民用飛機等直接繼承Airplane即可,對於鳥也是類似的,不同種類的鳥直接繼承Bird類即可。從這裏可以看出,繼承是一個 "是不是"的關係,而 接口 實現則是 "有沒有"的關係。如果一個類繼承了某個抽象類,則子類必定是抽象類的種類,而接口實現則是有沒有、具備不具備的關係,比如鳥是否能飛(或者是否具備飛行這個特點),能飛行則可以實現這個接口,不能飛行就不實現這個接口。

25. 自動裝箱和拆箱

Java包裝類及自動裝箱、拆箱

java會自動調用裝箱和拆箱操作,Integer.valueOf(3)和 i.intValue()

  • 128陷阱
    超過128會返回 new Integer();
public static Integer valueOf(int i) {
        if(i >= -128 && i <= IntegerCache.high)
            return IntegerCache.cache[i + 128];
        else
            return new Integer(i);
    }

26. 理解字符串常量池和運行時常量池

  • 運行時常量池存在於內存中,也就是class常量池被加載到內存之後的版本,不同之處是:它的字面量可以動態的添加,符號引用可以被解析爲直接引用
  • JVM在執行某個類的時候,必須經過加載、連接、初始化,而連接又包括驗證、準備、解析三個階段。而當類加載到內存中後,jvm就會將class常量池中的內容存放到運行時常量池中,由此可知,運行時常量池也是每個類都有一個。在解析階段,會把符號引用替換爲直接引用,解析的過程會去查詢字符串常量池,也就是我們上面所說的StringTable,以保證運行時常量池所引用的字符串與字符串常量池中是一致的。

27. MyISAM和Innodb的區別

MyISAM不支持外鍵,不是事務安全的,索引時使用非聚簇索引,數據鎖是表級鎖。
相反,Innodb支持外鍵約束,具有事務的提交、回滾和崩潰恢復能力,在索引時使用聚簇索引,數據鎖爲行鎖(並非所有操作都是行鎖,在一些條件下仍會鎖定全表)。

28. Innodb什麼時候會查詢全表(大數據量數據庫優化方案)

  1. 首先考慮在涉及到where或order by的字句上建立索引
  2. where 中存在 is null
  3. 使用like進行查詢
  4. 避免在where字句中使用!= 或<>操作符
  5. 避免在where字句中使用or進行連接
  6. 能使用between儘量減少in字句的使用,以及not in
  7. .不要在 where 子句中的“=”左邊進行函數、算術運算或其他表達式運算,否則系統將可能無法正確使用索引。
  8. select count(*) from table;這樣不帶任何條件的count會引起全表掃描,並且沒有任何業務意義,是一定要杜絕的。
  9. 對於多張大數據量(這裏幾百條就算大了)的表JOIN,要先分頁再JOIN,否則邏輯讀會很高,性能很差。
  10. Update 語句,如果只更改1、2個字段,不要Update全部字段,否則頻繁調用會引起明顯的性能消耗,同時帶來大量日誌。
  11. 索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,因爲 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有 必要。
  12. 任何地方都不要使用 select * from t ,用具體的字段列表代替“*”,不要返回用不到的任何字段。
  13. 如果使用到了臨時表,在存儲過程的最後務必將所有的臨時表顯式刪除,先 truncate table ,然後 drop table ,這樣可以避免系統表的較長時間鎖定。
  14. 儘量避免大事務操作,提高系統併發能力。

29. OpenJdk和OracleJDK的區別

30. switch穿透

31. Mybatis相關

  1. #和$的區別
  2. 當實體類中的屬性名和表中的字段名不一樣 ,怎麼辦 ?
  3. 如何獲取自動生成的(主)鍵值?
  4. 在mapper中如何傳遞多個參數?
    • 在映射文件中使用#{0},#{1}代表傳遞進來的第幾個參數
    • 使用@param註解:來命名參數
    • 使用Map集合作爲參數來裝載
  5. 接口綁定有幾種實現方式,分別是怎麼實現的?
  6. 簡述Mybatis的插件運行原理,以及如何編寫一個插件
    Mybatis僅可以編寫針對ParameterHandler、ResultSetHandler、StatementHandler、Executor這4種接口的插件,Mybatis使用JDK的動態代理,爲需要攔截的接口生成代理對象以實現接口方法攔截功能,每當執行這4種接口對象的方法時,就會進入攔截方法,具體就是InvocationHandler的invoke()方法,當然,只會攔截那些你指定需要攔截的方法。
    實現Mybatis的Interceptor接口並複寫intercept()方法,然後在給插件編寫註解,指定要攔截哪一個接口的哪些方法即可,記住,別忘了在配置文件中配置你編寫的插件。
  7. Mybatis都有哪些Executor執行器?它們之間的區別是什麼?
    • SimpleExecutor
    • ReuseExecutor
    • BatchExecutor

32. 爲什麼重寫了equals方法還要重寫hashCode方法

equals方法原本比較的是對象的地址,此時hashCode也是使用地址進行的比較。
若對equals方法進行了重寫,如對其中的某個屬性進行比較,但並未重寫hashCode方法,就會產生equals方法返回true但hashCode中比較爲false的現象。這會在集合框架中,hashMap產生嚴重的問題

保證equals判斷結果爲true,那麼他們的hashCode必然相同。

33. 對象的內存存儲

對象的實例(instantOopDesc)存儲在堆中,對象的元數據(instantKlass)存儲在方法區中,對象的引用存儲在虛擬機棧中。
在這裏插入圖片描述

方法區用於存儲虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。 所謂加載的類信息,其實不就是給每一個被加載的類都創建了一個 instantKlass對象麼。

在HotSpot虛擬機中,使用oop-klass模型來表示對象。每一個Java類,在被JVM加載的時候,JVM會給這個類創建一個instanceKlass,保存在方法區,用來在JVM層表示該Java類。當我們在Java代碼中,使用new創建一個對象的時候,JVM會創建一個instanceOopDesc對象,這個對象中包含了對象頭以及實例數據。

https://www.hollischuang.com/archives/1910

34. Java對象頭

在這裏插入圖片描述
java對象頭使用3位來標記鎖,其中1 bit 用於標記偏向鎖,剩餘 2 bit 共標記四種鎖,其中11標記GC。

https://www.hollischuang.com/archives/1953

35. 線程池的正確使用姿勢

  • 使用Executors創建線程池
  • 使用ThreadPoolExecutors創建線程池
    **使用Executors創建線程池可能會導致OOM的問題。**java開發手冊規定:
    FixedThreadPool和SingleThreadPool:允許的請求隊列長度爲Integer.MAX_VALUE,可能導致大量請求堆積,導致OOM。
    CachedThreadPool和ScheduledThreadPool:允許創建的線程數量爲Integer。MAX_VALUE,可能會創建大量的線程,從而導致OOM。

避免使用Executors創建線程池,主要是避免使用其中的默認實現,那麼我們可以自己直接調用ThreadPoolExecutor的構造函數來自己創建線程池。在創建的同時,給BlockQueue指定容量就可以了。

private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue(10));

這種情況下,一旦提交的線程數超過當前可用線程數時,就會拋出java.util.concurrent.RejectedExecutionException,這是因爲當前線程池使用的隊列是有邊界隊列,隊列已經滿了便無法繼續處理新的請求。但是異常(Exception)總比發生錯誤(Error)要好。

除了自己定義ThreadPoolExecutor外。還有其他方法。這個時候第一時間就應該想到開源類庫,如apache和guava等。

作者推薦使用guava提供的ThreadFactoryBuilder來創建線程池。

public class ExecutorsDemo {

    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();

    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {

        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            pool.execute(new SubThread());
        }
    }
}z`

36. Java爲什麼要將字符串設計爲final類型的

  1. **緩存hashCode。**提升集合框架的性能。hashcode進行緩存,後面的調用可以直接使用緩存中的值,能夠提升集合數據結構的性能。
  2. 節約堆內存空間,方便其他對象的使用。
  3. 天然的線程安全。將變量設計爲final類型的,由於其不可變性,在併發操作的時候不會存在多線程操作的問題。
  4. 安全性:String被廣泛的使用在其他Java類中充當參數。比如網絡連接、打開文件等操作。如果字符串可變,那麼類似操作可能導致安全問題。因爲某個方法在調用連接操作的時候,他認爲會連接到某臺機器,但是實際上並沒有(其他引用同一String對象的值修改會導致該連接中的字符串內容被修改)。可變的字符串也可能導致反射的安全問題,因爲他的參數也是字符串。

http://www.hollischuang.com/archives/1246

37. 生產唯一token

雪花算法
1bit留空 41bit時間戳 10bit機器碼 12bit自增序號

38. 導致事務回滾的異常

RuntimeTimeException Error 也可使用@Transactional(rollBackFor=“xxException”)註解指定拋出回滾的異常類型

39. 線上服務器CPU佔用率高如何排查定位問題?

  1. 問題定位
  2. 定位進程 使用top命令查看cpu佔用情況
  3. 定位線程 top -Hp 1893 查看進程中各個線程的CPU使用情況
  4. 定位代碼 使用printf %x將線程轉爲16進制 使用jstack命令查看棧信息
  5. 發現問題,解決問題

40. SimpleDateFormate線程安全

若使用SimpleDateformate用作成員變量且聲明爲static,則會產生線程安全的問題。如下:

public class Main {

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        System.out.println(simpleDateFormat.format(Calendar.getInstance().getTime()));
    }
}

因爲simpleDateFormate中存在着一個私有的calendar屬性,在併發的情況下就是一個共享變量,會產生併發問題。

解決方式:

  1. 使用局部變量
  2. 加同步鎖 synchronized
  3. 使用ThreadLocal :ThreadLocal 可以確保每個線程都可以得到單獨的一個 SimpleDateFormat 的對象,那麼自然也就不存在競爭問題了。
  4. Java8以上可採用DateTimeFormatter

41. 爲什麼不能再增強for循環內使用remove、add方法

在foreach中使用remove和add方法會導致ConcurrentModificationException的拋出。
原因:

  • foreach的底層源碼爲iterator。
  • modCount是ArrayList的一個成員變量
  • expectedModCount是ArrayList的一個內部類,表示這個迭代器預期該集合被修改的次數。其值隨着Itr被創建而初始化。只有通過迭代器對集合進行操作,該值纔會改變。

remove僅改變了modCount但未改變expectedModCount的值,因此會導致比較異常的拋出。

42. Http長連接與短連接

短連接: 每次Http請求建立一次連接
長連接: 一個網頁打開完成後,客戶端和服務器之間用於傳輸HTTP數據的 TCP連接不會關閉,如果客戶端再次訪問這個服務器上的網頁,會繼續使用這一條已經建立的連接。Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件(如Apache)中設定這個時間。實現長連接要客戶端和服務端都支持長連接。

43. fail-fast機制

Fail-fast機制是java集合框架中的一種異常檢測機制,當多線程對集合進行結構性改變時,有可能會產生fail-fast機制。

  • 如:
    • 在for each(增強for循環)中進行add和remove操作會觸發fail-fast機制,拋出ConcurrentModificationException異常。

異常原理:

  • foreach其實是依賴了while循環和Iterator實現的。
  • 該方法是在iterator.next()方法中調用的。我們看下該方法的實現:
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

如上,在該方法中對modCount和expectedModCount進行了比較,如果二者不想等,則拋出CMException。

那麼modCount和expectedModCount又是什麼呢?

  • modCount是ArrayList中的一個成員變量。它表示該集合實際被修改的次數。
  • expectedModCount 是 ArrayList中的一個內部類——Itr中的成員變量。 expectedModCount表示這個迭代器預期該集合被修改的次數。其值隨着Itr被創建而初始化。只有通過迭代器對集合進行操作,該值纔會改變。

看下面remove的源碼:

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

僅對modCount的值進行了修改,但並未對內部類中的expectedModCount進行修改。因而在對值進行的比較的時候觸發了fail-fast機制,導致拋出ConcurrentModificationException異常。

總結簡單總結一下,之所以會拋出CMException異常,是因爲我們的代碼中使用了增強for循環,而在增強for循環中,集合遍歷是通過iterator進行的,但是元素的add/remove卻是直接使用的集合類自己的方法。這就導致iterator在遍歷的時候,會發現有一個元素在自己不知不覺的情況下就被刪除/添加了,就會拋出一個異常,用來提示用戶,可能發生了併發修改!

所以,在使用Java的集合類的時候,如果發生CMException,優先考慮fail-fast有關的情況,實際上這裏並沒有真的發生併發,只是Iterator使用了fail-fast的保護機制,只要他發現有某一次修改是未經過自己進行的,那麼就會拋出異常

https://www.hollischuang.com/archives/3542

44. 爲什麼阿里巴巴要求謹慎使用ArrayList中的subList方法

在這裏插入圖片描述

底層原理:
首先,我們看下subList方法給我們返回的List到底是個什麼東西,這一點在JDK源碼中註釋是這樣說的:

Returns a view of the portion of this list between the specifiedfromIndex, inclusive, and toIndex, exclusive

也就是說subList 返回是一個視圖,那麼什麼叫做視圖呢?

我們看下subList的源碼:

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

這個方法返回了一個SubList,這個類是ArrayList中的一個內部類

SubList這個類中單獨定義了set、get、size、add、remove等方法。

當我們調用subList方法的時候,會通過調用SubList的構造函數創建一個SubList,那麼看下這個構造函數做了哪些事情:

SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
    this.parent = parent;
    this.parentOffset = fromIndex;
    this.offset = offset + fromIndex;
    this.size = toIndex - fromIndex;
    this.modCount = ArrayList.this.modCount;
}

可以看到,這個構造函數中把原來的List以及該List中的部分屬性直接賦值給自己的一些屬性了。

也就是說,SubList並沒有重新創建一個List,而是直接引用了原有的List(返回了父類的視圖),只是指定了一下他要使用的元素的範圍而已(從fromIndex(包含),到toIndex(不包含))。

**總結:**我們簡單總結一下,List的subList方法並沒有創建一個新的List,而是使用了原List的視圖(fromIndex和toIndex),這個視圖使用內部類SubList表示。

所以,我們不能把subList方法返回的List強制轉換成ArrayList等類,因爲他們之間沒有繼承關係。

另外,視圖和原List的修改還需要注意幾點,尤其是他們之間的相互影響:

  1. 對父(sourceList)子(subList)List做的非結構性修改(non-structural changes),都會影響到彼此。

  2. 對子List做結構性修改,操作同樣會反映到父List上。

  3. 對父List做結構性修改,會拋出異常ConcurrentModificationException。

https://www.hollischuang.com/archives/3775

45. 數據庫死鎖問題的排查

  1. 查看數據庫版本:
select version();
  1. 引擎查詢方法:
show engines
  1. 查詢事務隔離級別:
    8.0版本之前:
select @@tx_isolation;

8.0版本

select @@transaction_isolation
  1. 查詢日誌
show engine innodb status
  1. 查看鎖情況
  2. 修改代碼或索引

46. 除StringBuilder和String Buffer以及String外,Java 8新增StringJoiner進行字符串拼接

StringJoiner sj = new StringJoiner("Hollis");

        sj.add("hollischuang");
        sj.add("Java乾貨");
        System.out.println(sj.toString());

        StringJoiner sj1 = new StringJoiner(":","[","]");

        sj1.add("Hollis").add("hollischuang").add("Java乾貨");
        System.out.println(sj1.toString());

根據此特性,可使用StringJoiner通過一個LIst進行字符串拼接

47. 線程池

所謂線程池本質是一個hashSet。多餘的任務會放在阻塞隊列中。

只有當阻塞隊列滿了後,纔會觸發非核心線程的創建。所以非核心線程只是臨時過來打雜的。直到空閒了,然後自己關閉了。

線程池提供了兩個鉤子(beforeExecute,afterExecute)給我們,我們繼承線程池,在執行任務前後做一些事情。

線程池原理關鍵技術:鎖(lock,cas)、阻塞隊列、hashSet(資源池)

使用ThreadPoolExecutor創建線程池,不允許使用Executors去創建
在這裏插入圖片描述
原理

Java中的BlockingQueue主要有兩種實現,分別是ArrayBlockingQueue 和 LinkedBlockingQueue。

  • ArrayBlockingQueue是一個用數組實現的有界阻塞隊列,必須設置容量。

  • LinkedBlockingQueue是一個用鏈表實現的有界阻塞隊列,容量可以選擇進行設置,不設置的話,將是一個無邊界的阻塞隊列,最大長度爲Integer.MAX_VALUE。
    這裏的問題就出在:不設置的話,將是一個無邊界的阻塞隊列,最大長度爲Integer.MAX_VALUE。也就是說,如果我們不設置LinkedBlockingQueue的容量的話,其默認容量將會是Integer.MAX_VALUE。

而newFixedThreadPool中創建LinkedBlockingQueue時,並未指定容量。此時,LinkedBlockingQueue就是一個無邊界隊列,對於一個無邊界隊列來說,是可以不斷的向隊列中加入任務的,這種情況下就有可能因爲任務過多而導致內存溢出問題。

創建線程池的正確姿勢:

private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue(10));

這種情況下,一旦提交的線程數超過當前可用線程數時,就會拋出java.util.concurrent.RejectedExecutionException,這是因爲當前線程池使用的隊列是有邊界隊列,隊列已經滿了便無法繼續處理新的請求。但是異常(Exception)總比發生錯誤(Error)要好。

48. ThreadLocal

ThreadLocal
ThreadLocal存放的值是線程內共享的,線程間互斥的,主要用於線程內共享一些數據,避免通過參數來傳遞,這樣處理後,能夠優雅的解決一些實際問題。

49. 創建線程的四種方式

  1. 繼承Thread類
  2. 實現Runnable接口
  3. 通過Callable和FutureTask創建線程
public class MultiThreads {
    public static void main(String[] args) throws InterruptedException {
        CallableThread callableThread = new CallableThread();
        FutureTask futureTask = new FutureTask<>(callableThread);
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
}

class CallableThread implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println(Thread.currentThread().getName());
        return "Hollis";
    }

}
  1. 通過線程池創建線程
public class MultiThreads {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println(Thread.currentThread().getName());
        System.out.println("通過線程池創建線程");
        ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(10));
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
    }
}

50. SimpleDateFormat

SimpleDateFormat不是一個線程安全的類。見阿里巴巴編碼規範:
在這裏插入圖片描述

如何解決:

  1. 使用局部變量
  2. 進行加鎖
for (int i = 0; i < 100; i++) {
    //獲取當前時間
    Calendar calendar = Calendar.getInstance();
    int finalI = i;
    pool.execute(() -> {
        //加鎖
        synchronized (simpleDateFormat) {
            //時間增加
            calendar.add(Calendar.DATE, finalI);
            //通過simpleDateFormat把時間轉換成字符串
            String dateString = simpleDateFormat.format(calendar.getTime());
            //把字符串放入Set中
            dates.add(dateString);
            //countDown
            countDownLatch.countDown();
        }
    });
}
  1. 使用ThreadLocal
/**
 * 使用ThreadLocal定義一個全局的SimpleDateFormat
 */
private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};

//用法
String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());

ThreadLocal保證每個線程都有一個獨享的對象,避免了頻繁創建的對象,也避免了多線程的競爭。

  1. 使用DateTimeFormatter
    使用DateTimeFormatter代替SimpleDateFormat,他是一個線程安全的類。

51. 不要直接使用Log4J等日誌工具

使用Slf4j等門面日誌而不直接使用Log4j等日誌。

52. Spring源碼

  • Application接口
    ApplicationContext接口繼承自BeanFactory接口(非直接繼承),就是我們常說的bean容器。在這裏插入圖片描述

有實現類 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext Spring提供的兩個常用的ApplicationContext實現類,前者默認從類路徑加載配置文件,後者從文件系統加載。 然後初始化一個ApplicationContext。

ApplicationContext ac =new ClassPathXmlApplicationContext("application.xml");
ApplicationContext ac =new FileSystemXmlApplicationContext(/User/Desktop/application.xml);

@Autowired

Spring通過BeanPostProcessor處理Autowired註解,所以不能在自己的BeanPostProcessor或BeanFactoryPostProcessor類型應用這些註解,這些類型必須通過xml或者Spring的@Bean註解進行加載。

在這裏插入圖片描述

53. StringBuilder和StringBuffer

String是final類型的數據,因此+號拼接需要重新創建String對象。
StringBuilder類似String內部也是char[] 但並不是final類型,因此使用append添加內容時會進行擴容,並添加元素。
StringBuffer內部使用synchronized保證線程安全。

54. 內存泄漏

簡單的理解內存泄漏的問題就是不使用的對象沒有被回收(沒有被GC的對象。)

解決方案:

  • 儘量減小變量的作用域,如能使用局部變量的就不要使用全局變量。
  • 明確之後不會繼續使用的對象可手動賦null值,使對象區域更容易被判爲不可達。

55. AOP

  • 配置切面aspect

  • 配置切入點pointcut 配置expression
    在這裏插入圖片描述
    如何聲明?
    在這裏插入圖片描述

  • before

  • after returning

  • after throwing

  • after

  • around

56. AQS

AQS的英文全稱爲AbstractQueuedSynchronizer 隊列同步器。
AQS實現了對同步狀態的管理,以及對阻塞線程進行排隊,等待通知等等一些底層的實現處理。AQS的核心也包括了這些方面:同步隊列,獨佔式鎖的獲取和釋放,共享鎖的獲取和釋放以及可中斷鎖,超時等待鎖獲取這些特性的實現,而這些實際上則是AQS提供出來的模板方法

其含義爲當共享資源被某個線程佔有,其他請求該資源的線程將會阻塞,從而進入同步隊列。(隊列有兩種實現形式 一種爲數組,一種爲鏈表)AQS底層採用雙向鏈表隊列的形式進行實現,包含共享鎖和獨佔鎖。

57. Spring Bean的生命週期

在這裏插入圖片描述

借用網上廣爲流傳的一個圖。
Spring Bean的聲明週期分爲對象的實例化,和銷燬兩個過程。在初始化Spring ApplicationContext後進行Bean的實例化,對Bean的實例化有域注入,setter注入和構造器注入三種形式。

  • Bean的銷燬:實現DisposableBean接口並覆蓋destory方法
  • Aware接口:Spring中提供了一些以aware結尾的幾口,實現了Aware接口的bean在被初始化後,可以獲取相應的資源。 如ApplicationContextAware接口
  • BeanPostProcessor:後置處理器。bean對象已經被正確的構造,但如果你想要對象被使用前再進行一些自定義的處理,就可以通過BeanPostProcessor接口實現。 接口包含postProcessorBeforeInitialzation和postProcessorAfterInitialzation兩個方法
  • **初始化:**初始化時可實現InitializingBean接口,並實現afterPropertiesSet方法。在對Bean進行實例化是若實現了此接口則會提前調用afterPropertiesSet方法

更詳細的過程可參考 https://www.zhihu.com/question/38597960

58. 數據庫

理解數據庫的架構需要從他的物理存儲和邏輯存儲兩方面來理解,參照下圖:
在這裏插入圖片描述
物理存儲指的是我們的文件存儲結構,數據庫中的數據一定是存儲在磁盤上,而我們常說的索引樹之類的內容,都是邏輯上的結構。可參照上圖進行理解,上圖包含了數據庫相關的全部架構和知識體系。

59.計算機結構——CPU

60.列式存儲結構

不同於Mysql的行式存儲結構,列式存儲對於 OLTP 不友好,一行數據的寫入需要同時修改多個列。但對 OLAP 場景有着很大的優勢:

  • 當查詢語句只涉及部分列時,只需要掃描相關的列
  • 列式存儲的每一列數據類型都是相同的,彼此之間的相關性更大,對列數據壓縮的效率較高

OLTP是傳統的關係型數據庫的主要應用,主要是基本的、日常的事務處理,記錄即時的增、刪、改、查,比如在銀行存取一筆款,就是一個事務交易。OLAP即聯機分析處理,是數據倉庫的核心部心,支持複雜的分析操作,側重決策支持,並且提供直觀易懂的查詢結果。典型的應用就是複雜的動態報表系統。

61. 詳解內部類

內部類總共有四種,先來看一下他們在表層上或者說是寫法上的差異:

  • 成員內部類:普通的定義在一個類內部的類
  • 局部內部類:可以定義在一個方法或者代碼塊內
  • 匿名內部類:類似局部內部類,但是並未進行顯式聲明,而是採用匿名的方式
  • 靜態內部類:使用static關鍵字修飾的內部類

那麼內部類存在的意義究竟?

衆所周知,我們的C++設計是多繼承的,而多繼承的好處就是我們只需要寫很少的代碼即可完成一個尅的的定義,可以通過集成其他類來獲取其他類的實現。
Java在設計繼承機制時認爲多繼承會是一個麻煩,所以Java採用了單繼承的方式進行實現,其思想是is-a的原則,(如果一個類是A,那麼就不能是B,因此是單繼承的設計)。

但是多繼承的方式也並不是一無是處,在一些需要大量複用代碼的情況下,也不失爲一個好的解決方式。因此採用內部類的設計方式我們可以很好的解決大量複用的問題。採用內部類的方式,我們可以很輕鬆地在一個類的內部定義多個內部類,並分別繼承多個類。因此我們可以說Java的內部類完善了他的多繼承機制。

通過例子來理解一下:

public interface Contents {
	int value();
}
public interface Destination {
	String readLabel();
}
public class Goods {
	private class Content implements Contents {
		private int i = 11;
		public int value() {
			return i;
		}
	}
	protected class GDestination implements Destination {
		private String label;
		private GDestination(String whereTo) {
			label = whereTo;
		}
		public String readLabel() {
			return label;
		}
	}
	public Destination dest(String s) {
		return new GDestination(s);
	}
	public Contents cont() {
		return new Content();
	}
}
class TestGoods {
	public static void main(String[] args) {
		Goods p = new Goods();
		Contents c = p.cont();
		Destination d = p.dest("Beijing");
	}
}

這是最普通的使用方式。

在這個例子裏類Content和GDestination被定義在了類Goods內部,並且分別有着protected和private修飾符來控制訪問級別。Content代表着Goods的內容,而GDestination代表着Goods的目的地。它們分別實現了兩個接口Content和Destination。在後面的main方法裏,直接用 Contents c和Destination d進行操作,你甚至連這兩個內部類的名字都沒有看見!這樣,內部類的第一個好處就體現出來了 隱藏你不想讓別人知道的操作,也即封裝性。
同時,我們也發現了在外部類作用範圍之外得到內部類對象的第一個方法,那就是利用其外部類的方法創建並返回。上例中的cont()和dest()方法就是這麼做的。那麼還有沒有別的方法呢?當然有,其語法格式如下:
outerObject=new outerClass(Constructor Parameters);
outerClass.innerClass innerObject=outerObject.new InnerClass(Constructor Parameters);
注意在創建非靜態內部類對象時,一定要先創建起相應的外部類對象。至於原因,也就引出了我們下一個話題 非靜態內部類。

  1. 成員內部類(非靜態內部類)

類對象有着指向其外部類對象的引用 對剛纔的例子稍作修改:

public class Goods {
	private int valueRate = 2;
	private class Content implements Contents {
		private int i = 11 * valueRate;
		public int value() {
			return i;
		}
	}
	protected class GDestination implements Destination {
		private String label;
		private GDestination(String whereTo) {
			label = whereTo;
		}
		public String readLabel() {
			return label;
		}
	}
	public Destination dest(String s) {
		return new GDestination(s);
	}
	public Contents cont() {
		return new Content();
	}
}

在這裏我們給Goods類增加了一個private成員變量valueRate,意義是貨物的價值係數,在內部類Content的方法value()計算價值時把它乘上。我們發現,value()可以訪問valueRate,這也是內部類的第二個好處 一個內部類對象可以訪問創建它的外部類對象的內容,甚至包括私有變量!這是一個非常有用的特性,爲我們在設計時提供了更多的思路和捷徑。要想實現這個功能,內部類對象就必須有指向外部類對象的引用。Java編譯器在創建內部類對象時,隱式的把其外部類對象的引用也傳了進去並一直保存着。這樣就使得內部類對象始終可以訪問其外部類對象,同時這也是爲什麼在外部類作用範圍之外向要創建內部類對象必須先創建其外部類對象的原因。
有人會問,如果內部類裏的一個成員變量與外部類的一個成員變量同名,也即外部類的同名成員變量被屏蔽了,怎麼辦?沒事,Java裏用如下格式表達外部類的引用:
outerClass.this
有了它,我們就不怕這種屏蔽的情況了。

  1. 靜態內部類
    和普通的類一樣,內部類也可以有靜態的。不過和非靜態內部類相比,區別就在於靜態內部類沒有了指向外部的引用。

public class Goods {
	private int valueRate = 2;
	private class Content implements Contents {
		private int i = 11 * valueRate;
		public int value() {
			return i;
		}
	}
	protected class GDestination implements Destination {
		private String label;
		private GDestination(String whereTo) {
			label = whereTo;
		}
		public String readLabel() {
			return label;
		}
	}
	public Destination dest(String s) {
		return new GDestination(s);
	}
	public Contents cont() {
		return new Content();
	}
}

這實際上和C++中的嵌套類很相像了,Java內部類與C++嵌套類最大的不同就在於是否有指向外部的引用這一點上,當然從設計的角度以及以它一些細節來講還有區別。
除此之外,在任何非靜態內部類中,都不能有靜態數據,靜態方法或者又一個靜態內部類(內部類的嵌套可以不止一層)。不過靜態內部類中卻可以擁有這一切。這也算是兩者的第二個區別吧。

  1. 局部內部類

是的,Java內部類也可以是局部的,它可以定義在一個方法甚至一個代碼塊之內。

public class Goods1 {
	public Destination dest(String s) {
		class GDestination implements Destination {
			private String label;
			private GDestination(String whereTo) {
				label = whereTo;
			}
			public String readLabel() {
				return label;
			}
		}
		return new GDestination(s);
	}
	public static void main(String[] args) {
		Goods1 g = new Goods1();
		Destination d = g.dest("Beijing");
	}
}

  1. 匿名內部類
    匿名內部類類似於我們的匿名對象的思想。是對類的定義採用了一種匿名的方式。

java的匿名內部類的語法規則看上去有些古怪,不過如同匿名數組一樣,當你只需要創建一個類的對象而且用不上它的名字時,使用內部類可以使代碼看上去簡潔清楚。它的語法規則是這樣的:
new interfacename(){…}; 或 new superclassname(){…};
下面接着前面繼續舉例子:

public class Goods3 {
	public Contents cont() {
		return new Contents() {
			private int i = 11;
			public int value() {
				return i;
			}
		};
	}
}

這裏方法cont()使用匿名內部類直接返回了一個實現了接口Contents的類的對象,看上去的確十分簡潔。
在java的事件處理的匿名適配器中,匿名內部類被大量的使用。例如在想關閉窗口時加上這樣一句代碼:

frame.addWindowListener(new WindowAdapter(){
	public void windowClosing(WindowEvent e){
	   System.exit(0);
	}
}); 

62. Mysql的連接

  • 對於三類連接查詢,MySQL只支持其中的:內連接 和 外連接
  • 對於所有的連接類型而言,就是將符合關鍵字 ON 後條件匹配的對應組合都成爲一條記錄出現在結果集中,對於兩個表中的某條記錄可能存在:一對多 或者 多對一 的情況會在結果集中形成多條記錄,只是另外一個表中查詢的字段信息相同而已;千萬不要誤以爲:左外連接查詢到的記錄數是和左表的記錄數一樣,對於其他連接一樣,不能形成這個誤區。
  • 對於內連接查詢到的是一個符合 ON 條件匹配的在兩個表中都存在記錄的結果集
    -對於外連接,例如左外連接查詢到的是一個包含所有左表記錄且在右表符合ON匹配時的右表信息的結果集,如果左表的某條記錄和右表的多條記錄匹配,結果集中就存在同一個左表記錄對應多個右表記錄的多條記錄,此時的結果集記錄數是大於左表的記錄數的。對於右外連接來說同理。
  • 對於全連接,就是把左表右表都包含在內,如果符合ON條件的匹配在結果集中形成一條記錄,如果對左表或者右表中的某一條記錄在對方表中不存在ON匹配時,就以單獨一條記錄出現在結果集中,對方表不存在的信息以NULL補充。

63. 數據庫範式

  • 第一範式:數據庫某個屬性不能有多個值->第一範式就是無重複的列
  • 第二範式:屬性完全依賴於主鍵-> 消除部分子函數依賴
  • 第三範式:屬性不依賴其他非主屬性-> 消除傳遞依賴

64. 三次握手 四次揮手

三次握手 四次揮手

65. 雙親委派模型

類加載器加載類,首先會把這個類請求委派給父類加載器去進行類加載,每一層都是如此,一直遞歸到頂層。
在這裏插入圖片描述
存在原因:

雙親委派有啥好處呢?它使得類有了層次的劃分。就拿java.lang.Object來說,你加載它經過一層層委託最終是由Bootstrap ClassLoader來加載的,也就是最終都是由Bootstrap ClassLoader去找<JAVA_HOME>\lib中rt.jar裏面的java.lang.Object加載到JVM中。

這樣如果有不法分子自己造了個java.lang.Object,裏面嵌了不好的代碼,如果我們是按照雙親委派模型來實現的話,最終加載到JVM中的只會是我們rt.jar裏面的東西,也就是這些核心的基礎類代碼得到了保護。

JDBC的SPI機制:
在rt.jar裏面定義了SPI。
mysql有mysql的jdbc實現,oracle有oracle的jdbc實現,反正我java不管你內部如何實現的,反正你們都得統一按我這個來,這樣我們java開發者才能容易的調用數據庫操作。所以因爲這樣那就不得不違反這個約束啊,Bootstrap ClassLoader就得委託子類來加載數據庫廠商們提供的具體實現。因爲它的手只能摸到<JAVA_HOME>\lib中,其他的它無能爲力。這就違反了自下而上的委託機制了。
Java就搞了個線程上下文類加載器,通過setContextClassLoader()默認情況就是應用程序類加載器然後Thread.current.currentThread().getContextClassLoader()獲得類加載器來加載。

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