2021年面試,整理全網初、中、高級常見Java面試題附答案

此爲部分面試題包含答案,更多面試題見微信小程序 “Java精選面試題”,3000+道面試題。內容持續更新中包含基礎、集合、併發、JVM、Spring、Spring MVC、Spring Boot、Spring Cloud、Dubbo、MySQL、Redis、MyBaits、Zookeeper、Linux、數據結構與算法、項目管理工具、消息隊列、設計模式、Nginx、常見 BUG 問題、網絡編程等。

————————————————

面向對象編程有哪些特徵?

**一、抽象和封裝**

類和對象體現了抽象和封裝

抽象就是解釋類與對象之間關係的詞。類與對象之間的關係就是抽象的關係。一句話來說明:類是對象的抽象,而對象則是類得特例,即類的具體表現形式。

封裝兩個方面的含義:一是將有關數據和操作代碼封裝在對象當中,形成一個基本單位,各個對象之間相對獨立互不干擾。二是將對象中某些屬性和操作私有化,已達到數據和操作信息隱蔽,有利於數據安全,防止無關人員修改。把一部分或全部屬性和部分功能(函數)對外界屏蔽,就是從外界(類的大括號之外)看不到,不可知,這就是封裝的意義。

**二、繼承**

面向對象的繼承是爲了軟件重用,簡單理解就是代碼複用,把重複使用的代碼精簡掉的一種手段。如何精簡,當一個類中已經有了相應的屬性和操作的代碼,而另一個類當中也需要寫重複的代碼,那麼就用繼承方法,把前面的類當成父類,後面的類當成子類,子類繼承父類,理所當然。就用一個關鍵字extends就完成了代碼的複用。

**三、多態**

沒有繼承就沒有多態,繼承是多態的前提。雖然繼承自同一父類,但是相應的操作卻各不相同,這叫多態。由繼承而產生的不同的派生類,其對象對同一消息會做出不同的響應。
JDK、JRE、JVM 之間有什麼關係?

**1、JDK**

JDK(Java development Toolkit),JDK是整個Java的核心,包括了Java的運行環境(Java Runtime Environment),一堆的Java工具(Javac,java,jdb等)和Java基礎的類庫(即Java API 包括rt.jar).

Java API 是Java的應用程序的接口,裏面有很多寫好的Java class,包括一些重要的結構語言以及基本圖形,網絡和文件I/O等等。

**2、JRE**

JRE(Java Runtime Environment),Java運行環境。在Java平臺下,所有的Java程序都需要在JRE下才能運行。只有JVM還不能進行class的執行,因爲解釋class的時候,JVM需調用解釋所需要的類庫lib。JRE裏面有兩個文件夾bin和lib,這裏可以認爲bin就是JVM,lib就是JVM所需要的類庫,而JVM和lib合起來就稱JRE。

JRE包括JVM和JAVA核心類庫與支持文件。與JDK不同,它不包含開發工具-----編譯器,調試器,和其他工具。

**3、JVM**

JVM:Java Virtual Machine(Java 虛擬機)JVM是JRE的一部分,它是虛擬出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件構架,入處理器,堆棧,寄存器等,還有相應的指令系統。

JVM是Java實現跨平臺最核心的部分,所有的Java程序會首先被編譯爲class的類文件,JVM的主要工作是解釋自己的指令集(即字節碼)並映射到本地的CPU的指令集或OS的系統調用。Java面對不同操作系統使用不同的虛擬機,一次實現了跨平臺。JVM對上層的Java源文件是不關心的,它關心的只是由源文件生成的類文件
如何使用命令行編譯和運行 Java 文件?

編譯和運行Java文件,需瞭解兩個命令:

1)javac命令:編譯java文件;使用方法: javac Hello.java ,如果不出錯的話,在與Hello.java 同一目錄下會生成一個Hello.class文件,這個class文件是操作系統能夠使用和運行的文件。

2)java命令: 作用:運行.class文件;使用方法:java Hello,如果不出錯的話,會執行Hello.class文件。注意:這裏的Hello後面不需要擴展名。

新建文件,編寫代碼如下:
```java
public class Hello{


public static void main(String[] args){

 

System.out.println("Hello world,歡迎關注微信公衆號“Java精選”!");


}
}
```
文件命名爲Hello.java,注意後綴爲“java”。

打開cmd,切換至當前文件所在位置,執行javac Hello.java,該文件夾下面生成了一個Hello.class文件

輸入java Hello命令,cmd控制檯打印出代碼的內容Hello world,歡迎關注微信公衆號“Java精選”!
說說常用的集合有哪些?

Map接口和Collection接口是所有集合框架的父接口

Collection 接口的子接口包括:Set接口和List接口。

Set中不能包含重複的元素。List是一個有序的集合,可以包含重複的元素,提供了按索引訪問的方式。

Map 接口的實現類主要有:HashMap、Hashtable、ConcurrentHashMap以及TreeMap等。Map不能包含重複的key,但是可以包含相同的value。根據鍵得到值,對map集合遍歷時先得到鍵的set集合,對set集合進行遍歷,得到相應的值。此爲部分面試題包含答案,更多面試題見微信小程序 “Java精選面試題”,3000+道面試題。

Set 接口的實現類主要有:HashSet、TreeSet、LinkedHashSet等

List 接口的實現類主要有:ArrayList、LinkedList、Stack以及Vector等

Iterator,所有的集合類,都實現了Iterator接口,這是一個用於遍歷集合中元素的接口,主要包含以下三種方法:

hasNext()是否還有下一個元素

next()返回下一個元素

remove()刪除當前元素
進程與線程之間有什麼區別?

進程是系統中正在運行的一個程序,程序一旦運行就是進程。

進程可以看成程序執行的一個實例。進程是系統資源分配的獨立實體,每個進程都擁有獨立的地址空間。一個進程無法訪問另一個進程的變量和數據結構,如果想讓一個進程訪問另一個進程的資源,需要使用進程間通信,比如管道,文件,套接字等。

一個進程可以擁有多個線程,每個線程使用其所屬進程的棧空間。線程與進程的一個主要區別是,統一進程內的一個主要區別是,同一進程內的多個線程會共享部分狀態,多個線程可以讀寫同一塊內存(一個進程無法直接訪問另一進程的內存)。同時,每個線程還擁有自己的寄存器和棧,其他線程可以讀寫這些棧內存。

線程是進程的一個實體,是進程的一條執行路徑。

線程是進程的一個特定執行路徑。當一個線程修改了進程的資源,它的兄弟線程可以立即看到這種變化。
什麼是 JVM?

Java程序的跨平臺特性主要是指字節碼文件可以在任何具有Java虛擬機的計算機或者電子設備上運行,Java虛擬機中的Java解釋器負責將字節碼文件解釋成爲特定的機器碼進行運行。

因此在運行時,Java源程序需要通過編譯器編譯成爲.class文件。

衆所周知java.exe是java class文件的執行程序,但實際上java.exe程序只是一個執行的外殼,它會裝載jvm.dll(windows下,下皆以windows平臺爲例,linux下和solaris下其實類似,爲:libjvm.so),這個動態連接庫纔是java虛擬機的實際操作處理所在。

JVM是JRE的一部分。它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。

JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。Java語言最重要的特點就是跨平臺運行。

使用JVM就是爲了支持與操作系統無關,實現跨平臺。所以,JAVA虛擬機JVM是屬於JRE的,而現在我們安裝JDK時也附帶安裝了JRE(當然也可以單獨安裝JRE)。
什麼是事務?

事務(transaction)是指數據庫操作的最小工作單元,是作爲單個邏輯工作單元執行的一系列操作;這些操作作爲一個整體一起向系統提交,要麼都執行、要麼都不執行;事務是一組不可再分割的操作集合(工作邏輯單元)。

通俗的說就是事務可以作爲一個單元的一組有序的數據庫操作。如果組中的所有操作都成功,則認爲事務成功,即使只有一個操作失敗,事務也不成功。如果所有操作完成,事務則提交,其修改將作用於所有其他數據庫進程。如果一個操作失敗,則事務將回滾,該事務所有操作的影響都將取消。
MySQL 事務都有哪些特性?

事務的四大特性:

**1 、原子性**

事務是數據庫的邏輯工作單位,事務中包含的各操作要麼都做,要麼都不做

**2 、一致性**

事務執行的結果必須是使數據庫從一個一致性狀態變到另一個一致性狀態。因此當數據庫只包含成功事務提交的結果時,就說數據庫處於一致性狀態。如果數據庫系統 運行中發生故障,有些事務尚未完成就被迫中斷,這些未完成事務對數據庫所做的修改有一部分已寫入物理數據庫,這時數據庫就處於一種不正確的狀態,或者說是 不一致的狀態。

**3 、隔離性**

一個事務的執行不能其它事務干擾。即一個事務內部的操作及使用的數據對其它併發事務是隔離的,併發執行的各個事務之間不能互相干擾。

**4 、持續性**

也稱永久性,指一個事務一旦提交,它對數據庫中的數據的改變就應該是永久性的。接下來的其它操作或故障不應該對其執行結果有任何影響。
MyBatis 是什麼框架?

MyBatis框架是一個優秀的數據持久層框架,在實體類和SQL語句之間建立映射關係,是一種半自動化的ORM實現。其封裝性要低於Hibernate,性能優秀,並且小巧。

ORM即對象/關係數據映射,也可以理解爲一種數據持久化技術。

MyBatis的基本要素包括核心對象、核心配置文件、SQL映射文件。

數據持久化是將內存中的數據模型轉換爲存儲模型,以及將存儲模型轉換爲內存中的數據模型的統稱。
什麼是 Redis?

redis是一個高性能的key-value數據庫,它是完全開源免費的,而且redis是一個NOSQL類型數據庫,是爲了解決高併發、高擴展,大數據存儲等一系列的問題而產生的數據庫解決方案,是一個非關係型的數據庫。但是,它也是不能替代關係型數據庫,只能作爲特定環境下的擴充。

redis是一個以key-value存儲的數據庫結構型服務器,它支持的數據結構類型包括:字符串(String)、鏈表(lists)、哈希表(hash)、集合(set)、有序集合(Zset)等。爲了保證讀取的效率,redis把數據對象都存儲在內存當中,它可以支持週期性的把更新的數據寫入磁盤文件中。而且它還提供了交集和並集,以及一些不同方式排序的操作。
什麼是 Spring 框架?

Spring中文翻譯過來是春天的意思,被稱爲J2EE的春天,是一個開源的輕量級的Java開發框架, 具有控制反轉(IoC)和麪向切面(AOP)兩大核心。Java Spring框架通過聲明式方式靈活地進行事務的管理,提高開發效率和質量。

Spring框架不僅限於服務器端的開發。從簡單性、可測試性和松耦合的角度而言,任何 Java 應用都可以從Spring中受益。Spring框架還是一個超級粘合平臺,除了自己提供功能外,還提供粘合其他技術和框架的能力。

**1)IOC 控制反轉**

對象創建責任的反轉,在Spring中BeanFacotory是IOC容器的核心接口,負責實例化,定位,配置應用程序中的對象及建立這些對象間的依賴。XmlBeanFacotory實現BeanFactory接口,通過獲取xml配置文件數據,組成應用對象及對象間的依賴關係。

Spring中有3中注入方式,一種是set注入,另一種是接口注入,另一種是構造方法注入。

**2)AOP面向切面編程**

AOP是指縱向的編程,比如兩個業務,業務1和業務2都需要一個共同的操作,與其往每個業務中都添加同樣的代碼,通過寫一遍代碼,讓兩個業務共同使用這段代碼。

Spring中面向切面編程的實現有兩種方式,一種是動態代理,一種是CGLIB,動態代理必須要提供接口,而CGLIB實現是由=有繼承。
什麼是 Spring MVC 框架?

Spring MVC屬於Spring FrameWork的後續產品,已經融合在Spring Web Flow中。

Spring框架提供了構建Web應用程序的全功能MVC模塊。

使用Spring可插入MVC架構,從而在使用Spring進行WEB開發時,可以選擇使用Spring中的Spring MVC框架或集成其他MVC開發框架,如Struts1(已基本淘汰),Struts2(老項目還在使用或已重構)等。

通過策略接口,Spring框架是高度可配置的且包含多種視圖技術,如JavaServer Pages(JSP)技術、Velocity、Tiles、iText和POI等。

Spring MVC 框架並不清楚或限制使用哪種視圖,所以不會強迫開發者只使用JSP技術。

Spring MVC分離了控制器、模型對象、過濾器以及處理程序對象的角色,這種分離讓它們更容易進行定製。
什麼是 Spring Boot 框架?

Spring Boot是由Pivotal團隊提供的全新框架,其設計目的是用來簡化新Spring應用的初始搭建以及開發過程。

Spring Boot框架使用了特定的方式來進行配置,從而使開發人員不再需要定義樣板化的配置。通過這種方式,Spring Boot致力於在蓬勃發展的快速應用開發領域(rapid application development)成爲領導者。

2014年4月發佈第一個版本的全新開源的Spring Boot輕量級框架。它基於Spring4.0設計,不僅繼承了Spring框架原有的優秀特性,而且還通過簡化配置來進一步簡化了Spring應用的整個搭建和開發過程。

另外Spring Boot通過集成大量的框架使得依賴包的版本衝突,以及引用的不穩定性等問題得到了很好的解決。
什麼是 Spring Cloud 框架?

Spring Cloud是一系列框架的有序集合,它利用Spring Boot的開發便利性巧妙地簡化了分佈式系統基礎設施的開發,如服務發現註冊、配置中心、消息總線、負載均衡、斷路器、數據監控等,都可以用Spring Boot的開發風格做到一鍵啓動和部署。

Spring Cloud並沒有重複製造輪子,它只是將各家公司開發的比較成熟、經得起實際考驗的服務框架組合起來,通過Spring Boot風格進行再封裝屏蔽掉了複雜的配置和實現原理,最終給開發者留出了一套簡單易懂、易部署和易維護的分佈式系統開發工具包。

Spring Cloud的子項目,大致可分成兩大類:

一類是對現有成熟框架“Spring Boot化”的封裝和抽象,也是數量最多的項目;

第二類是開發一部分分佈式系統的基礎設施的實現,如Spring Cloud Stream扮演的是kafka, ActiveMQ這樣的角色。

對於快速實踐微服務的開發者來說,第一類子項目已經基本足夠使用,如:

1)Spring Cloud Netflix是對Netflix開發的一套分佈式服務框架的封裝,包括服務的發現和註冊,負載均衡、斷路器、REST客戶端、請求路由等;

2)Spring Cloud Config將配置信息中央化保存, 配置Spring Cloud Bus可以實現動態修改配置文件;

3)Spring Cloud Bus分佈式消息隊列,是對Kafka, MQ的封裝;

4)Spring Cloud Security對Spring Security的封裝,並能配合Netflix使用;

5)Spring Cloud Zookeeper對Zookeeper的封裝,使之能配置其它Spring Cloud的子項目使用;

6)Spring Cloud Eureka是Spring Cloud Netflix微服務套件中的一部分,它基於Netflix Eureka做了二次封裝,主要負責完成微服務架構中的服務治理功能。注意的是從2.x起,官方不會繼續開源,若需要使用2.x,風險還是有的。但是我覺得問題並不大,eureka目前的功能已經非常穩定,就算不升級,服務註冊/發現這些功能已經夠用。consul是個不錯的替代品,還有其他替代組件,後續篇幅會有詳細贅述或關注微信公衆號“Java精選”,有詳細替代方案源碼分享。
Spring Cloud 框架有哪些優缺點?

**Spring Cloud優點:**

1)服務拆分粒度更細,有利於資源重複利用,有利於提高開發效率,每個模塊可以獨立開發和部署、代碼耦合度低;

2)可以更精準的制定優化服務方案,提高系統的可維護性,每個服務可以單獨進行部署,升級某個模塊的時候只需要單獨部署對應的模塊服務即可,效率更高;

3)微服務架構採用去中心化思想,服務之間採用Restful等輕量級通訊,比ESB更輕量,模塊專一性提升,每個模塊只需要關心自己模塊所負責的功能即可,不需要關心其他模塊業務,專一性更高,更便於功能模塊開發和拓展;

4)技術選型不再單一,由於每個模塊是單獨開發並且部署,所以每個模塊可以有更多的技術選型方案,如模塊1數據庫選擇mysql,模塊2選擇用oracle也是可以的;


5)適於互聯網時代,產品迭代週期更短。系統穩定性以及性能提升,由於微服務是幾個服務共同組成的項目或者流程,因此相比傳統單一項目的優點就在於某個模塊提供的服務宕機過後不至於整個系統癱瘓掉,而且微服務裏面的容災和服務降級機制也能大大提高項目的穩定性;從性能而言,由於每個服務是單獨部署,所以每個模塊都可以有自己的一套運行環境,當某個服務性能低下的時候可以對單個服務進行配置或者代碼上的升級,從而達到提升性能的目的。

**Spring Cloud缺點:**

1)微服務過多,治理成本高,不利於維護系統,服務之間接口調用成本增加,相比以往單項目的時候調用某個方法或者接口可以直接通過本地方法調用就能夠完成,但是當切換成微服務的時候,調用方式就不能用以前的方式進行調試、目前主流採用的技術有http api接口調用、RPC、WebService等方式進行調用,調用成本比單個項目的時候有所增加;

2)分佈式系統開發的成本高(容錯,分佈式事務等)對團隊挑戰大

2)獨立的數據庫,微服務產生事務一致性的問題,由於各個模塊用的技術都各不相同、而且每個服務都會高併發進行調用,就會存在分佈式事務一致性的問題;

3)分佈式部署,造成運營的成本增加、相比較單個應用的時候,運營人員只需要對單個項目進行部署、負載均衡等操作,但是微服務的每個模塊都需要這樣的操作,增加了運行時的成本;

4)由於整個系統是通過各個模塊組合而成的,因此當某個服務進行變更時需要對前後涉及的所有功能進行迴歸測試,測試功能不能僅限於當個模塊,增加了測試難度和測試成本;

總體來說優點大過於缺點,目前看來Spring Cloud是一套非常完善的微服務框架,目前很多企業開始用微服務,Spring Cloud的優勢是顯而易見的。
什麼是消息隊列?

MQ全稱爲Message Queue 消息隊列(MQ)是一種應用程序對應用程序的通信方法。

消息隊列中間件是分佈式系統中重要的組件,主要解決應用耦合,異步消息,流量削鋒等問題。實現高性能,高可用,可伸縮和最終一致性架構。是大型分佈式系統不可缺少的中間件。

MQ是消費生產者模型的一個典型的代表,一端往消息隊列中不斷寫入消息,而另一端則可以讀取隊列中的消息。

消息生產者只需要把消息發佈到MQ中而不用管誰來獲取,消息消費者只管從MQ中獲取消息而不管是誰發佈的消息,這樣生產者和消費者雙方都不用清楚對方的存在。 

目前在生產環境,使用較多的消息隊列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。
消息隊列有哪些應用場景?

列舉消息隊列場景,異步處理,應用解耦,流量削鋒,日誌處理和消息通訊五個場景。

**1、異步處理場景**

用戶註冊後,需要發註冊郵件和註冊短信。傳統的做法有兩種

1)串行方式:將註冊信息寫入數據庫成功後,發送註冊郵件,再發送註冊短信。以上三個任務全部完成後,返回給客戶端。

2)並行方式:將註冊信息寫入數據庫成功後,發送註冊郵件的同時,發送註冊短信。以上三個任務完成後,返回給客戶端。與串行的差別是,並行的方式可以提高處理的時間。

按照以上描述,用戶的響應時間相當於是註冊信息寫入數據庫的時間,也就是50毫秒。註冊郵件,發送短信寫入消息隊列後,直接返回,因此寫入消息隊列的速度很快,基本可以忽略,因此用戶的響應時間可能是50毫秒。因此採用消息隊列的方式,系統的吞吐量提高到每秒20 QPS。比串行提高了3倍,比並行提高了兩倍。

**2、應用解耦場景**

用戶購買物品下單後,訂單系統需要通知庫存系統。傳統的做法是訂單系統調用庫存系統的接口。該模式的缺點:

1)假如庫存系統無法訪問,則訂單減庫存將失敗,從而導致訂單失敗;

2)訂單系統與庫存系統耦合;

如何解決以上問題呢?

訂單系統:用戶下單後,訂單系統完成持久化處理,將消息寫入消息隊列,返回用戶訂單下單成功。

庫存系統:訂閱下單的消息,採用拉/推的方式,獲取下單信息,庫存系統根據下單信息,進行庫存操作。

假如:在下單時庫存系統不能正常使用。也不影響正常下單,因爲下單後訂單系統寫入消息隊列就不再關心其他的後續操作,從而實現訂單系統與庫存系統的應用解耦。

**3、流量削鋒場景**

流量削鋒也是消息隊列中的常用場景,一般在秒殺或團搶活動中被廣泛應用。

秒殺活動,一般會因爲流量併發過大,導致流量暴增,應用宕機,從而爲解決該問題,一般在應用前端加入消息隊列。

控制活動的人數,緩解短時間內高流量壓垮應用。當用戶的請求,服務器接收後,先寫入消息隊列。如消息隊列長度超過最大數量,則直接拋棄用戶請求或跳轉到錯誤頁面,而其秒殺業務根據消息隊列中的請求信息,再做後續處理。

**4、日誌處理場景**

日誌處理是指將消息隊列用在日誌處理中,比如Kafka的應用,解決大量日誌傳輸的問題。日誌採集客戶端,負責日誌數據採集,定時寫受寫入Kafka隊列,而Kafka消息隊列,負責日誌數據的接收,存儲和轉發,日誌處理應用訂閱並消費kafka隊列中的日誌數據。

**5、消息通訊**

消息通訊是指消息隊列一般都內置了高效的通信機制,因此也可以用在純的消息通訊。比如實現點對點消息隊列,或者聊天室等。
什麼是 Linux 操作系統?

Linux全稱GNU/Linux,是一套免費使用和自由傳播的類Unix操作系統,是一個基於POSIX的多用戶、多任務、支持多線程和多CPU的操作系統。

伴隨着互聯網的發展,Linux得到了來自全世界軟件愛好者、組織、公司的支持。它除了在服務器方面保持着強勁的發展勢頭以外,在個人電腦、嵌入式系統上都有着長足的進步。使用者不僅可以直觀地獲取該操作系統的實現機制,而且可以根據自身的需要來修改完善Linux,使其最大化地適應用戶的需要。 

Linux不僅系統性能穩定,而且是開源軟件。其核心防火牆組件性能高效、配置簡單,保證了系統的安全。

在很多企業網絡中,爲了追求速度和安全,Linux不僅僅是被網絡運維人員當作服務器使用,甚至當作網絡防火牆,這是Linux的一大亮點。

Linux具有開放源碼、沒有版權、技術社區用戶多等特點,開放源碼使得用戶可以自由裁剪,靈活性高,功能強大,成本低。尤其系統中內嵌網絡協議棧,經過適當的配置就可實現路由器的功能。這些特點使得Linux成爲開發路由交換設備的理想開發平臺。
什麼是數據結構?

數據結構是計算機存儲、組織數據的方式。數據結構是指相互之間存在一種或多種特定關係的數據元素的集合。通常情況下,精心選擇的數據結構可以帶來更高的運行或者存儲效率。數據結構往往同高效的檢索算法和索引技術有關。

數據結構(data structure)是帶有結構特性的數據元素的集合,它研究的是數據的邏輯結構和數據的物理結構以及它們之間的相互關係,並對這種結構定義相適應的運算,設計出相應的算法,並確保經過這些運算以後所得到的新結構仍保持原來的結構類型。

簡而言之,數據結構是相互之間存在一種或多種特定關係的數據元素的集合,即帶“結構”的數據元素的集合。“結構”就是指數據元素之間存在的關係,分爲邏輯結構和存儲結構。

數據的邏輯結構和物理結構是數據結構的兩個密切相關的方面,同一邏輯結構可以對應不同的存儲結構。算法的設計取決於數據的邏輯結構,而算法的實現依賴於指定的存儲結構。

數據結構的研究內容是構造複雜軟件系統的基礎,它的核心技術是分解與抽象。

通過分解可以劃分出數據的3個層次;再通過抽象,捨棄數據元素的具體內容,就得到邏輯結構。類似地,通過分解將處理要求劃分成各種功能,再通過抽象捨棄實現細節,就得到運算的定義。

上述兩個方面的結合可以將問題變換爲數據結構。這是一個從具體(即具體問題)到抽象(即數據結構)的過程。

然後,通過增加對實現細節的考慮進一步得到存儲結構和實現運算,從而完成設計任務。這是一個從抽象(即數據結構)到具體(即具體實現)的過程。
什麼是設計模式?

設計模式(Design pattern) 是解決軟件開發某些特定問題而提出的一些解決方案也可以理解成解決問題的一些思路,通過設計模式可以幫助我們增強代碼的可重用性、可擴充性、 可維護性、靈活性好。使用設計模式最終的目的是實現代碼的高內聚和低耦合。

高內聚低耦合是軟件工程中的概念,是判斷軟件設計好壞的標準,主要用於程序的面向對象的設計,主要看類的內聚性是否高,耦合度是否低。

目的是使程序模塊的可重用性、移植性大大增強。

通常程序結構中各模塊的內聚程度越高,模塊間的耦合程度就越低。

內聚是從功能角度來度量模塊內的聯繫,一個好的內聚模塊應當恰好做一件事,它描述的是模塊內的功能聯繫;耦合是軟件結構中各模塊之間相互連接的一種度量,耦合強弱取決於模塊間接口的複雜程度、進入或訪問一個模塊的點以及通過接口的數據。
什麼是 Zookeeper?

ZooKeeper由雅虎研究院開發,ZooKeeper是一個分佈式的,開放源碼的分佈式應用程序協調服務,是Google的Chubby一個開源的實現,後來託管到Apache,是Hadoop和Hbase的重要組件。

ZooKeeper是一個經典的分佈式數據一致性解決方案,致力於爲分佈式應用提供一個高性能、高可用,且具有嚴格順序訪問控制能力的分佈式協調服務。

ZooKeeper的目標就是封裝好複雜易出錯的關鍵服務,將簡單易用的接口和性能高效、功能穩定的系統提供給用戶。

ZooKeeper包含一個簡單的原語集,提供Java和C的接口。

ZooKeeper代碼版本中,提供了分佈式獨享鎖、選舉、隊列的接口,代碼在$zookeeper_home\src\recipes。其中分佈鎖和隊列有Java和C兩個版本,選舉只有Java版本。

於2010年11月正式成爲Apache的頂級項目。它是一個爲分佈式應用提供一致性服務的軟件,提供的功能包括:配置維護、域名服務、分佈式同步、組服務等。

分佈式應用程序可以基於ZooKeeper實現數據發佈與訂閱、負載均衡、命名服務、分佈式協調與通知、集羣管理、Leader選舉、分佈式鎖、分佈式隊列等功能。
應用服務 8080 端口被意外佔用如何解決?

1)按鍵盤WIN+R鍵,打開後在運行框中輸入“CMD”命令,點擊確定。

2)在CMD窗口,輸入“netstat -ano”命令,按回車鍵,即可查看所有的端口占用情況。

3)找到本地地址一覽中類似“0.0.0.0:8080”信息,通過此列查看8080端口對應的程序PID。

4)打開任務管理器,詳細信息找到對應的應用PID(若不存在通過設置可以調出來),右鍵結束任務即可。
什麼是 Dubbo 框架?

Dubbo(讀音[ˈdʌbəʊ])是阿里巴巴公司開源的一個高性能優秀的服務框架,使得應用可通過高性能的 RPC 實現服務的輸出和輸入功能,可以和Spring框架無縫集成。

Dubbo提供了六大核心能力:面向接口代理的高性能RPC調用,智能容錯和負載均衡,服務自動註冊和發現,高度可擴展能力,運行期流量調度,可視化的服務治理與運維。

Dubbo是一款高性能、輕量級的開源Java RPC框架,它提供了三大核心能力:面向接口的遠程方法調用,智能容錯和負載均衡,以及服務自動註冊和發現。

**核心組件**

Remoting: 網絡通信框架,實現了 sync-over-async 和request-response 消息機制;

RPC: 一個遠程過程調用的抽象,支持負載均衡、容災和集羣功能;

Registry: 服務目錄框架用於服務的註冊和服務事件發佈和訂閱。
什麼是 Maven?

Maven即爲項目對象模型(POM),它可以通過一小段描述信息來管理項目的構建,報告和文檔的項目管理工具軟件。

Maven 除了以程序構建能力爲特色之外,還提供高級項目管理工具。

由於Maven的缺省構建規則有較高的可重用性,所以常常用兩三行Maven構建腳本就可以構建簡單的項目。

由於Maven面向項目的方法,許多Apache Jakarta項目發文時使用Maven,而且公司項目採用Maven的比例在持續增長,相比較Gradle,在之後的篇幅中會說明,歡迎大家關注微信公衆號“Java精選”。

Maven這個單詞來自於意第緒語(猶太語),意爲知識的積累,最初在Jakata Turbine項目中用來簡化構建過程。

當時有一些項目(有各自Ant build文件),僅有細微的差別,而JAR文件都由CVS來維護。於是希望有一種標準化的方式構建項目,一個清晰的方式定義項目的組成,一個容易的方式發佈項目的信息,以及一種簡單的方式在多個項目中共享JARs。
應用層中常見的協議都有哪些?

應用層協議(application layer protocol)定義了運行在不同端系統上的應用程序進程如何相互傳遞報文。

**應用層協議**

1)DNS:一種用以將域名轉換爲IP地址的Internet服務,域名系統DNS是因特網使用的命名系統,用來把便於人們使用的機器名字轉換爲IP地址。

現在頂級域名TLD分爲三大類:國家頂級域名nTLD;通用頂級域名gTLD;基礎結構域名。

域名服務器分爲四種類型:根域名服務器;頂級域名服務器;本地域名服務器;權限域名服務器。

2)FTP:文件傳輸協議FTP是因特網上使用得最廣泛的文件傳送協議。FTP提供交互式的訪問,允許客戶指明文件類型與格式,並允許文件具有存取權限。

基於客戶服務器模式,FTP協議包括兩個組成部分,一是FTP服務器,二是FTP客戶端,提供交互式的訪問面向連接,使用TCP/IP可靠的運輸服務,主要功能:減少/消除不同操作系統下文件的不兼容性 。

3)telnet遠程終端協議:telnet是一個簡單的遠程終端協議,它也是因特網的正式標準。又稱爲終端仿真協議。

4)HTTP:超文本傳送協議,是面向事務的應用層協議,它是萬維網上能夠可靠地交換文件的重要基礎。http使用面向連接的TCP作爲運輸層協議,保證了數據的可靠傳輸。

5)電子郵件協議SMTP:即簡單郵件傳送協議。SMTP規定了在兩個相互通信的SMTP進程之間應如何交換信息。SMTP通信的三個階段:建立連接、郵件傳送、連接釋放。

6)POP3:郵件讀取協議,POP3(Post Office Protocol 3)協議通常被用來接收電子郵件。

7)遠程登錄協議(Telnet):用於實現遠程登錄功能。

8)SNMP:簡單網絡管理協議。由三部分組成:SNMP本身、管理信息結構SMI和管理信息MIB。SNMP定義了管理站和代理之間所交換的分組格式。SMI定義了命名對象類型的通用規則,以及把對象和對象的值進行編碼。MIB在被管理的實體中創建了命名對象,並規定類型。
Java 中的關鍵字都有哪些?

1)48個關鍵字:abstract、assert、boolean、break、byte、case、catch、char、class、continue、default、do、double、else、enum、extends、final、finally、float、for、if、implements、import、int、interface、instanceof、long、native、new、package、private、protected、public、return、short、static、strictfp、super、switch、synchronized、this、throw、throws、transient、try、void、volatile、while。

2)2個保留字(目前未使用,以後可能用作爲關鍵字):goto、const。

3)3個特殊直接量(直接量是指在程序中通過源代碼直接給出的值):true、false、null。
Java 中基本類型都有哪些?

Java的類型分成兩種,一種是基本類型,一種是引用類型。其中Java基本類型共有八種。

基本類型可以分爲三大類:字符類型char,布爾類型boolean以及數值類型byte、short、int、long、float、double。

數值類型可以分爲整數類型byte、short、int、long和浮點數類型float、double。

JAVA中的數值類型不存在無符號的,它們的取值範圍是固定的,不會隨着機器硬件環境或操作系統的改變而改變。實際上《Thinking in Java》一書作者,提到Java中還存在另外一種基本類型void,它也有對應的包裝類 java.lang.Void,因爲Void是不能new,也就是不能在堆裏面分配空間存對應的值,所以將Void歸成基本類型,也有一定的道理。

8種基本類型表示範圍如下:

byte:8位,最大存儲數據量是255,存放的數據範圍是-128~127之間。

short:16位,最大數據存儲量是65536,數據範圍是-32768~32767之間。

int:32位,最大數據存儲容量是2的32次方減1,數據範圍是負的2的31次方到正的2的31次方減1。

long:64位,最大數據存儲容量是2的64次方減1,數據範圍爲負的2的63次方到正的2的63次方減1。

float:32位,數據範圍在3.4e-45~1.4e38,直接賦值時必須在數字後加上f或F。

double:64位,數據範圍在4.9e-324~1.8e308,賦值時可以加d或D也可以不加。

boolean:只有true和false兩個取值。

char:16位,存儲Unicode碼,用單引號賦值。
爲什麼 Map 接口不繼承 Collection 接口?

1)Map提供的是鍵值對映射(即Key和value的映射),而Collection提供的是一組數據並不是鍵值對映射。

2)若果Map繼承了Collection接口,那麼所實現的Map接口的類到底是用Map鍵值對映射數據還是用Collection的一組數據呢?比如平常所用的hashMap、hashTable、treeMap等都是鍵值對,所以它繼承Collection是完全沒意義,而且Map如果繼承Collection接口的話,違反了面向對象的接口分離原則。

接口分離原則:客戶端不應該依賴它不需要的接口。

另一種定義是類間的依賴關係應該建立在最小的接口上。

接口隔離原則將非常龐大、臃腫的接口拆分成爲更小的和更具體的接口,這樣客戶將會只需要知道他們感興趣的方法。

接口隔離原則的目的是系統解開耦合,從而容易重構、更改和重新部署,讓客戶端依賴的接口儘可能地小。

3)Map和List、Set不同,Map放的是鍵值對,List、Set存放的是一個個的對象。說到底是因爲數據結構不同,數據結構不同,操作就不一樣,所以接口是分開的,還是接口分離原則。
Collection 和 Collections 有什麼區別?

java.util.Collection是一個集合接口。它提供了對集合對象進行基本操作的通用接口方法。Collection接口在Java 類庫中有很多具體的實現。

Collection接口的意義是爲各種具體的集合提供了最大化的統一操作方式。其直接繼承接口有List與Set。
> Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set

java.util.Collections是一個包裝類。它包含有各種有關集合操作的靜態多態方法。此類不能實例化,就像一個工具類,服務於Java的Collection框架。
堆和棧的概念,它們有什麼區別和聯繫?

在說堆和棧之前,我們先說一下JVM(虛擬機)內存的劃分:

Java程序在運行時都要開闢空間,任何軟件在運行時都要在內存中開闢空間,Java虛擬機運行時也是要開闢空間的。JVM運行時在內存中開闢一片內存區域,啓動時在自己的內存區域中進行更細緻的劃分,因爲虛擬機中每一片內存處理的方式都不同,所以要單獨進行管理。

JVM內存的劃分有五片:

1)寄存器;

2)本地方法區;

3)方法區;

4)棧內存;

5)堆內存。

**重點來說一下堆和棧:**

棧內存:棧內存首先是一片內存區域,存儲的都是局部變量,凡是定義在方法中的都是局部變量(方法外的是全局變量),for循環內部定義的也是局部變量,是先加載函數才能進行局部變量的定義,所以方法先進棧,然後再定義變量,變量有自己的作用域,一旦離開作用域,變量就會被釋放。棧內存的更新速度很快,因爲局部變量的生命週期都很短。

堆內存:存儲的是數組和對象(其實數組就是對象),凡是new建立的都是在堆中,堆中存放的都是實體(對象),實體用於封裝數據,而且是封裝多個(實體的多個屬性),如果一個數據消失,這個實體也沒有消失,還可以用,所以堆是不會隨時釋放的,但是棧不一樣,棧裏存放的都是單個變量,變量被釋放了,那就沒有了。堆裏的實體雖然不會被釋放,但是會被當成垃圾,Java有垃圾回收機制不定時的收取。

比如主函數裏的語句 int [] arr=new int [3];在內存中是怎麼被定義的:

主函數先進棧,在棧中定義一個變量arr,接下來爲arr賦值,但是右邊不是一個具體值,是一個實體。實體創建在堆裏,在堆裏首先通過new關鍵字開闢一個空間,內存在存儲數據的時候都是通過地址來體現的,地址是一塊連續的二進制,然後給這個實體分配一個內存地址。數組都是有一個索引,數組這個實體在堆內存中產生之後每一個空間都會進行默認的初始化(這是堆內存的特點,未初始化的數據是不能用的,但在堆裏是可以用的,因爲初始化過了,但是在棧裏沒有),不同的類型初始化的值不一樣。所以堆和棧裏就創建了變量和實體:

**那麼堆和棧是怎麼聯繫起來的呢?**

>剛剛說過給堆分配了一個地址,把堆的地址賦給arr,arr就通過地址指向了數組。所以arr想操縱數組時,就通過地址,而不是直接把實體都賦給它。這種我們不再叫他基本數據類型,而叫引用數據類型。稱爲arr引用了堆內存當中的實體。可以理解爲c或“c++”的指針,Java成長自“c++”和“c++”很像,優化了“c++”

如果當int [] arr=null;

arr不做任何指向,null的作用就是取消引用數據類型的指向。

當一個實體,沒有引用數據類型指向的時候,它在堆內存中不會被釋放,而被當做一個垃圾,在不定時的時間內自動回收,因爲Java有一個自動回收機制,(而“c++”沒有,需要程序員手動回收,如果不回收就越堆越多,直到撐滿內存溢出,所以Java在內存管理上優於“c++”)。自動回收機制(程序)自動監測堆裏是否有垃圾,如果有,就會自動的做垃圾回收的動作,但是什麼時候收不一定。

**所以堆與棧的區別很明顯:**

1)棧內存存儲的是局部變量而堆內存存儲的是實體;

2)棧內存的更新速度要快於堆內存,因爲局部變量的生命週期很短;


3)棧內存存放的變量生命週期一旦結束就會被釋放,而堆內存存放的實體會被垃圾回收機制不定時的回收。
Class.forName 和 ClassLoader 有什麼區別?

在java中對類進行加載可以使用Class.forName()和ClassLoader。
ClassLoader遵循雙親委派模型,最終調用啓動類加載器的類加載器,實現的功能是“通過一個類的全限定名來獲取描述此類的二進制字節流”,獲取到二進制流後放到JVM中。

Class.forName()方法實際上也是調用的ClassLoader來實現的。

通過分析源碼可以得出:最後調用的方法是forName()方法,方法中的第2個參數默認設置爲true,該參數表示是否對加載的類進行初始化,設置爲true時會對類進行初始化,這就意味着會執行類中的靜態代碼塊以及對靜態變量的賦值等操作。

也可以自行調用Class.forName(String name, boolean initialize,ClassLoader loader)方法手動選擇在加載類的時候是否要對類進行初始化。

JDK源碼中對參數initialize的描述是:if {@code true} the class will be initialized,大概意思是說:當值爲true,則加載的類將會被初始化。
爲什麼要使用設計模式?

1)設計模式是前人根據經驗總結出來的,使用設計模式,就相當於是站在了前人的肩膀上。

2)設計模式使程序易讀。熟悉設計模式的人應該能夠很容易讀懂運用設計模式編寫的程序。

3)設計模式能使編寫的程序具有良好的可擴展性,滿足系統設計的開閉原則。比如策略模式,就是將不同的算法封裝在子類中,在需要添加新的算法時,只需添加新的子類,實現規定的接口,即可在不改變現有系統源碼的情況下加入新的系統行爲。

4)設計模式能降低系統中類與類之間的耦合度。比如工廠模式,使依賴類只需知道被依賴類所實現的接口或繼承的抽象類,使依賴類與被依賴類之間的耦合度降低。

5)設計模式能提高代碼的重用度。比如適配器模式,就能將系統中已經存在的符合新需求的功能代碼兼容新的需求提出的接口 。

6)設計模式能爲常見的一些問題提供現成的解決方案。

7)設計模式增加了重用代碼的方式。比如裝飾器模式,在不使用繼承的前提下重用系統中已存在的代碼。
爲什麼 String 類型是被 final 修飾的?

**1、爲了實現字符串池**

final修飾符的作用:final可以修飾類,方法和變量,並且被修飾的類或方法,被final修飾的類不能被繼承,即它不能擁有自己的子類,被final修飾的方法不能被重寫, final修飾的變量,無論是類屬性、對象屬性、形參還是局部變量,都需要進行初始化操作。

String爲什麼要被final修飾主要是爲了”安全性“和”效率“的原因。

final修飾的String類型,代表了String不可被繼承,final修飾的char[]代表了被存儲的數據不可更改性。雖然final修飾的不可變,但僅僅是引用地址不可變,並不代表了數組本身不會改變。

爲什麼保證String不可變呢?

因爲只有字符串是不可變,字符串池纔有可能實現。字符串池的實現可以在運行時節約很多heap空間,不同的字符串變量都指向池中的同一個字符串。但如果字符串是可變的,那麼String interning將不能實現,反之變量改變它的值,那麼其它指向這個值的變量值也會隨之改變。

如果字符串是可變,會引起很嚴重的安全問題。如數據庫的用戶名、密碼都是以字符串的形式傳入來獲得數據庫的連接或在socket編程中,主機名和端口都是以字符串的形式傳入。因爲字符串是不可變的,所以它的值是不可改變的,否則改變字符串指向的對象值,將造成安全漏洞。

**2、爲了線程安全**

因爲字符串是不可變的,所以是多線程安全的,同一個字符串實例可以被多個線程共享。這樣便不用因爲線程安全問題而使用同步。字符串自己便是線程安全的。

**3、爲了實現String可創建HashCode不可變性**

因爲字符串是不可變的,所以在它創建的時候HashCode就被緩存了,不需要重新計算。使得字符串很適合作爲Map鍵值對中的鍵,字符串的處理速度要快過其它的鍵對象。這就是HashMap中的鍵往往都使用字符串。
​final 關鍵字的基本用法?

在Java中final關鍵字可以用來修飾類、方法和變量(包括成員變量和局部變量)。下面從這三個方面來了解一下final關鍵字的基本用法。

**1、修飾類**

當用final修飾一個類時,表明這個類不能被繼承。也就是說,如果一個類你永遠不會讓他被繼承,就可以用final進行修飾。final類中的成員變量可以根據需要設爲final,但是要注意final類中的所有成員方法都會被隱式地指定爲final方法。

在使用final修飾類的時候,要注意謹慎選擇,除非這個類真的在以後不會用來繼承或者出於安全的考慮,儘量不要將類設計爲final類。

**2、修飾方法**

下面這段話摘自《Java編程思想》第四版第143頁:

“使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率。在早期的Java實現版本中,會將final方法轉爲內嵌調用。但是如果方法過於龐大,可能看不到內嵌調用帶來的任何性能提升。在最近的Java版本中,不需要使用final方法進行這些優化了。“

因此,如果只有在想明確禁止該方法在子類中被覆蓋的情況下才將方法設置爲final的。即父類的final方法是不能被子類所覆蓋的,也就是說子類是不能夠存在和父類一模一樣的方法的。

final修飾的方法表示此方法已經是“最後的、最終的”含義,亦即此方法不能被重寫(可以重載多個final修飾的方法)。此處需要注意的一點是:因爲重寫的前提是子類可以從父類中繼承此方法,如果父類中final修飾的方法同時訪問控制權限爲private,將會導致子類中不能直接繼承到此方法,因此,此時可以在子類中定義相同的方法名和參數,此時不再產生重寫與final的矛盾,而是在子類中重新定義了新的方法。(注:類的private方法會隱式地被指定爲final方法。)

**3、修飾變量**

final成員變量表示常量,只能被賦值一次,賦值後值不再改變。
當final修飾一個基本數據類型時,表示該基本數據類型的值一旦在初始化後便不能發生變化;如果final修飾一個引用類型時,則在對其初始化之後便不能再讓其指向其他對象了,但該引用所指向的對象的內容是可以發生變化的。本質上是一回事,因爲引用的值是一個地址,final要求值,即地址的值不發生變化。

final修飾一個成員變量(屬性),必須要顯示初始化。這裏有兩種初始化方式,一種是在變量聲明的時候初始化;第二種方法是在聲明變量的時候不賦初值,但是要在這個變量所在的類的所有的構造函數中對這個變量賦初值。

當函數的參數類型聲明爲final時,說明該參數是隻讀型的。即你可以讀取使用該參數,但是無法改變該參數的值。
如何理解 final 關鍵字?

1)類的final變量和普通變量有什麼區別?

當用final作用於類的成員變量時,成員變量(注意是類的成員變量,局部變量只需要保證在使用之前被初始化賦值即可)必須在定義時或者構造器中進行初始化賦值,而且final變量一旦被初始化賦值之後,就不能再被賦值了。

2)被final修飾的引用變量指向的對象內容可變嗎?

引用變量被final修飾之後,雖然不能再指向其他對象,但是它指向的對象的內容是可變的

3)final參數的問題

在實際應用中,我們除了可以用final修飾成員變量、成員方法、類,還可以修飾參數、若某個參數被final修飾了,則代表了該參數是不可改變的。如果在方法中我們修改了該參數,則編譯器會提示你:

> The final local variable i cannot be assigned. It must be blank and not using a compound assignment。

java採用的是值傳遞,對於引用變量,傳遞的是引用的值,也就是說讓實參和形參同時指向了同一個對象,因此讓形參重新指向另一個對象對實參並沒有任何影響。
ArrayList 和 LinkedList 有什麼區別?

1)ArrayList是Array動態數組的數據結構,LinkedList是Link鏈表的數據結構,此外,它們兩個都是對List接口的實現。前者是數組隊列,相當於動態數組;後者爲雙向鏈表結構,也可當作堆棧、隊列、雙端隊列。

2)當隨機訪問List時(get和set操作),ArrayList比LinkedList的效率更高,因爲LinkedList是線性的數據存儲方式,所以需要移動指針從前往後依次查找。

3)當對數據進行增加和刪除的操作時(add和remove操作),LinkedList比ArrayList的效率更高,因爲ArrayList是數組,所以在其中進行增刪操作時,會對操作點之後所有數據的下標索引造成影響,需要進行數據的移動。

4)從利用效率來看,ArrayList自由性較低,因爲它需要手動設置固定大小的容量,但是它的使用比較方便,只需要創建,然後添加數據,通過調用下標進行使用;而LinkedList自由性較高,能夠動態的隨數據量的變化而變化,但是它不便於使用。

5)ArrayList主要控件開銷在於需要在List列表預留一定空間;而LinkList主要控件開銷在於需要存儲結點信息以及結點指針信息。
HashMap 和 HashTable 有什麼區別?

Hashtable是線程安全,而HashMap則非線程安全。

Hashtable所有實現方法添加了synchronized關鍵字來確保線程同步,因此相對而言HashMap性能會高一些,平時使用時若無特殊需求建議使用HashMap,在多線程環境下若使用HashMap需要使用Collections.synchronizedMap()方法來獲取一個線程安全的集合。

HashMap允許使用null作爲key,不過建議還是儘量避免使用null作爲key。HashMap以null作爲key時,總是存儲在table數組的第一個節點上。而Hashtable則不允許null作爲key。

HashMap繼承了AbstractMap,HashTable繼承Dictionary抽象類,兩者均實現Map接口。

HashMap的初始容量爲16,Hashtable初始容量爲11,兩者的填充因子默認都是0.75。

HashMap擴容時是當前容量翻倍即:capacity\*2,Hashtable擴容時是容量翻倍+1即:capacity\*2+1。

HashMap和Hashtable的底層實現都是數組+鏈表結構實現。
線程的生命週期包括哪幾個階段?

當線程被創建並啓動以後,它既不是一啓動就進入了執行狀態,也不是一直處於執行狀態。在線程的生命週期中,它要經過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)五種狀態。當線程啓動以後,它不能一直佔用着CPU獨自運行,所以CPU需要在多條線程之間切換,於是線程狀態也會多次在運行、阻塞之間切換。

線程的生命週期包含5個階段,包括:新建、就緒、運行、阻塞、死亡。

**新建(new Thread)**

當創建Thread類的一個實例(對象)時,此線程進入新建狀態(未被啓動)。

**就緒(runnable)**

線程已經被啓動,正在等待被分配給CPU時間片,也就是說此時線程正在就緒隊列中排隊等候得到CPU資源

**運行(running)**

線程獲得CPU資源正在執行任務(run()方法),此時除非此線程自動放棄CPU資源或者有優先級更高的線程進入,線程將一直運行到結束。

**堵塞(blocked)**

由於某種原因導致正在運行的線程讓出CPU並暫停自己的執行,即進入堵塞狀態。

正在睡眠:用sleep(long t) 方法可使線程進入睡眠方式。一個睡眠着的線程在指定的時間過去可進入就緒狀態。

正在等待:調用wait()方法。(調用motify()方法回到就緒狀態)

被另一個線程所阻塞:調用suspend()方法。(調用resume()方法恢復)

**死亡(dead)**
當線程執行完畢或被其它線程殺死,線程就進入死亡狀態,這時線程不可能再進入就緒狀態等待執行。

自然終止:正常運行run()方法後終止

異常終止:調用stop()方法讓一個線程終止運行
Thread 類中的 start() 和 run() 方法有什麼區別?

Thread類中通過start()方法來啓動一個線程,此時線程處於就緒狀態,可以被JVM來調度執行,在調度過程中,JVM通過調用Thread類的run()方法來完成實際的業務邏輯,當run()方法結束後,此線程就會終止,所以通過start()方法可以達到多線程的目的。

如果直接調用線程類的run()方法,會被當做一個普通的函數調用,程序中仍然只有主線程這一個線程,即start()方法呢能夠異步的調用run()方法,但是直接調用run()方法確實同步的,無法達到多線程的目的。
notify 和 notifyAll 有什麼區別?

Java中提供了notify()和notifyAll()兩個方法來喚醒在某些條件下等待的線程。

當調用notify()方法時,只有一個等待線程會被喚醒而且它不能保證哪個線程會被喚醒,這取決於線程調度器。

當調用notifyAll()方法時,等待該鎖的所有線程都會被喚醒,但是在執行剩餘的代碼之前,所有被喚醒的線程都將爭奪鎖。

如果線程調用了對象的wait()方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。

當有線程調用了對象的notifyAll()方法(喚醒所有 wait 線程)或notify()方法(只隨機喚醒一個 wait 線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。

也就是說,調用了notify後只要一個線程會由等待池進入鎖池,而notifyAll會將該對象等待池內的所有線程移動到鎖池中,等待鎖競爭優先級高的線程競爭到對象鎖的概率大,若某線程沒有競爭到該對象鎖,它將會留在鎖池中,唯有線程再次調用wait()方法,纔會重新回到等待池中。

而競爭到對象鎖的線程則繼續往下執行,直到執行完了synchronized代碼塊,釋放掉該對象鎖,此時鎖池中的線程會繼續競爭該對象鎖。

因此,notify()和notifyAll()之間的關鍵區別在於notify()只會喚醒一個線程,而notifyAll()方法將喚醒所有線程。
什麼是樂觀鎖,什麼是悲觀鎖?

**樂觀鎖**

樂觀鎖的意思是樂觀思想,即認爲讀多寫少,遇到併發寫的可能性低,每次獲取數據時都認爲不會被修改,因此不會上鎖,但是在更新操作時會判斷有沒有去更新這個數據,採取在寫時先讀出當前版本號,然後加鎖操作(比較跟上一次的版本號,如果一樣則更新),如果失敗則要重複讀比較寫的操作。

Java中的樂觀鎖基本上都是通過CAS操作實現的,CAS是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,反之失敗。

**悲觀鎖**

悲觀鎖的意思是悲觀思想,即認爲寫多,遇到併發寫的可能性高,每次獲取數據時都認爲會被修改,因此每次在讀寫數據時都會上鎖,在讀寫數據時就會block直到拿到鎖。

Java中的悲觀鎖就是Synchronized、AQS框架下的鎖則是先嚐試CAS樂觀鎖去獲取鎖,獲取不到,纔會轉換爲悲觀鎖,如RetreenLock。
Java 中 volatile 關鍵字有什麼作用?

Java語言提供了弱同步機制,即volatile變量,以確保變量的更新通知其他線程。

volatile變量具備變量可見性、禁止重排序兩種特性。

volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值。

volatile變量的兩種特性:

**變量可見性**

保證該變量對所有線程可見,這裏的可見性指的是當一個線程修改了變量的值,那麼新的值對於其他線程是可以立即獲取的。

**禁止重排序**

volatile禁止了指令重排。比sychronized更輕量級的同步鎖。在訪問volatile 變量時不會執行加鎖操作,因此也就不會使執行線程阻塞,因此volatile變量是一種比sychronized關鍵字更輕量級的同步機制。

volatile適合場景:一個變量被多個線程共享,線程直接給這個變量賦值。

當對非volatile 變量進行讀寫的時候,每個線程先從內存拷貝變量到CPU緩存中。如果計算機有多個CPU,每個線程可能在不同的CPU上被處理,這意味着每個線程可以拷貝到不同CPU cache中。而聲明變量是volatile的,JVM 保證了每次讀變量都從內存中讀,跳過CPU cache這一步。

**適用場景**

值得說明的是對volatile變量的單次讀/寫操作可以保證原子性的,如long和double類型變量,但是並不能保證“i++”這種操作的原子性,因爲本質上i++是讀、寫兩次操作。在某些場景下可以代替Synchronized。但是,volatile的不能完全取代Synchronized的位置,只有在一些特殊的場景下,才能適用volatile。

總體來說,需要必須同時滿足下面兩個條件時才能保證併發環境的線程安全:

1)對變量的寫操作不依賴於當前值(比如 “i++”),或者說是單純的變量賦值(boolean flag = true)。

2)該變量沒有包含在具有其他變量的不變式中,也就是說,不同的volatile變量之間,不 能互相依賴。只有在狀態真正獨立於程序內其他內容時才能使用volatile。
Spring 中常用的註解包含哪些?

**1)聲明bean的註解**

@Component 組件,沒有明確的角色

@Service 在業務邏輯層使用(service層)

@Repository 在數據訪問層使用(dao層)

@Controller 在展現層使用,控制器的聲明(C*上使用)

**2)注入bean的註解**

@Autowired:由Spring提供

@Inject:由JSR-330提供

@Resource:由JSR-250提供

都可以註解在set方法和屬性上,推薦註解在屬性上(一目瞭然,少寫代碼)。

**3)java配置類相關注解**

@Configuration 聲明當前類爲配置類,相當於xml形式的Spring配置(類上使用)

@Bean 註解在方法上,聲明當前方法的返回值爲一個bean,替代xml中的方式(方法上使用)

@Configuration 聲明當前類爲配置類,其中內部組合了@Component註解,表明這個類是一個bean(類上使用)

@ComponentScan 用於對Component進行掃描,相當於xml中的(類上使用)

@WishlyConfiguration 爲@Configuration與@ComponentScan的組合註解,可以替代這兩個註解

**4)切面(AOP)相關注解**

Spring支持AspectJ的註解式切面編程。

@Aspect 聲明一個切面(類上使用)

使用@After、@Before、@Around定義建言(advice),可直接將攔截規則(切點)作爲參數。

@After 在方法執行之後執行(方法上使用)

@Before 在方法執行之前執行(方法上使用)

@Around 在方法執行之前與之後執行(方法上使用)

@PointCut 聲明切點

在java配置類中使用@EnableAspectJAutoProxy註解開啓Spring對AspectJ代理的支持(類上使用)

**5)@Bean的屬性支持**

@Scope 設置Spring容器如何新建Bean實例(方法上使用,得有@Bean)

其設置類型包括:

Singleton (單例,一個Spring容器中只有一個bean實例,默認模式),

Protetype (每次調用新建一個bean),

Request (web項目中,給每個http request新建一個bean),

Session (web項目中,給每個http session新建一個bean),

GlobalSession(給每一個 global http session新建一個Bean實例)

@StepScope 在Spring Batch中還有涉及

@PostConstruct 由JSR-250提供,在構造函數執行完之後執行,等價於xml配置文件中bean的initMethod

@PreDestory 由JSR-250提供,在Bean銷燬之前執行,等價於xml配置文件中bean的destroyMethod

**6)@Value註解**

@Value 爲屬性注入值(屬性上使用)

**7)環境切換**

@Profile 通過設定Environment的ActiveProfiles來設定當前context需要使用的配置環境。(類或方法上使用)

@Conditional Spring4中可以使用此註解定義條件話的bean,通過實現Condition接口,並重寫matches方法,從而決定該bean是否被實例化。(方法上使用)

**8)異步相關**

@EnableAsync 配置類中,通過此註解開啓對異步任務的支持,敘事性AsyncConfigurer接口(類上使用)

@Async 在實際執行的bean方法使用該註解來申明其是一個異步任務(方法上或類上所有的方法都將異步,需要@EnableAsync開啓異步任務)

**9)定時任務相關**

@EnableScheduling 在配置類上使用,開啓計劃任務的支持(類上使用)

@Scheduled 來申明這是一個任務,包括cron,fixDelay,fixRate等類型(方法上,需先開啓計劃任務的支持)

**10)@Enable*註解說明**

註解主要用來開啓對xxx的支持。

@EnableAspectJAutoProxy 開啓對AspectJ自動代理的支持

@EnableAsync 開啓異步方法的支持

@EnableScheduling 開啓計劃任務的支持

@EnableWebMvc 開啓Web MVC的配置支持

@EnableConfigurationProperties 開啓對@ConfigurationProperties註解配置Bean的支持

@EnableJpaRepositories 開啓對SpringData JPA Repository的支持

@EnableTransactionManagement 開啓註解式事務的支持

@EnableTransactionManagement 開啓註解式事務的支持

@EnableCaching 開啓註解式的緩存支持

**11)測試相關注解**

@RunWith 運行器,Spring中通常用於對JUnit的支持

@ContextConfiguration 用來加載配置ApplicationContext,其中classes屬性用來加載配置類
Spring MVC 中常用的註解包含哪些?

@EnableWebMvc 在配置類中開啓Web MVC的配置支持,如一些ViewResolver或者MessageConverter等,若無此句,重寫WebMvcConfigurerAdapter方法(用於對SpringMVC的配置)。

@Controller 聲明該類爲SpringMVC中的Controller

@RequestMapping 用於映射Web請求,包括訪問路徑和參數(類或方法上)

@ResponseBody 支持將返回值放在response內,而不是一個頁面,通常用戶返回json數據(返回值旁或方法上)

@RequestBody 允許request的參數在request體中,而不是在直接連接在地址後面。(放在參數前)

@PathVariable 用於接收路徑參數,比如@RequestMapping(“/hello/{name}”)申明的路徑,將註解放在參數中前,即可獲取該值,通常作爲Restful的接口實現方法。

@RestController 該註解爲一個組合註解,相當於@Controller和@ResponseBody的組合,註解在類上,意味着,該Controller的所有方法都默認加上了@ResponseBody。

@ControllerAdvice 通過該註解,我們可以將對於控制器的全局配置放置在同一個位置,註解了@Controller的類的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute註解到方法上,

這對所有註解了 @RequestMapping的控制器內的方法有效。

@ExceptionHandler 用於全局處理控制器裏的異常

@InitBinder 用來設置WebDataBinder,WebDataBinder用來自動綁定前臺請求參數到Model中。

@ModelAttribute 本來的作用是綁定鍵值對到Model裏,在@ControllerAdvice中是讓全局的@RequestMapping都能獲得在此處設置的鍵值對。
爲什麼說 MyBatis 是半自動 ORM 映射?

ORM是Object和Relation之間的映射,包括Object->Relation和Relation->Object兩方面。Hibernate是個完整的ORM框架,而MyBatis完成的是Relation->Object,也就是其所說的Data Mapper Framework。

JPA是ORM映射標準,主流的ORM映射都實現了這個標準。MyBatis沒有實現JPA,它和ORM框架的設計思路不完全一樣。MyBatis是擁抱SQL,而ORM則更靠近面向對象,不建議寫SQL,實在要寫需用框架自帶的類SQL代替。MyBatis是SQL映射而不是ORMORM映射,當然ORM和MyBatis都是持久層框架。

最典型的ORM映射是Hibernate,它是全自動ORM映射,而MyBatis是半自動的ORM映射。Hibernate完全可以通過對象關係模型實現對數據庫的操作,擁有完整的JavaBean對象與數據庫的映射結構來自動生成SQL。而MyBatis僅有基本的字段映射,對象數據以及對象實際關係仍然需要通過手寫SQL來實現和管理。

Hibernate數據庫移植性遠大於MyBatis。Hibernate通過它強大的映射結構和HQL語言,大大降低了對象與數據庫(oracle、mySQL等)的耦合性,而MyBatis由於需要手寫SQL,因此與數據庫的耦合性直接取決於程序員寫SQL的方法,如果SQL不具通用性而用了很多某數據庫特性的SQL語句的話,移植性也會隨之降低很多,成本很高。
main 方法中 args 參數是什麼含義?

java中args即爲arguments的縮寫,是指字符串變量名,屬於引用變量,屬於命名,可以自定義名稱也可以採用默認值,一般習慣性照寫。

String[] args是main函數的形式參數,可以用來獲取命令行用戶輸入進去的參數。

1)字符串變量名(args)屬於引用變量,屬於命名,可以自定義名稱。

2)可以理解成用於存放字符串數組,若去掉無法知曉"args"聲明的變量是什麼類型。

3)假設public static void main方法,代表當啓動程序時會啓動這部分;

4)String[] args是main函數的形式參數,可以用來獲取命令行用戶輸入進去的參數。

5)java本身不存在不帶String args[]的main函數,java程序中去掉String args[]會出現錯誤。
什麼是高內聚、低耦合?

內聚關注模塊內部的元素結合程度,耦合關注模塊之間的依賴程度。

**1)內聚性**

又稱塊內聯繫。指模塊的功能強度的度量,即一個模塊內部各個元素彼此結合的緊密程度的度量。若一個模塊內各元素(語名之間、程序段之間)聯繫的越緊密,則它的內聚性就越高。

所謂高內聚是指一個軟件模塊是由相關性很強的代碼組成,只負責一項任務,也就是常說的單一責任原則。

**2)耦合性**

也稱塊間聯繫。指軟件系統結構中各模塊間相互聯繫緊密程度的一種度量。模塊之間聯繫越緊密,其耦合性就越強,模塊的獨立性則越差。模塊間耦合高低取決於模塊間接口的複雜性、調用的方式及傳遞的信息。

對於低耦合,粗淺的理解是:一個完整的系統,模塊與模塊之間,儘可能的使其獨立存在。也就是說,讓每個模塊,儘可能的獨立完成某個特定的子功能。模塊與模塊之間的接口,儘量的少而簡單。如果某兩個模塊間的關係比較複雜的話,最好首先考慮進一步的模塊劃分。這樣有利於修改和組合。
Spring Boot 框架的優缺點?

**Spring Boot優點**

1)創建獨立的Spring應用程序

Spring Boot以jar包的形式獨立運行,使用java -jar xx.jar命令運行項目或在項目的主程序中運行main方法。

2)Spring Boot內嵌入Tomcat,Jetty或者Undertow,無序部署WAR包文件

Spring項目部署時需要在服務器上部署tomcat,然後把項目打成war包放到tomcat中webapps目錄。

Spring Boot項目不需要單獨下載Tomcat等傳統服務器,內嵌容器,使得可以執行運行項目的主程序main函數,讓項目快速運行,另外,也降低對運行環境的基本要求,環境變量中有JDK即可。

3)Spring Boot允許通過maven工具根據需要獲取starter

Spring Boot提供了一系列的starter pom用來簡化我們的Maven依賴,通過這些starter項目就能以Java Application的形式運行Spring Boot項目,而無需其他服務器配置。

starter pom:

> https://docs.spring.io/spring-boot/docs/1.4.1.RELEASE/reference/htmlsingle/#using-boot-starter

4)Spring Boot儘可能自動配置Spring框架

Spring Boot提供Spring框架的最大自動化配置,使用大量自動配置,使得開發者對Spring的配置減少。

Spring Boot更多的是採用Java Config的方式,對Spring進行配置。

5)提供生產就緒型功能,如指標、健康檢查和外部配置

Spring Boot提供了基於http、ssh、telnet對運行時的項目進行監控;可以引入spring-boot-start-actuator依賴,直接使用REST方式來獲取進程的運行期性能參數,從而達到監控的目的,比較方便。

但是Spring Boot只是微框架,沒有提供相應的服務發現與註冊的配套功能、監控集成方案以及安全管理方案,因此在微服務架構中,還需要Spring Cloud來配合一起使用,可關注微信公衆號“Java精選”,後續篇幅會針對Spring Cloud面試題補充說明。

5)絕對沒有代碼生成,對XML沒有要求配置

**Spring Boot優點**

1)依賴包太多,一個spring Boot項目就需要很多Maven引入所需的jar包

2)缺少服務的註冊和發現等解決方案

3)缺少監控集成、安全管理方案
Spring Boot 核心註解都有哪些?

1)@SpringBootApplication*

用於Spring主類上最最最核心的註解,自動化配置文件,表示這是一個SpringBoot項目,用於開啓SpringBoot的各項能力。

相當於@SpringBootConfigryation、@EnableAutoConfiguration、@ComponentScan三個註解的組合。

2)@EnableAutoConfiguration

允許SpringBoot自動配置註解,開啓這個註解之後,SpringBoot就能根據當前類路徑下的包或者類來配置Spring Bean。

如當前路徑下有MyBatis這個Jar包,MyBatisAutoConfiguration 註解就能根據相關參數來配置Mybatis的各個Spring Bean。

3)@Configuration

Spring 3.0添加的一個註解,用來代替applicationContext.xml配置文件,所有這個配置文件裏面能做到的事情都可以通過這個註解所在的類來進行註冊。

4)@SpringBootConfiguration

@Configuration註解的變體,只是用來修飾Spring Boot的配置而已。

5)@ComponentScan

Spring 3.1添加的一個註解,用來代替配置文件中的component-scan配置,開啓組件掃描,自動掃描包路徑下的@Component註解進行註冊bean實例放到context(容器)中。

6)@Conditional

Spring 4.0添加的一個註解,用來標識一個Spring Bean或者Configuration配置文件,當滿足指定條件纔開啓配置

7)@ConditionalOnBean

組合@Conditional註解,當容器中有指定Bean纔開啓配置。

8)@ConditionalOnMissingBean

組合@Conditional註解,當容器中沒有值當Bean纔可開啓配置。

9)@ConditionalOnClass

組合@Conditional註解,當容器中有指定Class纔可開啓配置。

10)@ConditionalOnMissingClass

組合@Conditional註解,當容器中沒有指定Class纔可開啓配置。

11)@ConditionOnWebApplication

組合@Conditional註解,當前項目類型是WEB項目纔可開啓配置。

項目有以下三種類型:

① ANY:任意一個Web項目

② SERVLET: Servlet的Web項目

③ REACTIVE :基於reactive-base的Web項目

12) @ConditionOnNotWebApplication

組合@Conditional註解,當前項目類型不是WEB項目纔可開啓配置。

13)@ConditionalOnProperty

組合@Conditional註解,當指定的屬性有指定的值時纔可開啓配置。

14)@ConditionalOnExpression

組合@Conditional註解,當SpEl表達式爲true時纔可開啓配置。

15)@ConditionOnJava

組合@Conditional註解,當運行的Java JVM在指定的版本範圍時纔開啓配置。

16)@ConditionalResource

組合@Conditional註解,當類路徑下有指定的資源纔開啓配置。

17)@ConditionOnJndi

組合@Conditional註解,當指定的JNDI存在時纔開啓配置。

18)@ConditionalOnCloudPlatform

組合@Conditional註解,當指定的雲平臺激活時纔可開啓配置。

19)@ConditiomalOnSingleCandidate

組合@Conditional註解,當制定的Class在容器中只有一個Bean,或者同時有多個但爲首選時纔開啓配置。

20)@ConfigurationProperties

用來加載額外的配置(如.properties文件),可用在@Configuration註解類或者@Bean註解方法上面。可看一看Spring Boot讀取配置文件的幾種方式。

21)@EnableConfigurationProperties

一般要配合@ConfigurationProperties註解使用,用來開啓@ConfigurationProperties註解配置Bean的支持。

22)@AntoConfigureAfter

用在自動配置類上面,便是該自動配置類需要在另外指定的自動配置類配置完之後。如Mybatis的自動配置類,需要在數據源自動配置類之後。

23)@AutoConfigureBefore

用在自動配置類上面,便是該自動配置類需要在另外指定的自動配置類配置完之前。

24)@Import

Spring 3.0添加註解,用來導入一個或者多個@Configuration註解修飾的配置類。

25)@IMportReSource

Spring 3.0添加註解,用來導入一個或者多個Spring配置文件,這對Spring Boot兼容老項目非常有用,一位內有些配置文件無法通過java config的形式來配置
Spring Boot 的目錄結構是怎樣的?

**1、代碼層的結構**

根目錄:com.springboot

1)工程啓動類(ApplicationServer.java)置於com.springboot.build包下

2)實體類(domain)置於com.springboot.domain

3)數據訪問層(Dao)置於com.springboot.repository

4)數據服務層(Service)置於com,springboot.service,數據服務的實現接口(serviceImpl)至於com.springboot.service.impl

5)前端控制器(Controller)置於com.springboot.controller

6)工具類(utils)置於com.springboot.utils

7)常量接口類(constant)置於com.springboot.constant

8)配置信息類(config)置於com.springboot.config

9)數據傳輸類(vo)置於com.springboot.vo

**2、資源文件的結構**

根目錄:src/main/resources

1)配置文件(.properties/.json等)置於config文件夾下

2)國際化(i18n)置於i18n文件夾下

3)spring.xml置於META-INF/spring文件夾下

4)頁面以及js/css/image等置於static文件夾下的各自文件下
Spring Boot 需要獨立的容器運行嗎?

Spring Boot項目可以不需要,內置了Tomcat/Jetty等容器,默認Tomcat。

Spring Boot不需要獨立的容器就可以運行,因爲在Spring Boot工程發佈的jar文件裏已經包含了tomcat插件的jar文件。

Spring Boot運行時創建tomcat對象實現web服務功能,另外也可以將Spring Boot編譯成war包文件放到tomcat中運行。
Spring Boot 運行方式有哪幾種?

1)直接執行main方法運行,通過IDE工具運行Application這個類的main方法

2)使用Maven插件spring-boot-plugin方式啓動,在Spring Boot應用的根目錄下運行mvn spring-boot:run

3)使用mvn install生成jar後通過java -jar命令運行
Spring Boot 自動配置原理是什麼?

Spring Boot的啓動類中使用了@SpringBootApplication註解,裏面的@EnableAutoConfiguration註解是自動配置的核心,註解內部使用@Import(AutoConfigurationImportSelector.class)(class文件用來哪些加載配置類)註解來加載配置類,並不是所有的bean都會被加載,在配置類或bean中使用@Condition來加載滿足條件的bean。

@EnableAutoConfiguration給容器導入META-INF/spring.factories中定義的自動配置類,篩選有效的自動配置類。每一個自動配置類結合對應的xxxProperties.java讀取配置文件進行自動配置功能
Spring Boot 熱部署有幾種方式?

1)spring-boot-devtools

通過Springboot提供的開發者工具spring-boot-devtools來實現,在pom.xml引用其依賴。

然後在Settings→Build→Compiler中將Build project automatically勾選上,最後按ctrl+shift+alt+/ 選擇registy,將compiler.automake.allow.when.app.running勾選。

2)Spring Loaded

Spring官方提供的熱部署程序,實現修改類文件的熱部署

下載Spring Loaded(項目地址https://github.com/spring-projects/spring-loaded)

添加運行時參數:-javaagent:C:/springloaded-1.2.5.RELEASE.jar –noverify

3)JRebel

收費的一個熱部署軟件,安裝插件使用即可。
MyBatis 中 $ 和 # 傳參有什麼區別?

1)“#”符號將傳入的數據當成一個字符串並將傳入的數據加上雙引號。

如:order by #{userId},如果傳入的值是1,那麼解析成sql時的值爲order by "1",如果傳入的值是userId,則解析成的sql爲order by "userId"。

2)“$”符號將傳入的數據直接顯示生成在sql語句中。

如:order by ${userId},如果傳入的值是1,那麼解析成sql時的值爲order by 1, 如果傳入的值是userId,則解析成的sql爲order by userId。

3)“#”符號能夠很大程度防止sql注入,而“$”符號無法防止sql注入。

4)“$”符號方式一般用於傳入數據庫對象,例如傳入表名。

5)一般能用“#”符號的就別用“$”符號

6)MyBatis排序時使用order by動態參數時需要注意使用“$”符號而不是“#”符號。
MyBatis 如何實現分頁?

1)相對原始方法,使用limit分頁,需要處理分頁邏輯:

MySQL數據庫使用limit,如:

select * from table limit 0,10; --返回0-10行

Oracle數據庫使用rownum,如:

從表Sys_option(主鍵爲sys_id)中從第10條記錄開始檢索20條記錄,語句如下:

SELECT * FROM (SELECT ROWNUM R,t1.* From Sys_option where rownum < 30 ) t2 Where t2.R >= 10

2)攔截StatementHandler,其實質還是在最後生成limit語句。

3)使用PageHelper插件,目前比較常見的方法。
MyBatis 如何獲取自動生成的主鍵id?

數據插入時獲得主鍵值分爲兩種情況:支持主鍵自增數據庫和不支持主鍵自增。

1)對於支持自動生成主鍵的數據庫,如Mysql、sqlServer,可以通過Mybatis元素useGeneratedKeys返回當前插入數據主鍵值到輸入類中。

```xml
<insert id="insertTest" useGeneratedKeys="true" keyProperty="id"
 parameterType="com.kq.domain.IdentityTest">
insert into identity_test(name)
values(#{name,jdbcType=VARCHAR})
</insert>
```
當執行此條插入語句以後,實體類IdentityTest中的Id會被當前插入數據的主鍵自動填充。

2)對於不支持自動生成主鍵的數據庫。Oracle、DB2等,可以用元素selectKey 回當前插入數據主鍵值到輸入類中。(同時生成一個自定義的隨機主鍵)
```xml
<insert id="insertTest" useGeneratedKeys="true" keyProperty="id"
 parameterType="com.kq.domain.IdentityTest">
 <selectKey keyProperty="id" resultType="String" order="BEFORE">
SELECT REPLACE(UUID(),'-','')
</selectKey>
insert into identity_test(name)
values(#{name,jdbcType=VARCHAR})
</insert>
```

當執行此條插入語句以後,實體類IdentityTest中的Id也會被當前插入數據的主鍵自動填充。
TCP 和 UDP 協議有什麼區別?

1)基於連接

TCP是面向連接的協議,而UDP是無連接的協議。即TCP面向連接;UDP是無連接的,即發送數據之前不需要建立連接。

2)可靠性和有序性

TCP 提供交付保證(Tcp通過校驗和,重傳控制,序號標識,滑動窗口、確認應答實現可靠傳輸),無差錯,不丟失,不重複,且按序到達,也保證了消息的有序性。該消息將以從服務器端發出的同樣的順序發送到客戶端,儘管這些消息到網絡的另一端時可能是無序的。TCP協議將會爲你排好序。

UDP不提供任何有序性或序列性的保證。UDP盡最大努力交付,數據包將以任何可能的順序到達。

TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠信道。

3)實時性

UDP具有較好的實時性,工作效率比TCP高,適用於對高速傳輸和實時性有較高的通信或廣播通信。

4)協議首部大小

TCP首部開銷20字節;UDP的首部開銷小,只有8個字節。

5)運行速度

TCP速度比較慢,而UDP速度比較快,因爲TCP必須創建連接,以保證消息的可靠交付和有序性,畢竟TCP協議比UDP複雜。

6)擁塞機制

UDP沒有擁塞控制,因此網絡出現擁塞不會使源主機的發送速率降低,對實時應用很有用,如IP電話,實時視頻會議等。

7)流模式(TCP)與數據報模式(UDP)
TCP面向字節流,實際上是TCP把數據看成一連串無結構的字節流;UDP是面向報文的 。

8)資源佔用

TCP對系統資源要求較多,UDP對系統資源要求較少。

TCP被認爲是重量級的協議,而與之相比,UDP協議則是一個輕量級的協議。因爲UDP傳輸的信息中不承擔任何間接創造連接,保證交貨或秩序的的信息。這也反映在用於承載元數據的頭的大小。

9)應用

每一條TCP連接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互通信 。基於UDP不需要建立連接,所以且適合多播的環境,UDP是大量使用在遊戲和娛樂場所。
Integer 類型值是 0 ,爲什麼 != '' 無法執行?

開發微信小程序“Java精選面試題”後臺管理系統時,遇到根據狀態判斷是或否發佈。

MySQL數據庫中設計數據庫表,其中某字段status使用tinyint數據類型,當修改狀態的時候,賦值status屬性的值爲0,用於改變狀態記錄試題庫中發佈情況。

但是通過Debug模式查看Controller控制層明顯已經獲取到status等於0,但是在執行到MyBatis中xml文件SQL語句時,總是無法賦值成功,xml配置如下:
```xml
<update id="updateWarehouse" parameterType="Warehouse">


update t_warehouse


<set>

 

<if test="title != null and title != ''">title = #{title},</if>

 

<if test="code != null and code != ''">code = #{code},</if>

 

<if test="content != null and content != ''">content = #{content},</if>

 

<if test="status != null and status != ''">status = #{status},</if>

 

<if test="parentId != null and parentId != ''">parentId = #{parentId}</if>


</set>


where id = #{id}
</update>
```
分析:

通過分析表面上沒有任何傳參問題,通過上網查詢MyBatis相關資料,終於弄明白什麼原因。
此行代碼中
```xml
<if test="status != null and status != ''">status = #{status},</if>
```
and status != '',MyBatis中傳參status的值爲0時,因爲數據類型爲Integer類型,判斷爲false值。

MyBatis認定 0 = ''的,因此判斷status != ''爲false,這導致修改試題信息時狀態值無法改變爲0。

正確寫法:
```xml
<update id="updateWarehouse" parameterType="Warehouse">


update t_warehouse


<set>

 

<if test="title != null and title != ''">title = #{title},</if>

 

<if test="code != null and code != ''">code = #{code},</if>

 

<if test="content != null and content != ''">content = #{content},</if>

 

<if test="status != null">status = #{status},</if>

 

<if test="parentId != null and parentId != ''">parentId = #{parentId}</if>


</set>


where id = #{id}
</update>
```
MySQL 的索引有哪些設計原則?

**選擇唯一性索引**

唯一性索引的值是唯一的,可以更快速的通過該索引來確定某條記錄

**作爲查詢條件的字段建立索引**

某個字段用來做查詢條件,那麼該字段的查詢速度會影響整個表的查詢速度。因此,爲該字段建立索引,可以提高整個表的查詢速度

**限制索引的數量**

索引的數量並不是越多越好,每個索引都需要佔用磁盤空間,索引越多,需要的磁盤空間就越大。

修改表時,索引過多會使得更新錶速度變慢

**盡量使用字段數據量少的索引**

若索引的字段數據量過長,會導致查詢的速度變慢

如:對一個char(200)類型的字段進行全文檢索需要的時間肯定比對char(10)類型的字段需要的時間更多

**排序、分組和聯合字段建立索引**

使用order by、group by、distinct和union等操作的字段進行排序操作會浪費很多時間。若爲其建立索引,可以有效的避免排序操作

**儘量使用前綴索引**

索引字段的值很長,最好使用值的前綴來索引

如:text和blog類型的字段,進行全文檢索會浪費時間。若只檢索字段的部分若干個字符,可以提高檢索速度

**刪除不使用或者很少使用的索引**

表中數據被批量更新或數據的使用方式被改變後,原有的一些索引可能不再需要。應當定期清理這些索引

**小表不創建索引(超過200w數據的表,創建索引)**

包含很多列且不需要搜索非空值時可以考慮不建索引

**被用來過濾記錄的字段創建索引**

> primary key 字段,系統自動創建主鍵的索引
unique key 字段,系統自動創建對應的索引
foreign key 約束所定義的作爲外鍵的字段
在查詢中用來連接表的字段
用作爲排序(order by的字段)的字段

創建索引必須考慮數據的操作方式,原則是內容變動少,經常被用於查詢的字段創建索引,對於經常性變動的表而言,則需要謹慎創建必要的索引
爲什麼要使用自增 ID 作爲主鍵?

1、若定義主鍵(PRIMARY KEY),InnoDB會選擇主鍵作爲聚集索引,反之未定義主鍵,則InnoDB會選擇第一個不包含NULL值的唯一索引作爲主鍵索引。

沒有唯一索引,則InnoDB會選擇內置6字節長的ROWID作爲隱含的聚集索引(ROWID隨着行記錄的寫入而主鍵遞增,這個ROWID不像ORACLE的ROWID那樣可引用,是隱含的)。

2、數據記錄本身被存於主索引(一顆B+Tree)的葉子節點上,這就要求同一個葉子節點內(大小爲一個內存頁或磁盤頁)的各條數據記錄按主鍵順序存放。

每當有一條新的記錄插入時,MySQL會根據其主鍵將其插入適當的節點和位置,若頁面達到裝載因子(InnoDB默認爲15/16),則開闢一個新的頁(節點)。

3、若表使用自增主鍵,則每次插入新的記錄就會順序添加到當前索引節點的後續位置,當寫滿一頁就會自動開闢一個新的頁。

4、若使用非自增主鍵,因爲每次插入主鍵的值都近似於隨機,所以每次新紀錄都要被插到現有索引頁得中間某個位置,此時MySQL將爲新記錄插到合適位置而移動數據,甚至可能被回寫到磁盤上而從緩存中清除掉,此時又要從磁盤上讀回來,這將增大了開銷。同時頻繁的移動、分頁操作造成了大量的碎片,得到了不夠緊湊的索引結構,後續需通過OPTIMIZE TABLE來重建表並優化填充頁面。
Linux 如何切換用戶?

Linux系統切換用戶的命令是su

su的含義是(switch user)切換用戶的縮寫。

通過su命令,可以從普通用戶切換到root用戶,也可以從root用戶切換到普通用戶。

<font color="#dd0000">注:從普通用戶切換到root用戶需要密碼,從root用戶切換到普通用戶不需要密碼。</font>

使用SecureCRT工具連接終端,由普通用戶切換到root用戶。

在終端輸入su命令然後回車,要求輸入密碼(linux終端輸入的密碼不顯示)輸入密碼後回車進入root用戶。

在終端輸入su root然後回車,也可以進入root用戶或su - root回車,也可以切換root用戶。針對su root與su - root區別之後的篇幅會闡述,歡迎關注微信公衆號“Java精選”,送免費資料。
su root 和 su - root 有什麼區別?

su後面不加用戶是默認切到root,su是不改變當前變量;su -是改變爲切換到用戶的變量。

簡單來說就是su只能獲得root的執行權限,不能獲得環境變量,而su -是切換到root並獲得root的環境變量及執行權限。

語法:

```shell
$ su [user_name]
```

su命令可以用來更改你的用戶ID和組ID。su是switch user或set user id的一個縮寫。

此命令是用於開啓一個子進程,成爲新的用戶ID和賦予存取與這個用戶ID關聯所有文件的存取權限。

出於安全的考慮,在實際轉換身份時,會被要求輸入這個用戶帳號的密碼。

如果沒有參數,su命令將轉換爲root(系統管理員)。root帳號有時也被稱爲超級用戶,因爲這個用戶可以存取系統中的任何文件。

當必須要提供root密碼,想要回到原先的用戶身份,可以不使用su命令,只需使用exit命令退出使用su命令而生成的新的對話進程。

```shell
$ su – username
```

當使用命令su username時,對話特徵和原始的登錄身份一樣。

如果需要對話進程擁有轉換後的用戶ID一致的特徵,使用短斜槓命令: su – username。
Linux 怎麼切換目錄?

1)使用pwd命令查看一下當前所在的目錄

2)切換文件目錄使用的cd命令。

切換到根目錄命令如下:
```shell
cd /
```
3)根目錄下使用ls命令查看該目錄下有哪些文件,使用用絕對路徑的方式進入所需目錄,比如進入usr目錄,命令如下:
```shell
cd /usr
```

4)若進入文件下一層級目錄,可以使用相對路徑的方式切換目錄。比如目錄結構如下:
```shell
usr/local/other
```
在當前usr目錄下使用命令
```shell
cd ./local
```
進入usr目錄下的local目錄。此命令和使用絕對路徑的方式區別在於,前面多了個 ".",這個"."代表的是當前目錄。

5)若要回到上一級目錄,則可以使用命令
```shell
cd ../
```
Dubbo 支持哪些協議,推薦用哪種?

**dubbo協議(推薦使用)**
單一TCP長連接和NIO異步通訊,適合大併發小數據量的服務調用,以及服務消費者遠大於提供者的情況。

缺點是Hessian二進制序列化,不適合傳送大數據包的服務

**rmi協議**

採用JDK標準的rmi協議實現,傳輸參數和返回參數對象需要實現Serializable接口。

使用java標準序列化機制,使用阻塞式短連接,傳輸數據包不限,消費者和提供者個數相當。

多個短連接,TCP協議傳輸,同步傳輸,適用常規的遠程服務調用和rmi互操作。

缺點是在依賴低版本的Common-Collections包,java反序列化存在安全漏洞,需升級commons-collections3 到3.2.2版本或commons-collections4到4.1版本。

**webservice協議**

基於WebService的遠程調用協議(Apache CXF的frontend-simple和transports-http)實現,提供和原生WebService的互操作。

多個短連接,基於HTTP傳輸,同步傳輸,適用系統集成和跨語言調用。

**http協議**

基於Http表單提交的遠程調用協議,使用Spring的HttpInvoke實現,對傳輸數據包不限,傳入參數大小混合,提供者個數多於消費者。

缺點是不支持傳文件,只適用於同時給應用程序和瀏覽器JS調用。

**hessian協議**

集成Hessian服務,基於底層Http通訊,採用Servlet暴露服務,Dubbo內嵌Jetty作爲服務器實現,可與Hession服務互操作。

通訊效率高於WebService和Java自帶的序列化。

適用於傳輸大數據包(可傳文件),提供者比消費者個數多,提供者壓力較大。

缺點是參數及返回值需實現Serializable接口,自定義實現List、Map、Number、Date、Calendar等接口

**thrift協議**

協議:對thrift原生協議的擴展添加了額外的頭信息,使用較少,不支持傳null值。

**memcache協議**

基於memcached實現的RPC協議。

**redis協議**

基於redis實現的RPC協議。
Dubbo 默認使用什麼註冊中心,還有別的選擇嗎?

推薦使用Zookeeper作爲註冊中心,還有Redis、Multicast、Simple註冊中心,但不推薦。
爲什麼 Redis 需把數據放到內存中? 

若不將數據讀到內存中,磁盤I/O速度會嚴重影響Redis的性能。

Redis具有快速和數據持久化的特徵。

Redis爲了提高讀寫時的速度將數據讀到內存中,並通過異步的方式將數據寫入磁盤。

注:若設置最大使用內存,則數據已有記錄數達到內存限值後不能繼續插入新值。

爲什麼內存讀取比磁盤讀取數據速度快?

1)內存是電器元件,利用高低電平存儲數據,而磁盤是機械元件,電氣原件速度超快,而磁盤由於在每個磁盤塊切換時磁頭會消耗很多時間,說白了就是IO時間長,兩者性能沒發比較。

2)磁盤的數據進行操作時也是讀取到內存中交由CPU進行處理操作,因此直接放在內存中的數據,讀取速度肯定快很多。

Zookeeper 怎麼保證主從節點的狀態同步?

Zookeeper的核心是原子廣播機制,這個機制保證了各個server之間的同步。實現這個機制的協議叫做Zab協議。

Zab協議有兩種模式,分別是恢復模式和廣播模式。

**恢復模式**

當服務啓動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數server完成了和leader的狀態同步以後,恢復模式就結束了。

狀態同步保證了leader和server具有相同的系統狀態。

**廣播模式**

一旦leader已經和多數的follower進行了狀態同步後,它就可以開始廣播消息了,即進入廣播狀態。

此時當一個server加入ZooKeeper服務中,它會在恢復模式下啓動,發現leader,並和leader進行狀態同步。

待到同步結束,它也參與消息廣播。

ZooKeeper服務一直維持在Broadcast狀態,直到leader崩潰了或者leader失去了大部分的followers支持。
Dubbo 停止更新了嗎?

Dubbo是阿里巴巴內部使用的分佈式業務框架,於2012年由阿里巴巴開源。

由於Dubbo在阿里巴巴內部經過廣泛的業務驗證,在很短時間內,Dubbo就被許多互聯網公司所採用,併產生了許多衍生版本,如網易,京東,新浪,噹噹等等。

由於阿里巴巴內部策略的調整變化,在2014年10月Dubbo停止維護。隨後部分互聯網公司公開了自行維護的Dubbo版本,比較著名的如噹噹DubboX,新浪Motan等。

在2017年9月,阿里宣佈重啓Dubbo項目,並決策在未來對開源進行長期持續的投入。隨後Dubbo開始了密集的更新,並將擱置三年以來大量分支上的特性及缺陷快速修正整合。

在2018年2月,阿里巴巴將Dubbo捐獻給Apache基金會,Dubbo成爲Apache孵化器項目。
爲什麼選用 Maven 進行構建?

1)Maven是一個優秀的項目構建工具。

使用Maven可以比較方便的對項目進行分模塊構建,這樣在開發或測試打包部署時,會大大的提高效率。

2)Maven可以進行依賴的管理。

使用Maven 可以將不同系統的依賴進行統一管理,並且可以進行依賴之間的傳遞和繼承。

Maven可以解決jar包的依賴問題,根據JAR包的座標去自動依賴/下載相關jar,通過倉庫統一管理jar包。

多個項目JAR包冗餘,使用Maven解決一致性問題。

4)屏蔽開發工具之間的差異,例如:IDE,Eclipse,maven項目可以無損導入其他編輯器。
Maven 規約是什麼?

src/main/java 存放項目的類文件(後綴.java文件,開發源代碼)

src/main/resources 存放項目配置文件,若沒有配置文件該目錄可無,如Spring、Hibernate、MyBatis等框架配置文件

src/main/webapp 存放web項目資源文件(web項目需要)

src/test/java 存放所有測試類文件(後綴.java文件,測試源代碼)

src/test/resources 測試配置文件,若沒有配置文件該目錄可無

target 文件編譯過程中生成的後綴.class文件、jar、war等

pom.xml maven項目核心配置文件,管理項目構建和依賴的Jar包

Maven負責項目的自動化構建,以編譯爲例,Maven若果自動進行編譯,需要知道Java的源文件保存位置,通過這些規約,不用開發者手動指定位置,Maven就可以清晰的知道相關文件所在位置,從而完成自動編譯。

遵循**“約定>>>配置>>>編碼”**。即能進行配置的不要去編碼指定,能事先約定規則的不要去進行配置。這樣既減輕了工作量,也能防止編譯出錯。
Maven 常用命令有哪些?

1)mvn clean

清理輸出目錄默認target/

2)mvn clean compline

編譯項目主代碼,默認編譯至target/classes目錄下

3)mvn clean install

maven安裝,將生成的JAR包文件複製到本地maven倉庫中,其他項目可以直接使用這個JAR包

4)mvn clean test

maven測試,但實際執行的命令有:
```shell
clean:clean
resource:resources
compiler:compile
resources:testResources
compiler:testCompile
```
maven執行test前,先自動執行項目主資源處理,主代碼編譯,測試資源處理,測試代碼編譯等工作

測試代碼編譯通過之後默認在target/test-calsses目錄下生成二進制文件,隨後執行surefile:test任務運行測試,並輸出測試報告,顯示運行多少次測試,失敗成功等。

5)mvn celan package

maven打包,maven會在打包之前默認執行編譯,測試等操作,打包成功之後默認輸出在target/目錄中

6)mvn help:system

打印出java系統屬性和環境變量。

7)echo %MAVEN_HOME%:

查看maven安裝路徑。

8)mvn deploy

在整合或發佈環境下執行,將終版本的包拷貝到遠程的repository,使得其他的開發者或者工程可以共享。

9)mvn

檢查是否安裝了maven。

10)mvn dependency:list

查看當前項目中的已解析依賴

11)mvn dependency:tree

查看當前項目的依賴樹

12)mvn dependency:analyse

查看當前項目中使用未聲明的依賴和已聲明但未使用的依賴
什麼是鏈式存儲結構?

鏈接存儲結構的含義是在計算機中用一組任意的存儲單元存儲線性表的數據元素(這組存儲單元可以是連續的,也可以是不連續的),它不要求邏輯上相鄰的元素在物理位置上也相鄰。

鏈式存儲結構的優點主要是插入和刪除簡單,前提條件是知道操作位置,時間複雜度是O(1),如果不知道操作位置則要定位元素,時間複雜度爲O(n),沒有容量的限制,可以使用過程中動態分配的內存空間,不用擔心溢出問題,但是它並不能實現隨機讀取,同時空間利用率不高。
說說幾種常見的排序算法和複雜度?

**1)快速排序**

原理:快速排序採用的是一種分治的思想,通過依次排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。

選定一個合適的值(理想情況下選擇中間值最好,但實際中一般使用數組第一個值),稱爲“樞軸”(pivot)。

基於這個值將數組分爲兩部分,較小的分在左邊,較大的分在右邊,如此一輪下來,這個樞軸的位置一定在最終位置上。

對兩個子數組分別重複上述過程,直到每個數組只有一個元素,排序完成。

複雜度:O(n)

特點:快速排序是平時最常使用的一種排序算法,因它速度快,效率高的一種排序算法。

**2)冒泡排序**

原理:冒泡排序採用的事兩個相鄰的值進行比較,將值大的交換到右側。就是逐一比較交換,進行內外兩次循環,外層循環爲遍歷所有數值,逐個確定每個位置,內層循環爲確定位置後遍歷所有後續沒有確定位置的數字,與該位置的值進行比較,只要比該位置的值小,就位置交換。

複雜度:O(n^2),最佳時間複雜度爲O(n)

特點:冒泡排序在實際開發中使用比較少,更適合數據量比較少的場景,因其效率比較低,但邏輯簡單,方便記憶。

**3)直接插入排序**

原理:直接插⼊排序是從第二個數字開始,逐個取出,插入到之前排好序的數組中。

複雜度:O(n^2),最佳時間複雜度爲O(n)

**4)直接選擇排序**

原理:直接選擇排序是從第一個位置開始遍歷位置,找到剩餘未排序的數組中最小值,將最小值做交換位置。

複雜度:O(n^2)

特點:類似冒泡排序其邏輯簡單,但效率低,適合少量數據排序。
Java 遞歸遍歷目錄下的所有文件?

```java
import java.io.File;

public class ListFiles {


public static void listAll(File directory) {

 

if(!(directory.exists() && directory.isDirectory())) {

 


throw new RuntimeException("目錄不存在");

 

}

 

File[] files = directory.listFiles();

 

 

 

for (File file : files) {

 


System.out.println(file.getPath() + file.getName());

 


if(file.isDirectory()) {

 

 

listAll(file);

 


}

 

}


}

 

 

public static void main(String[] args) {

 

File directory = new File("E:\\Program Files (x86)");

 

listAll(directory);


}
}
```
JSP 獲取 ModelAndView 傳參數據問題?

Idea開發工具自動創建的web.xml約束太低,導致無法正常獲取數據,需要把web.xml約束的信息調整一下,參考如下:

```xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
```

Linux 運行 SQL 語句文件報錯?

原因分析:Linux下MySQL版本不兼容導致的。

解決辦法:把文件中所有的utf8mb4_0900_ai_ci替換爲utf8_general_ci以及utf8mb4替換爲utf8類型。
如何解決 Linux 顯示中文亂碼問題?

在Linux中通過locale來設置程序運行的不同語言環境,locale由 ANSI C提供支持。locale的命名規則爲_.,如zh_CN.GBK,zh代表中文,CN代表大陸地區,GBK表示字符集。

修改 /etc/locale.conf文件的內容
```shell
LANG="zh_CN.UTF-8"
```
執行命令,使修改文件立刻生效
```shell
source /etc/locale.conf
```
IDEA 中 Maven 項目無法自動識別 pom.xml?

**方式一**

File->Settings->Build,Excecution,Deployment->Build Tools->Maven->Ignored Files

查看是否存在maven pom被勾選,去掉勾選即可。

**方式二**

右鍵項目pom.xml文件,選擇“add as maven project”,自動導入pom所依賴的jar包。

刷新Maven配置

右鍵單擊項目,在彈出菜單中選擇Maven->Reimport菜單項。IDEA將通過網絡自動下載相關依賴,並存放在Maven的本地倉庫中。

或者將Maven的刷新設置爲自動,單擊File|Setting菜單項,打開Settings選項卡,在左側的目錄樹中展開Maven節點,勾選Import Maven projects automatically選擇項。
面向過程與面向對象有什麼區別?

**面向過程**   

性能相比面向對象高,因其類調用時需要實例化,開銷比較大,消耗資源,比如單片機、嵌入式開發、Linux/Unix等一般採用面向過程開發,性能是最重要的因素。

**面向對象** 

易維護、複用以及擴展,由於面向對象有封裝、繼承、多態性等特徵,可以設計出低耦合、高內聚的系統,使得更加靈活,易於維護。
Java 編程語言有哪些特點?

1)簡單易學;

2)面向對象(封裝,繼承,多態);

3)平臺無關性(Java虛擬機實現平臺無關性);

4)可靠性;

5)安全性;

6)支持多線程;

7)支持網絡編程並方便易用;

8)編譯與解釋並存。
重載和重寫有什麼區別?

**重載(Overload)** 是指讓類以統一的方式處理不同類型數據的一種手段,實質表現就是多個具有不同的參數個數或者不同類型的同名函數,存在於同一個類中,返回值類型不同,是一個類中多態性的一種表現。

調用方法時通過傳遞不同參數個數和參數類型來決定具體使用哪個方法的多態性。

**重寫(Override)** 是指父類與子類之間的多態性,實質就是對父類的函數進行重新定義。

如果子類中定義某方法與其父類有相同的名稱和參數則該方法被重寫,需注意的是子類函數的訪問修飾權限不能低於父類的。

如果子類中的方法與父類中的某一方法具有相同的方法名、返回類型和參數表,則新方法將覆蓋原有的方法,如需父類中原有的方法則可使用super關鍵字。
靜態方法和實例方法有什麼不同?

靜態方法和實例方法的區別主要體現在兩個方面:

其一在外部調用靜態方法時,可以使用"類名.方法名"的方式,也可以使用"對象名.方法名"的方式而實例方法只能試用後面這種方式。也就是說,調用靜態方法可以無需創建對象進行實例化。

其二靜態方法在訪問本類的成員時,只允許訪問靜態成員也就是靜態成員變量和靜態方法,而不允許訪問實例成員變量和實例方法,實例方法是沒有這個限制的。
== 和 equals 兩者有什麼區別?

**使用==比較**

用於對比基本數據類型的變量,是直接比較存儲的 “值”是否相等;

用於對比引用類型的變量,是比較的所指向的對象地址。

**使用equals比較**

equals方法不能用於對比基本數據類型的變量;

如果沒對Object中equals方法進行重寫,則是比較的引用類型變量所指向的對象地址,反之則比較的是內容。
HashMap 是怎麼擴容的?

當HashMap中元素個數超過數組大小*loadFactor時,需進行數組擴容。

loadFactor默認值爲0.75,默認情況下,數組大小爲16,HashMap中元素個數超過16 * 0.75=12的時,就把數組的大小擴展爲2*16=32,即擴大一倍,然後重新計算每個元素在數組中的位置,而這是一個非常消耗性能的操作,所以能夠預選知道HashMap中元素的個數,應該預設數組的大小,可以有效的提高HashMap的性能。

假設有1000個元素new HashMap(1000),理論上來講new HashMap(1024)更合適,不過上面已經提過即使是1000個元素,HashMap也會自動設置爲1024。但是new HashMap(1024),而0.75*1024 <1000, 爲了可以0.75 * size >1000,必須new HashMap(2048),避免了resize的問題。

**總結:**

添加元素時會檢查容器當前元素個數。當HashMap的容量值超過臨界值(默認16 * 0.75=12)時擴容。HashMap將會重新擴容到下一個2的指數冪(16->32->64)。調用resize方法,定義長度爲新長度(32)的數組,然後對原數組數據進行再Hash。注意的是這個過程比較損耗性能。
JDK1.8 和 JDK1.7 中 ArrayList 的初始容量多少?

JDK1.7下ArrayList()初始化後的默認長度是10,源碼如下:
```java
//無參構造方法
public ArrayList() {
this(10);
}

public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
```
通過上述源代碼可以看出,默認的構造方法中直接指定數組長度爲10,同時調用重載的構造方法,創建了長度爲10的一個數組。

JDK1.8下ArrayList()初始化後的默認長度是0,源碼如下:
```java
//無參構造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
```

構造方法中靜態類型的數組DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}是個空數組,數組長度爲0,因此JDK1.8下的ArrayList()初始化後默認的數組長度爲0。


Arrays.asList() 有什麼使用限制?

1)Arrays.asList()方法不適用於基本數據類型

```java
byte
short
int
long
float
double
boolean
```

2)Arrays.asList()方法把數組與列表鏈接起來,當更新其中之一時,另一個自動更新。

3)Arrays.asList()方法不支持add和remove方法。
Set 爲什麼是無序的?

Set系列集合添加元素無序的根本原因是底層採用哈希表存儲元素。

JDK1.8以下版本:哈希表 = 數組 + 鏈表 + (哈希算法)

JDK1.8及以上版本:哈希表 = 數組 + 鏈表 + 紅黑樹 + (哈希算法)

當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減少了查找時間。

Comparable 和 Comparator有什麼區別?

Comparable接口出自java.lang包,它有一個compareTo(Object obj)方法用來排序。

Comparator接口出自java.util包,它有一個compare(Object obj1, Object obj2)方
法用來排序。

一般對集合使用自定義排序時,需要重寫compareTo()方法或compare()方法。

當需要對某一個集合實現兩種排序方式,比如一個用戶對象中的姓名和身份證分別採用一種排序方法。
方式一:重寫compareTo()方法實現姓名、身份證排序

方式二:使用自定義的Comparator方法實現姓名、身份證排序

方法三:使用兩個Comparator來實現姓名、身份證排序

其中方式二代表只能使用兩個參數的形式Collections.sort()。

Collections是一個工具類,sort是其中的靜態方法,是用來對List類型進行排序的,它有兩種參數形式:

```java
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
```
HashMap 中如何實現同步?

HashMap可以使用如下代碼實現:

```java
Map map = Collections.synchronizedMap(new HashMap());
```

來達到同步的效果。

具體而言,該方法會返回一個同步的Map集合,這個Map封裝了底層HashMap的所有方法,使得底層的HashMap可以在多線程的環境中也能夠保證安全性。
List、Set、Map 三者有什麼區別?

List

存儲數據允許不唯一集合,可以有多個元素引用相同的對象且是有序的對象。

Set

不允許重複的集合,不存在多個元素引用相同的對象。

Map

使用鍵值對存儲方式。Map維護與Key有關聯的值。兩個Key可以引用相同的對象,但Key不能重複,比如Key是String類型,但也可以是任何對象。
多線程實現的方式有幾種?

1)繼承Thread類創建線程

Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。

啓動線程的唯一方法是通過Thread類的start()實例方法。

start()方法將啓動一個新線程,並執行run()方法。

這種方式實現多線程比較簡單,通過自己的類直接繼承Thread,並重寫run()方法,就可以啓動新線程並執行自己定義的run()方法。

2)實現Runnable接口創建線程

如果自己的類已經繼承了兩一個類,就無法再繼承Thread,因此可以實現一個Runnable接口

3)實現Callable接口,通過FutureTask包裝器來創建Thread線程

4)使用<code>ExecutorService</code>、<code>Callable</code>、<code>Future</code>實現有返回結果的線程

<code>ExecutorService</code>、<code>Callable</code>、<code>Future</code>三個接口實際上都是屬於Executor框架。

在JDK1.5中引入的新特徵,不需要爲了得到返回值而大費周折。主要需要返回值的任務必須實現Callable接口;不需要返回值的任務必須實現Runnabel接口。

執行Callable任務後,可以獲取一個Future對象,在該對象上調用get()方法可以獲取到Callable任務返回的Object了。注意的是get()方法是阻塞的,線程沒有返回結果時,該方法會一直等待。
什麼是線程局部變量?

ThreadLocal並非是一個線程本地實現版本,它並不是一個Thread,而是thread local variable(線程局部變量)。也許把它命名爲ThreadLocalVar更合適。

線程局部變量(ThreadLocal)功能非常簡單,就是爲每一個使用該變量的線程都提供了一個變量值副本,是Java中一種較爲特殊的線程綁定機制,使得每一個線程都獨立地改變所具有的副本,而不會和其他線程的副本衝突。
Java 中常見的阻塞隊列有哪些?

**ArrayBlockingQueue**

最典型的有界隊列,其內部是用數組存儲元素的,利用ReentrantLock實現線程安全,使用Condition來阻塞和喚醒線程。

**LinkedBlockingQueue**

內部用鏈表實現BlockingQueue。如果不指定它的初始容量,那麼它容量默認就爲整型的最大值Integer.MAX_VALUE,由於這個數非常大,通常不可能放入這麼多的數據,所以LinkedBlockingQueue 也被稱作無界隊列,代表它幾乎沒有界限。

**SynchronousQueue**

相比較其他,最大的不同之處在於它的容量爲0,所以沒有地方暫存元素,導致每次存儲或獲取數據都要先阻塞,直至有數據或有消費者獲取數據。

**PriorityBlockingQueue**

支持優先級的無界阻塞隊列,可以通過自定義類實現compareTo()方法來指定元素排序規則或初始化時通過構造器參數Comparator來指定排序規則。需主要的是插入隊列的對象必須是可以比較大小的值,否則會拋出ClassCastException異常。

**DelayQueue**

具有“延遲”的功能。隊列中的可以設定任務延遲多久之後執行,比如“30分鐘後未付款自動取消訂單”等需要執行的場景。
創建線程池的有幾種方式?

**newCachedThreadPool**

創建一個可緩存的線程池,如果線程池長度超過處理需求,可靈活回收空閒線程,如果沒有可回收線程,則新建線程。

**newFixedThreadPool**

創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。

**newScheduledThreadPool**

創建一個定長線程池,支持定時及週期性任務執行。

**newSingleThreadExecutor**

創建一個單線程化的線程池,它只會唯一的工作線程來執行任務,保證所有任務按照指定執行。
查看文件內容有哪些命令?

vi 文件名

編輯方式查看,可以修改文件

cat 文件名

顯示全部文件內容


tail 文件名

僅查看文件尾部信息,可以指定查看行數

head 文件名

僅查看頭部,可以指定查看行數

more 文件名

分頁顯示文件內容

less 文件名

與more相似,可以往前翻頁
命令中可以使用哪幾種通配符?

“?”

可以替代任意單個字符。

“*”

可以替代任意多個字符。

方括號“[charset]”

可以替代charset集中的任何單個字符,比如[a-z],[abABC]
根據文件名搜索文件有哪些命令?

find <指定目錄> <指定條件> <指定動作>,

whereis 加參數與文件名

locate 只加文件名
bash shell 中 hash 命令有什麼作用?

Linux中hash命令管理着一個內置的哈希表,記錄了已執行過命令的完整路徑, 用該命令可以打印出所使用過的命令以及執行的次數。
Linux 中進程有哪幾種狀態?

1)不可中斷狀態:進程處於睡眠狀態,此時的進程不可中斷,進程不響應異步信號。

2)暫停狀態/跟蹤狀態:向進程發送一個SIGSTOP信號,它就會因響應信號而進入TASK_STOPPED狀態;當進程正在被跟蹤時,它處於TASK_TRACED這個特殊的狀態。正在被跟蹤指的是進程暫停下來,等待跟蹤它的進程對它進行操作。

3)就緒狀態:在run_queue隊列中的狀態

4)運行狀態:在run_queue隊列中的狀態

5)可中斷睡眠狀態:處於這個狀態的進程因爲等待某事件的發生,比如等待socket連接等,而被掛起

6)zombie狀態(殭屍):父進程沒有通過wait系列的系統調用,直接將子進程的task_struct也釋放掉。

7)退出狀態
Integer 和 int 兩者有什麼區別?

Integer是int的包裝類,默認值是null;int是基本數據類型,默認值是0;

Integer變量必須實例化後才能使用;int變量不需要;

Integer實際是對象的引用,指向此new的Integer對象;int是直接存儲數據值。

**分析總結**

1)Integer與new Integer不相等。new出來的對象被存放在堆,而非new的Integer常量則在常量池,兩者內存地址不同,因此判斷是false。

2)兩個值都是非new Integer,如果值在-128,127區間,則是true,反之爲false。

這是因爲java在編譯Integer i2 = 128時,被翻譯成:
```java
Integer i2 = Integer.valueOf(128);
```
而valueOf()函數會對-128到127之間的數進行緩存。

3)兩個都是new Integer,兩者判斷爲false,內存地址不同。

4)int和Integer對比不管是否new對象,兩者判斷都是true,因爲會把Integer自動拆箱爲int再去比。
什麼是 Java 內部類?

內部類是指把A類定義在另一個B類的內部。

例如:把類User定義在類Role中,類User就被稱爲內部類。
```java
class Role {
class User {
}
}
```
**1、內部類的訪問規則**

1)可以直接訪問外部類的成員,包括私有

​2)外部類要想訪問內部類成員,必須創建對象

**2、內部類的分類**

​1)成員內部類

​2)局部內部類

​3)靜態內部類

​4)匿名內部類
常用的垃圾收集器有哪些?

**Serial 收集器(新生代)**

Serial即串行,以串行的方式執行,是單線程的收集器。它只會使用一個線程進行垃圾收集工作,GC線程工作時,其它所有線程都將停止工作。

作爲新生代垃圾收集器的方式:-XX:+UseSerialGC

**ParNew 收集器(新生代)**

Serial收集器的多線程版本,需注意的是ParNew在單核環境下性能比Serial差,在多核條件下有優勢。

作爲新生代垃圾收集器的方式:-XX:+UseParNewGC


**Parallel Scavenge 收集器(新生代)**

多線程的收集器,目標是提高吞吐量(吞吐量 = 運行用戶程序的時間 / (運行用戶程序的時間 + 垃圾收集的時間))。

作爲新生代垃圾收集器的方式:-XX:+UseParallelGC

**Serial Old 收集器(老年代)**

Serial收集器的老年代版本。

作爲老年代垃圾收集器的方式:-XX:+UseSerialOldGC


**Parallel Old 收集器(老年代)**

Parallel Scavenge收集器的老年代版本。

作爲老年代垃圾收集器的方式:-XX:+UseParallelOldGC

**CMS 收集器(老年代)**

CMS(Concurrent Mark Sweep),收集器幾乎佔據着JVM老年代收集器的半壁江山,它劃時代的意義就在於垃圾回收線程幾乎能做到與用戶線程同時工作。

作爲老年代垃圾收集器的方式:-XX:+UseConcMarkSweepGC

**G1 收集器(新生代 + 老年代)**

面向服務端應用的垃圾收集器,在多CPU和大內存的場景下有很好的性能。HotSpot開發團隊賦予它的使命是未來可以替換掉CMS收集器。關注微信公衆號Java精選,內涵視頻資料、開源項目、源碼分析等等。

作爲老年代垃圾收集器的方式:-XX:+UseG1GC
生產環境中應用的 JVM 參數有哪些?

-server
-Xms6000M
-Xmx6000M
-Xmn500M
-XX:PermSize=500M
-XX:MaxPermSize=500M
-XX:SurvivorRatio=65536
-XX:MaxTenuringThreshold=0
-Xnoclassgc
-XX:+DisableExplicitGC
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSClassUnloadingEnabled
-XX:-CMSParallelRemarkEnabled
-XX:CMSInitiatingOccupancyFraction=90
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:+PrintClassHistogram
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-Xloggc:log/gc.log
什麼情況下會發生棧內存溢出?

棧溢出(StackOverflowError)

棧是線程私有的,他的生命週期與線程相同,每個方法在執行時會創建一個棧幀,用來存儲局部變量表,操作數棧,動態鏈接,方法出口燈信息。局部變量表又包含基本數據類型,對象引用類型。

若線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常。

若虛擬機在動態擴展棧時無法申請到足夠的內存空間,則拋出OutOfMemoryError異常。
常用的 JVM 調優配置參數有哪些?

假設參數爲-Xms20m -Xmx20m -Xss256k

XX比X的穩定性更差,並且版本更新不會進行通知和說明。

1、-Xms

s爲strating,表示堆內存起始大小

2、-Xmx

x爲max,表示最大的堆內存,一般來說-Xms和-Xmx的設置爲相同大小,當heap自動擴容時,會發生內存抖動,影響程序的穩定性

3、-Xmn

n爲new,表示新生代大小,-Xss規定了每個線程虛擬機棧(堆棧)的大小。

4、-XX:SurvivorRator=8

表示堆內存中新生代、老年代和永久代的比爲8:1:1

5、-XX:PretenureSizeThreshold=3145728

表示當創建(new)的對象大於3M的時候直接進入老年代

6、-XX:MaxTenuringThreshold=15

表示當對象的存活的年齡(minor gc一次加1)大於多少時,進入老年代

7、-XX:-DisableExplicirGC

表示是否(+表示是,-表示否)打開GC日誌
什麼是類加載器?

類加載器是指把類文件加載到虛擬機中,通過一個類的全限定名(包名+類型)來獲取描述該類的二進制字節流。

類加載器是Java語言的一項創新,最開始是爲了滿足Java Applet的需求而設計的。

類加載器目前在層次劃分、程序熱部署和代碼加密等領域被廣泛使用。
類加載器分爲哪幾類?

JVM默認提供了系統類加載器(JDK1.8),包括如下:

Bootstrap ClassLoader(系統類加載器)

Application ClassLoader(應用程序類加載器)

Extension ClassLoader(擴展類加載器)

Customer ClassLoader(自定義加載器)
可以自定義一個 java.lang.String 嗎?

答案是可以的,但是不能被加載使用。

這個主要是因爲加載器的委託機制,在類加載器的結構圖中,BootStrap是頂層父類,ExtClassLoader是BootStrap類的子類,ExtClassLoader是AppClassLoader的父類。當使用java.lang.String類時,Java虛擬機會將java.lang.String類的字節碼加載到內存中。

加載某個類時,優先使用父類加載器加載需要使用的類。加載自定義java.lang.String類,其使用的加載器是AppClassLoader,根據優先使用父類加載器原理,AppClassLoader加載器的父類爲ExtClassLoader,這時加載String使用的類加載器是ExtClassLoader,但類加載器ExtClassLoader在jre/lib/ext目錄下並不能找到自定義java.lang.String類。

然後使用ExtClassLoader父類的加載器BootStrap,父類加載器BootStrap在JRE/lib目錄的rt.jar找到了String.class,將其加載到內存中。
MyBatis 實現批量插入數據的方式有幾種?

MyBatis 實現批量插入數據的方式有幾種?

**1、MyBatis foreach標籤**

foreach主要用在構建in條件,在SQL語句中進行迭代一個集合。

foreach元素的屬性主要有item,index,collection,open,separator,close。

>item表示集合中每一個元素進行迭代時的別名
index指定一個名字,用於表示在迭代過程中,每次迭代到的位置
open表示該語句以什麼開始
separator表示在每次進行迭代之間以什麼符號作爲分隔符
close表示以什麼結束

collection必須指定該屬性,在不同情況下,值是不同的,主要體現3種情況:

若傳入單參數且參數類型是List時,collection屬性值爲list

若傳入單參數且參數類型是array數組時,collection的屬性值爲array

若傳入參數是多個時,需要封裝成Map

具體用法如下:

```xml
<insert id="insertForeach" parameterType="java.util.List" useGeneratedKeys="false">


insert into t_userinfo


(name, age, sex) values


<foreach collection="list" item="item" index="index" separator=",">

 

(#{item.name},#{item.age},#{item.sex})


</foreach>

 

</insert>
```
**2、MyBatis ExecutorType.BATCH**

Mybatis內置ExecutorType,默認是simple,該模式下它爲每個語句的執行創建一個新的預處理語句,單條提交sql。關注微信公衆號Java精選,內涵視頻資料、開源項目、源碼分析等等。

batch模式會重複使用已經預處理的語句,並批量執行所有更新語句。但batch模式Insert操作時,在事務沒有提交前,是無法獲取到自增的id。
什麼是自動裝箱?什麼是自動拆箱?

自動裝箱是指將基本數據類型重新轉化爲對象。
```java
public class Test {


public static void main(String[] args) {

 

Integer num = 9;


}
}
```
num = 9的值是屬於基本數據類型,原則上不能直接賦值給對象Integer。但是在JDK1.5版本後就可以進行這樣的聲明自動將基本數據類型轉化爲對應的封裝類型,成爲對象後可以調用對象所聲明的方法。

自動拆箱是指將對象重新轉化爲基本數據類型。
```java
public class Test {


public static void main(String[] args) {

 

// 聲明Integer對象

 

Integer num = 9;

 

// 隱含自動拆箱

 

System.out.print(num--);


}
}
```
由於對象不能直接進行運算,而是需要轉化爲基本數據類型後才能進行加減乘除。
```java
// 裝箱
Integer num = 10;
// 拆箱
int num1 = num;
```
一級緩存和二級緩存有什麼區別?

**一級緩存**

Mybatis一級緩存是指SQLSession

一級緩存的作用域是SQlSession

Mabits默認開啓一級緩存

同一個SqlSession中,執行相同的SQL查詢時第一次會去查詢數據庫,並寫在緩存中,第二次會直接從緩存中取。

當執行SQL時兩次查詢中間發生了增刪改的操作,則SQLSession的緩存會被清空。

每次查詢會先去緩存中找,如果找不到,再去數據庫查詢,然後把結果寫到緩存中。

Mybatis的內部緩存使用一個HashMap,其中key爲hashcode+statementId+sql語句,Value爲查詢出來的結果集映射成的java對象。

SqlSession執行insert、update、delete等操作commit後會清空該SQLSession緩存。

**二級緩存**

二級緩存是mapper級別的,Mybatis默認是沒有開啓二級緩存的。

第一次調用mapper下的SQL去查詢用戶的信息,查詢到的信息會存放到mapper對應的二級緩存區域。

第二次調用namespace下的mapper映射文件中,相同的sql去查詢用戶信息,會去對應的二級緩存內取結果。
Redis 支持那些數據類型?

**String字符串**

命令: set key value

string類型是二進制安全的,它可以包含任何數據,如圖片或序列化對象等。

string類型是Redis最基本的數據類型,一個鍵最大能存儲512MB。

**Hash(哈希)**

命令: hmset name key1 value1 key2 value2

Redis hash是一個鍵值(key=>value)對集合。

Redis hash是一個string類型的field和value的映射表,hash特別適合用於存儲對象。

**List(列表)**

Redis列表是簡單的字符串列表,按照插入順序排序。可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)

命令: lpush name value

key對應list的頭部添加字符串元素

命令: rpush name value

key對應list的尾部添加字符串元素

命令: lrem name index

key對應list中刪除count個和value相同的元素

命令: llen name

返回key對應list的長度


**Set(集合)**

命令: sadd name value

Redis的Set是string類型的無序集合。

集合是通過哈希表實現的,所以添加,刪除,查找的複雜度都是O(1)。


**zset(sorted set:有序集合)**

命令: zadd name score value

Redis zset和set一樣也是string類型元素的集合,且不允許重複的成員。

不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來爲集合中的成員進行從小到大的排序。

zset的成員是唯一的,但分數(score)卻可以重複。
什麼是 Redis 持久化?Redis 有哪幾種持久化方式?

Redis持久化是指把內存的數據寫到磁盤中去,防止服務因宕機導致內存數據丟失。

Redis提供了兩種持久化方式:RDB(默認)和AOF。

RDB是Redis DataBase縮寫,功能核心函數rdbSave(生成RDB文件)和rdbLoad(從文件加載內存)兩個函數

AOF是Append-only file縮寫,每當執行服務器(定時)任務或者函數時flushAppendOnlyFile函數都會被調用,這個函數執行以下兩個工作。

**AOF寫入與保存**

WRITE:根據條件,將aof_buf中的緩存寫入到AOF文件

SAVE:根據條件,調用fsync或fdatasync函數,將AOF文件保存到磁盤中。
什麼是緩存穿透?如何避免?

緩存穿透是指一般的緩存系統,都是按照key去緩存查詢,如果不存在對應的value,就應該去後端系統查找(比如DB)。

一些惡意的請求會故意查詢不存在的key,導致請求量大,造成後端系統壓力大,這就是做緩存穿透。

**如何避免?**

1)對查詢結果爲空的情況也進行緩存,緩存時間設置短一點或key對應的數據insert後清理緩存。

2)對一定不存在的key進行過濾,可以把所有可能存在的key放到一個大的Bitmap中,查詢時通過bitmap過濾。
什麼是緩存雪崩?何如避免?

緩存雪崩是指當緩存服務器重啓或大量緩存集中在某一個時間段內失效,這樣在失效時,會給後端系統帶來很大壓力,導致系統崩潰。

**如何避免?**

1)在緩存失效後,使用加鎖或隊列的方式來控制讀數據庫寫緩存的線程數量。如對某個key只允許一個線程查詢數據和寫緩存,其他線程等待。

2)實現二級緩存方式,A1爲原始緩存,A2爲拷貝緩存,A1失效時,切換訪問A2,A1緩存失效時間設置爲短期,A2設置爲長期。

3)不同的key,設置不同的過期時間,讓緩存失效的時間點儘量均勻。
MyBatis 是否支持延遲加載?其原理是什麼?

Mybatis僅支持association關聯對象和collection關聯集合對象的延遲加載。

> association指的就是一對一
collection指的就是一對多查詢

在Mybatis配置文件中,啓用延遲加載配置參數

```xml
lazyLoadingEnabled=true。
```

原理:使用CGLIB創建目標對象的代理對象,當調用目標方法時,進入攔截器方法。

比如調用a.getB().getName(),攔截器invoke()方法發現a.getB()是null值,就會單獨發送事先保存好的查詢關聯B對象的SQL語句,先查詢出B,然後再調用a.setB(b)賦值,最後再調用a.getB().getName()方法就有值了。幾乎所有的包括Hibernate、Mybatis,支持延遲加載的原理都是一樣的。
如何解決 MyBatis 轉義字符的問題?

**xml配置文件使用轉義字符**

```sql
SELECT * FROM test WHERE crate_time &lt;= #{crate_time} AND end_date &gt;= #{crate_time}
```

**xml轉義字符關係表**

|字符|轉義|備註|
|-|-|-|
|<|\&lt;| 小於號 |
|>|\&gt;|大於號|
|&|\&amp;|和|
|'|\&apos;|單引號|
|"|\&quot;|雙引號|

注意:XML中只有”<”和”&”是非法的,其它三個都是合法存在的,使用時都可以把它們轉義了,養成一個良好的習慣。

轉義前後的字符都會被xml解析器解析,爲了方便起見,使用
```xml
<![CDATA[...]]>
```
來包含不被xml解析器解析的內容。標記所包含的內容將表示爲純文本,其中...表示文本內容。

**但要注意的是:**

1)此部分不能再包含”]]>”;

2)不允許嵌套使用;

3)”]]>”這部分不能包含空格或者換行。
Zookeeper 是如何保證事務的順序一致性的?

Zookeeper採用了全局遞增的事務Id來標識,所有的proposal(提議)都在被提出的時候加上了zxid。

zxid實際上是一個64位的數字,高32位是epoch用來標識leader的週期。

如果有新的leader產生出來,epoch會自增,低32位用來遞增計數。

當新產生proposal的時候,會依據數據庫的兩階段過程,首先會向其他的server發出事務執行請求,如果超過半數的機器都能執行並且能夠成功,那麼就會開始執行。

Zookeeper 有哪幾種部署模式?

部署模式:單機模式、僞集羣模式、集羣模式。
Zookeeper 集羣最少要幾臺服務器,什麼規則?

集羣最少要3臺服務器,集羣規則爲2N+1臺,N>0,即3臺。
Zookeeper 有哪些典型應用場景?

Zookeeper是一個典型的發佈/訂閱模式的分佈式數據管理與協調框架,開發者可以使用它來進行分佈式數據的發佈和訂閱。

通過對Zookeeper中豐富的數據節點進行交叉使用,配合Watcher事件通知機制,可以非常方便的構建一系列分佈式應用中年都會涉及的核心功能。應用場景如下:

1)數據發佈/訂閱

2)負載均衡

3)命名服務

4)分佈式協調/通知

5)集羣管理

6)Master選舉

7)分佈式鎖

8)分佈式隊列
Paxos 和 ZAB 算法有什麼區別和聯繫?

**相同點**

兩者都存在一個類似於Leader進程的角色,由其負責協調多個Follower進程的運行;

Leader進程都會等待超過半數的Follower做出正確的反饋後,纔會將一個提案進行提交;

ZAB協議中每個Proposal中都包含一個epoch值來代表當前的Leader週期,而Paxos中名字爲Ballot。

**不同點**

Paxos是用來構建分佈式一致性狀態機系統,而ZAB用來構建高可用的分佈式數據主備系統(Zookeeper)。
Zookeeper 中 Java 客戶端都有哪些?

Java客戶端:

1)zk自帶的zkclient

2)Apache開源的Curator
Zookeeper 集羣支持動態添加服務器嗎?

動態添加服務器等同於水平擴容,而Zookeeper在這方面的表現並不是太好。兩種方式:

**1)全部重啓**

關閉所有Zookeeper服務,修改配置之後啓動。不影響之前客戶端的會話。

**2)逐一重啓**

在過半存活即可用的原則下,一臺機器重啓不影響整個集羣對外提供服務。這是比較常用的方式,在Zookeeper 3.5版本開始支持動態擴容。
Zookeeper 和 Nginx 的負載均衡有什麼區別?

Zookeeper的負載均衡是可以調控,而Nginx的負載均衡只是能調整權重,其他可控的都需要自己寫Lua插件;但是Nginx的吞吐量比Zookeeper大很多,具體按業務應用場景選擇用哪種方式。
Zookeeper 節點宕機如何處理?

Zookeeper本身是集羣模式,官方推薦不少於3臺服務器配置。Zookeeper自身保證當一個節點宕機時,其他節點會繼續提供服務。

假設其中一個Follower宕機,還有2臺服務器提供訪問,因爲Zookeeper上的數據是有多個副本的,並不會丟失數據;

如果是一個Leader宕機,Zookeeper會選舉出新的Leader。

Zookeeper集羣的機制是隻要超過半數的節點正常,集羣就可以正常提供服務。只有Zookeeper節點掛得太多,只剩不到一半節點提供服務,纔會導致Zookeeper集羣失效。

**Zookeeper集羣節點配置原則**

3個節點的cluster可以掛掉1個節點(leader可以得到2節 > 1.5)。

2個節點的cluster需保證不能掛掉任何1個節點(leader可以得到 1節 <=1)。
Socket 前後端通信是如何實現服務器集羣?

假設有兩臺A服務器服務S1和B服務器服務S2,應用服務S0

首先你接收客戶端(瀏覽器)請求的服務肯定是單點(一對一)連接,之後交給應用服務S0處理分配給兩臺服務器A和B上的服務S1和S2。

分配原則可以按一定的策略,如計數器等,按當前活躍連接數來決定分給哪臺服務器。也可以更科學的按兩臺服務器實際處理的數據量來分配,因爲有些連接可能一直空閒。

如果兩臺服務器上的服務通信正常且數據庫能夠承受壓力,訪問請求並不是太多的情況下,可以考慮使用數據庫傳遞消息,反之可以考慮使用Redis緩存技術。

如果需要即時傳遞消息,在其中一個服務器上的服務S1查找不到,把消息發給另外一臺服務器上的服務S2或把消息存儲至數據庫或緩存當中,然後通知其他服務有生產消息。類似MQ處理方式可參考ActiveMQ。
爲什麼要用 Redis 而不用 Map、Guava 做緩存?

緩存可以劃分爲本地緩存和分佈式緩存。

以Java爲例,使用自帶Map或者Guava類實現的是本地緩存,主要特點是輕量以及快速,它們的生命週期會隨着JVM的銷燬而結束,且在多實例的情況下,每個實例都需要各自保存一份緩存,緩存不具有一致性。

使用Redis或Memcached等被稱爲分佈式緩存,在多實例的情況下,各實例共用一份緩存數據,具有一致性,但是需要保持Redis或Memcached服務的高可用。
Redis 是單線程的嗎?爲什麼這麼快?

Redis是單線程的,至於爲什麼這麼快主要是因如下幾方面:

1)Redis是純內存數據庫,一般都是簡單的存取操作,線程佔用的時間很多,時間的花費主要集中在IO上,所以讀取速度快。

2)Redis使用的是非阻塞IO、IO多路複用,使用了單線程來輪詢描述符,將數據庫的開、關、讀、寫都轉換成了事件,減少了線程切換時上下文的切換和競爭。

3)Redis採用了單線程的模型,保證了每個操作的原子性,也減少了線程的上下文切換和競爭。

4)Redis避免了多線程的鎖的消耗。

5)Redis採用自己實現的事件分離器,效率比較高,內部採用非阻塞的執行方式,吞吐能力比較大
爲什麼使用消息隊列?

消息隊列中間件有很多,使用的場景廣泛。主要解決的核心問題是削峯填谷、異步處理、模塊解耦。
RabbitMQ 有幾種廣播類型?

direct(默認方式):最基礎最簡單的模式,發送方把消息發送給訂閱方,如果有多個訂閱者,默認採取輪詢的方式進行消息發送。

headers:與direct類似,只是性能很差,此類型幾乎用不到。

fanout:分發模式,把消費分發給所有訂閱者。

topic:匹配訂閱模式,使用正則匹配到消息隊列,能匹配到的都能接收到。
Kafka 的分區策略有哪些?

ProducerRecord內部數據結構:
>-- Topic (名字)
-- PartitionID ( 可選)
-- Key[( 可選 )
-- Value

提供三種構造函數形參:

```java
ProducerRecord(topic, partition, key, value)
ProducerRecord(topic, key, value)
ProducerRecord(topic, value)
```

第一種分區策略:指定分區號,直接將數據發送到指定的分區中。

第二種分區策略:沒有指定分區號,指定數據的key值,通過key取上hashCode進行分區。

第三種分區策略:沒有指定分區號,也沒有指定key值,直接輪循進行分區。

第四種分區策略:自定義分區。
RabbitMQ 有哪些重要組件?

ConnectionFactory(連接管理器):應用程序與RabbitMQ之間建立連接的管理器

Channel(信道):消息推送使用的通道

Exchange(交換器):用於接受、分配消息

Queue(隊列):用於存儲生產者的消息

RoutingKey(路由鍵):用於把生產者的數據分配到交換器上

BindKey(綁定鍵):用於把交換器的消息綁定到隊列上
RabbitMQ 有哪些重要角色?

**生產者:**

消息的生產者,負責創建和推送數據到消息服務器。

**消費者:**

消息的接收者,用於處理數據和確認消息。

**代理:**

RabbitMQ本身,用於扮演傳遞消息的角色,本身並不生產消息。
RabbitMQ 如何保證消息順序性?

RabbitMQ保證消息的順序性方式有兩種:

1)拆分多個queue,每個queue對應一個consumer(消費者),就是多一些queue。

2)一個queue,對應一個consumer(消費者),然後這個consumer內部用內存隊列做排隊,然後分發給底層不同的worker來處理。
如何保證消息消費的冪等性?

1、利用數據庫的唯一約束實現冪等

比如將訂單表中的訂單編號設置爲唯一索引,創建訂單時,根據訂單編號就可以保證冪等

2、去重表

根據數據庫的唯一性約束類似的方式來實現。其實現大體思路是:首先在去重表上建唯一索引,其次操作時把業務表和去重表放在同個本地事務中,如果出現重現重複消費,數據庫會拋唯一約束異常,操作就會回滾。

3、利用redis的原子性

每次操作都直接set到redis裏面,然後將redis數據定時同步到數據庫中。

4、多版本(樂觀鎖)控制

此方式多用於更新的場景下。其實現的大體思路是:給業務數據增加一個版本號屬性,每次更新數據前,比較當前數據的版本號是否和消息中的版本一致,如果不一致則拒絕更新數據,更新數據的同時將版本號+1。

5、狀態機機制

此方式多用於更新且業務場景存在多種狀態流轉的場景。

6、token機制

生產者發送每條數據時,增加一個全局唯一ID,這個ID通常是業務的唯一標識。在消費者消費時,需要驗證這個ID是否被消費過,如果沒消費過,則進行業務處理。處理結束後,在把這個ID存入Redis,同時設置狀態爲已消費。如果已經消費過,則清除Redis緩存的這個ID。
Kafka 消費者如何取消訂閱?

在KafkaConsumer類中使用unsubscribe()方法來取消主題的訂閱。該方法可以取消如下的訂閱方法:

>subscribe(Collection);方式實現的訂閱
subscribe(Pattern);方式實現的訂閱
assign() 方式實現的訂閱


取消訂閱使用代碼如下:

```java
consumer.unsubscribe();
```

將subscribe(Collection)或assign()中的集合參數設置爲空集合,也可以實現取消訂閱,以下三種方式都可以取消訂閱:
```java
consumer.unsubscribe();
consumer.subscribe(new ArrayList<String>());
consumer.assign(new ArrayList<TopicPartition>());
```
設計模式有多少種,都有哪些設計模式?

Java有23種設計模式

設計模式總體分爲三大類:

創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。

結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。

行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。

其實還有兩類:併發型模式和線程池模式。
設計模式的六大原則是什麼?

1)開閉原則(Open Close Principle)

開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。所以一句話概括就是:爲了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類,後面的具體設計中我們會提到這點。

2)里氏代換原則(Liskov Substitution Principle)

里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。LSP是繼承複用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行爲。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。

3)依賴倒轉原則(Dependence Inversion Principle)

這個是開閉原則的基礎,具體內容:真對接口編程,依賴於抽象而不依賴於具體。

4)接口隔離原則(Interface Segregation Principle)

大概意思是指使用多個隔離的接口,比使用單個接口要好。還是一個降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟件的設計思想,從大型軟件架構出發,爲了升級和維護方便。所以上文中多次出現:降低依賴,降低耦合。

5)迪米特法則(最少知道原則)(Demeter Principle)

爲什麼叫最少知道原則,就是說:一個實體應當儘量少的與其他實體之間發生相互作用,使得系統功能模塊相對獨立。

6)合成複用原則(Composite Reuse Principle)

原則是儘量使用合成/聚合的方式,而不是使用繼承。
什麼是單例模式?

單例模式的定義就是確保某一個類僅有一個實例,而且自行實例化並向整個系統提供這個實例,這個類稱爲單例類,它提供全局訪問的方法。

單例模式分爲:懶漢式單例、餓漢式單例、登記式單例三種。

單例模式特點:

1)單例類只能有一個實例。

2)單例類必須自己創建自己的唯一實例。

3)單例類必須給所有其他對象提供這一實例。

單例模式的優點是內存中只有一個對象,節省內存空間;避免頻繁的創建銷燬對象,可以提高性能;避免對資源的多重佔用,簡化訪問;爲整個系統提供一個全局訪問點。

單例模式的缺點是不適用於變化頻繁的對象;濫用單利將帶來一些問題,如爲了節省資源將數據庫連接池對象設計爲的單利類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認爲該對象是垃圾而被回收,這可能會導致對象狀態的丟失。

從名字上來說餓漢和懶漢,餓漢就是類一旦加載,就把單例初始化完成,保證getInstance的時候單例是已經被初始化,而懶漢比較懶,只有當調用getInstance的時候,纔回去初始化這個單例。

1、懶漢式單例,具體代碼如下:

```java
package com.yoodb;
//懶漢式單例類.在第一次調用的時候實例化自己
public class Singleton {
//私有的無參構造方法
private Singleton(){

}
//注意,這裏沒有final
private static Singleton single = null;
//靜態工廠方法
public synchronized static Singleton getInstance(){
if(single == null){
single = new Singleton();
}
return single;
}
}
```

2、餓漢式單例,具體代碼如下:

```java
package com.yoodb;

public class Singleton {
//私有的無參構造方法
private Singleton(){

}
//自動實例化
private final static Singleton single = new Singleton();
//靜態工廠方法
public static synchronized Singleton getInstance(){
return single;
}
}
```

3、登記式單例,具體代碼如下:

```java
package com.yoodb;

import java.util.HashMap;
import java.util.Map;

public class Singleton {
public static Map<String,Singleton> map = new HashMap<String,Singleton>();
static{
//將類名注入下次直接獲取
Singleton single = new Singleton();
map.put(single.getClass().getName(),single);
}
//保護式無參構造方法
protected Singleton(){}

public static Singleton getInstance(String name){
if(name == null){
name = Singleton.class.getName();
}
if(map.get(name) == null){
try {
map.put(name, Singleton.class.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return map.get(name);
}

public String create() {
return "創建一個單例!";
}

public static void main(String[] args) {
Singleton single = Singleton.getInstance(null);
System.out.println(single.create());
}
}
```
單例模式中餓漢式和懶漢式有什麼區別?

**1、線程安全**

餓漢式是線程安全的,可以直接用於多線程而不會出現問題,懶漢式就不行,它是線程不安全的,如果用於多線程可能會被實例化多次,失去單例的作用。

**2、資源加載**

餓漢式在類創建的同時就實例化一個靜態對象,不管之後會不會使用這個單例,都會佔據一定的內存資源,相應的在調用時速度也會更快。

懶漢式顧名思義,會延遲加載,在第一次使用該單例時纔會實例化對象出來,第一次掉用時要初始化,如果要做的工作比較多,性能上會有些延遲,第一次調用之後就和餓漢式。
單例模式都有哪些應用場景?

1)需要生成唯一序列;

2)需要頻繁實例化然後銷燬代碼的對象;

3)有狀態的工具類對象;

4)頻繁訪問數據庫或文件的對象。

例如:項目中讀取配置文件、數據庫連接池參數、spring中的bean默認是單例、線程池(threadpool)、緩存(cache)、日誌參數等程序的對象。

需要注意的事這些場景只能有一個實例,如果生成多個實例,就會導致許多問題產生,如:程序行爲異常、資源使用過量或數據不一致等。
什麼是線程安全?

如果代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的且其他變量的值也和預期的是一樣的,就是線程安全的。

或者也可以理解成一個類或程序所提供的接口對於線程來說是原子操作,多個線程之間的切換不會導致該接口的執行結果存在二義性,也就是說不用考慮同步的問題,那就是線程安全的。
Spring 框架中使用了哪些設計模式?

Spring框架中使用大量的設計模式,下面列舉比較有代表性的:

**代理模式**

AOP能夠將那些與業務無關(事務處理、日誌管理、權限控制等)封裝起來,便於減少系統的重複代碼,降低模塊間的耦合度有利於可拓展性和可維護性。

**單例模式**

Spring中bean的默認作用域是單例模式,在Spring配置文件中定義bean默認爲單例模式。

**模板方法模式**

模板方法模式是一種行爲設計模式,用來解決代碼重複的問題,如RestTemplate、JmsTemplate、JpaTemplate。

**包裝器設計模式**

Spring根據不同的業務訪問不同的數據庫,能夠動態切換不同的數據源。

**觀察者模式**

Spring事件驅動模型就是觀察者模式很經典的一個應用。

**工廠模式**

Spring使用工廠模式通過BeanFactory、ApplicationContext創建bean對象。
Spring MVC 執行流程是什麼?

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

2)DispatcherServlet收到請求調用HandlerMapping處理器映射器。

3)處理器映射器找到具體的處理器,根據xml配置、註解進行查找,生成處理器對象及處理器攔截器,並返回給DispatcherServlet。

4)DispatcherServlet調用HandlerAdapter處理器適配器。

5)HandlerAdapter經過適配調用具體的後端控制器Controller。

6)Controller執行完成返回ModelAndView。

7)HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet。

8)DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器。

9)ViewReslover解析後返回具體View。

10)DispatcherServlet根據View進行渲染視圖,將模型數據填充至視圖中。

11)DispatcherServlet響應客戶端。

**組件說明**

DispatcherServlet:作爲前端控制器,整個流程控制的中心,控制其它組件執行,統一調度,降低組件之間的耦合性,提高每個組件的擴展性。

HandlerMapping:通過擴展處理器映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,註解方式等。

HandlAdapter:通過擴展處理器適配器,支持更多類型的處理器。

ViewResolver:通過擴展視圖解析器,支持更多類型的視圖解析,例如:jsp、freemarker、pdf、excel等。
Spring MVC 如何解決請求中文亂碼問題?

解決GET請求中文亂碼問題

方式一:每次請求前使用encodeURI對URL進行編碼。

方式二:在應用服務器上配置URL編碼格式,在Tomcat配置文件server.xml中增加URIEncoding="UTF-8",然後重啓Tomcat即可。

```xml
<ConnectorURIEncoding="UTF-8"
port="8080" maxHttpHeaderSize="8192" maxThreads="150"
minSpareThreads="25" maxSpareThreads="75"connectionTimeout="20000"

 

disableUploadTimeout="true" URIEncoding="UTF-8" />
```

解決POST請求中文亂碼問題

方式一:在request解析數據時設置編碼格式:

```java
request.setCharacterEncoding("UTF-8");
```

方式二:使用Spring提供的編碼過濾器

在web.xml文件中配置字符編碼過濾器,增加如下配置:
```xml
<!--編碼過濾器-->
<filter>


<filter-name>encodingFilter</filter-name>


<filter-class>

 

org.springframework.web.filter.CharacterEncodingFilter


</filter-class>


<!-- 開啓異步支持-->


<async-supported>true</async-supported>


<init-param>

 

<param-name>encoding</param-name>

 

<param-value>utf-8</param-value>


</init-param>
</filter>
<filter-mapping>


<filter-name>encodingFilter</filter-name>


<url-pattern>/*</url-pattern>
</filter-mapping>
```

該過濾器的作用就是強制所有請求和響應設置編碼格式:
```java
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
```
Spring MVC 請求轉發和重定向有什麼區別?

請求轉發是瀏覽器發出一次請求,獲取一次響應,而重定向是瀏覽器發出2次請求,獲取2次請求。

請求轉發是瀏覽器地址欄未發生變化,是第1次發出的請求,而重定向是瀏覽器地址欄發生變化,是第2次發出的請求。

請求轉發稱爲服務器內跳轉,重定向稱爲服務器外跳轉。

請求轉發是可以獲取到用戶提交請求中的數據,而重定向是不可以獲取到用戶提交請求中的數據,但可以獲取到第2次由瀏覽器自動發出的請求中攜帶的數據。

請求轉發是可以將請求轉發到WEB-INF目錄下的(內部)資源,而重定向是不可以將請求轉發到WEB-INF目錄下的資源。
Spring MVC 中系統是如何分層?

表現層(UI):數據的展現、操作頁面、請求轉發。

業務層(服務層):封裝業務處理邏輯。

持久層(數據訪問層):封裝數據訪問邏輯。

各層之間的關係:MVC是一種表現層的架構,表現層通過接口調用業務層,業務層通過接口調用持久層,當下一層發生改變,不影響上一層的數據。
如何開啓註解處理器和適配器?

在配置文件中(一般命名爲springmvc.xml 文件)通過開啓配置:

```xml
<mvc:annotation-driven>
```
來實現註解處理器和適配器的開啓。
Spring MVC 如何設置重定向和轉發?

在返回值前面加“forward:”參數,可以實現轉發。

```java
"forward:getName.do?name=Java精選"
```

在返回值前面加“redirect:”參數,可以實現重定向。

```java
"redirect:https://blog.yoodb.com"
Spring MVC 中函數的返回值是什麼?

Spring MVC的返回值可以有很多類型,如String、ModelAndView等,但事一般使用String比較友好。
@RequestMapping 註解用在類上有什麼作用?

@RequestMapping註解是一個用來處理請求地址映射的註解,可用於類或方法上。

用於類上,則表示類中的所有響應請求的方法都是以該地址作爲父路徑。

比如

```java
@Controller
@RequestMapping("/test/")
public class TestController{

}
```

啓動的是本地服務,默認端口是8080,通過瀏覽器訪問的路徑就是如下地址:

```shell
http://localhost:8080/test/
```
Spring MVC 控制器是單例的嗎?

默認情況下是單例模式,在多線程進行訪問時存在線程安全的問題。

解決方法可以在控制器中不要寫成員變量,這是因爲單例模式下定義成員變量是線程不安全的。此爲部分面試題包含答案,更多面試題見微信小程序 “Java精選面試題”,3000+道面試題。

通過@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)或@Scope("prototype")可以實現多例模式。但是不建議使用同步,因爲會影響性能。

使用單例模式是爲了性能,無需頻繁進行初始化操作,同時也沒有必要使用多例模式。
RequestMethod 可以同時支持POST和GET請求訪問嗎?

 

POST請求訪問

```java
@RequestMapping(value="/wx/getUserById",method = RequestMethod.POST)
```

GET請求訪問

```java
@RequestMapping(value="/wx/getUserById/{userId}",method = RequestMethod.GET)
```

同時支持POST和GET請求訪問

```java
@RequestMapping(value="/subscribe/getSubscribeList/{customerCode}/{sid}/{userId}")
```
**RequestMethod常用的參數**

GET(SELECT):從服務器查詢,在服務器通過請求參數區分查詢的方式。

POST(CREATE):在服務器新建一個資源,調用insert操作。

PUT(UPDATE):在服務器更新資源,調用update操作。

DELETE(DELETE):從服務器刪除資源,調用delete語句。
Spring 依賴注入有幾種實現方式?

1)Constructor構造器注入:通過將@Autowired註解放在構造器上來完成構造器注入,默認構造器參數通過類型自動裝配。

```java
public class Test {
private UserInterface user;
@Autowired
private Test(UserInterface user) {
this.user = user;
}
}
```

2)Field接口注入:通過將@Autowired註解放在構造器上來完成接口注入。

```java
@Autowired //接口注入
private IUserService userService;
```

3)Setter方法參數注入:通過將@Autowired註解放在方法上來完成方法參數注入。

```java
public class Test {

 

private User user;

@Autowired
public void setUser(User user) {
this.user = user;
}
public String getuser() {
return user;
}
}
```
Spring 可以注入null或空字符串嗎?

完全可以
Spring 支持哪幾種 bean 作用域?

1)singleton:默認,每個容器中只有一個bean的實例,單例的模式由BeanFactory自身來維護。

2)prototype:爲每一個bean請求提供一個實例。

3)request:爲每一個網絡請求創建一個實例,在請求完成後,bean會失效並被垃圾回收器回收。

4)session:與request範圍類似,確保每個session中有一個bean的實例,在session過期後,bean會隨之失效。

5)global-session:全局作用域,global-session和Portlet應用相關。

當應用部署在Portlet容器中工作時,它包含很多portlet。

如果想要聲明讓所有的portlet共用全局的存儲變量的話,那麼這全局變量需要存儲在global-session中。

全局作用域與Servlet中的session作用域效果相同。
JDK1.8 中 ConcurrentHashMap 不支持空鍵值嗎?

首先明確一點HashMap是支持空鍵值對的,也就是null鍵和null值,而ConcurrentHashMap是不支持空鍵值對的。

查看一下JDK1.8源碼,HashMap類部分源碼,代碼如下:

```java
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
```

```java
static final int hash(Object key) {


int h;


return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
```

HashMap在調用put()方法存儲數據時會調用hash()方法來計算key的hashcode值,可以從hash()方法上得出當key==null時返回值是0,這意思就是key值是null時,hash()方法返回值是0,不會再調用key.hashcode()方法。

ConcurrentHashMap類部分源碼,代碼如下:

```java
public V put(K key, V value) {


return putVal(key, value, false);
}
```

```java
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {


if (key == null || value == null) throw new NullPointerException();


int hash = spread(key.hashCode());


int binCount = 0;


for (Node<K,V>[] tab = table;;) {

 

Node<K,V> f; int n, i, fh;

 

if (tab == null || (n = tab.length) == 0)

 


tab = initTable();

 

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {

 


if (casTabAt(tab, i, null,

 

 

 

new Node<K,V>(hash, key, value, null)))

 

 

break; // no lock when adding to empty bin

 

}

 

else if ((fh = f.hash) == MOVED)

 


tab = helpTransfer(tab, f);

 

else {

 


V oldVal = null;

 


synchronized (f) {

 

 

if (tabAt(tab, i) == f) {

 

 


if (fh >= 0) {

 

 

 

binCount = 1;

 

 

 

for (Node<K,V> e = f;; ++binCount) {

 

 

 


K ek;

 

 

 


if (e.hash == hash &&

 

 

 

 

((ek = e.key) == key ||

 

 

 

 

(ek != null && key.equals(ek)))) {

 

 

 

 

oldVal = e.val;

 

 

 

 

if (!onlyIfAbsent)

 

 

 

 


e.val = value;

 

 

 

 

break;

 

 

 


}

 

 

 


Node<K,V> pred = e;

 

 

 


if ((e = e.next) == null) {

 

 

 

 

pred.next = new Node<K,V>(hash, key,

 

 

 

 

 

 

 

value, null);

 

 

 

 

break;

 

 

 


}

 

 

 

}

 

 


}

 

 


else if (f instanceof TreeBin) {

 

 

 

Node<K,V> p;

 

 

 

binCount = 2;

 

 

 

if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,

 

 

 

 

 

 


value)) != null) {

 

 

 


oldVal = p.val;

 

 

 


if (!onlyIfAbsent)

 

 

 

 

p.val = value;

 

 

 

}

 

 


}

 

 

}

 


}

 


if (binCount != 0) {

 

 

if (binCount >= TREEIFY_THRESHOLD)

 

 


treeifyBin(tab, i);

 

 

if (oldVal != null)

 

 


return oldVal;

 

 

break;

 


}

 

}


}


addCount(1L, binCount);


return null;
}
```
ConcurrentHashmap在調用put()方法時調用了putVal()方法,而在該方法中判斷key爲null或value爲null時拋出空指針異常NullPointerException。

ConcurrentHashmap是支持併發的,當通過get()方法獲取對應的value值時,如果指定的鍵爲null,則爲NullPointerException,這主要是因爲獲取到的是null值,無法分辨是key沒找到null還是有key值爲null。
Spring 有哪些不同的通知類型?

通知(advice)是在程序中想要應用在其他模塊中橫切關注點的實現。

Advice主要有5種類型:

**@Before註解使用Advice**

前置通知(BeforeAdvice):在連接點之前執行的通知(advice),除非它拋出異常,否則沒有能力中斷執行流。

**@AfterReturning註解使用Advice**

返回之後通知(AfterRetuningAdvice):如果一個方法沒有拋出異常正常返回,在連接點正常結束之後執行的通知(advice)。

**@AfterThrowing註解使用Advice**

拋出(異常)後執行通知(AfterThrowingAdvice):若果一個方法拋出異常來退出的話,這個通知(advice)就會被執行。

**@After註解使用Advice**

後置通知(AfterAdvice):無論連接點是通過什麼方式退出的正常返回或者拋出異常都會執行在結束後執行這些通知(advice)。

**註解使用Advice**

圍繞通知(AroundAdvice):圍繞連接點執行的通知(advice),只有這一個方法調用。這是最強大的通知(advice)。
Spring AOP 連接點和切入點是什麼?

**連接點(Joint Point)**

連接點是指一個應用執行過程中能夠插入一個切面的點,可以理解成一個方法的執行或者一個異常的處理等。

連接點可以是調用方法、拋出異常、修改字段等時,切面代碼可以利用這些點插入到應用的正規流程中。使得程序執行過程中能夠應用通知的所有點。

在Spring AOP中一個連接點總是代表一個方法執行。如果在這些方法上使用橫切的話,所有定義在EmpoyeeManager接口中的方法都可以被認爲是一個連接點。

**切入點(Point cut)**

切入點是一個匹配連接點的斷言或者表達式,如果通知定義了“什麼”和“何時”,那麼切點就定義了“何處”。

通知(Advice)與切入點表達式相關聯,切入點用於準確定位,確定在什麼地方應用切面通知。

例如表達式

```java
execution(* EmployeeManager.getEmployeeById(...))
```

可以匹配EmployeeManager接口的getEmployeeById()。

Spring默認使用AspectJ切入點表達式,由切入點表達式匹配的連接點概念是AOP的核心。此爲部分面試題包含答案,更多面試題見微信小程序 “Java精選面試題”,3000+道面試題。
Spring AOP 代理模式是什麼?

代理模式是使用非常廣泛的設計模式之一。

代理模式是指給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。通俗的來講代理模式就是生活中常見的房產中介。

Spring AOP是基於代理實現的。

Spring AOP代理是一個由AOP框架創建的用於在運行時實現切面協議的對象。

Spring AOP默認爲AOP代理使用標準的JDK動態代理,這使得任何接口(或者接口的集合)可以被代理。

Spring AOP也可以使用CGLIB代理,如果業務對象沒有實現任何接口那麼默認使用CGLIB。
Spring 框架有哪些特點?

1)方便解耦,簡化開發

通過Spring提供的IoC容器,可以將對象之間的依賴關係交由Spring進行控制,避免編碼所造成的過度耦合。使用Spring可以使用戶不必再爲單實例模式類、屬性文件解析等底層的需求編碼,可以更專注於上層的業務邏輯應用。

2)支持AOP面向切面編程

通過Spring提供的AOP功能,方便進行面向切面的編程,許多不容易用傳統OOP實現的功能可以通過AOP輕鬆完成。

3)支持聲明事物

通過Spring可以從單調繁瑣的事務管理代碼中解脫出來,通過聲明式方式靈活地進行事務的管理,提高開發效率和質量。

4)方便程序測試

使用非容器依賴的編程方式進行幾乎所有的測試工作,通過Spring使得測試不再是高成本的操作,而是隨手可做的事情。Spring對Junit4支持,可以通過註解方便的測試Spring程序。

5)方便便捷集成各種中間件框架

Spring可以降低集成各種中間件框架的難度,Spring提供對各種框架如Struts,Hibernate、Hessian、Quartz等的支持。

6)降低Java EE API使用難度

Spring對很多Java EE API如JDBC、JavaMail、遠程調用等提供了一個簡易的封裝層,通過Spring的簡易封裝,輕鬆實現這些Java EE API的調用。
Spring 是由哪些模塊組成的?

Core module

Bean module

Context module

Expression Language module

JDBC module

ORM module

OXM module

Java Messaging Service(JMS) module

Transaction module

Web module

Web-Servlet module

Web-Struts module

Web-Portlet module
Spring 提供幾種配置方式設置元數據?

Spring配置元數據可以採用三種方式,可以混合使用。

**1)基於XML的配置元數據**

使用XML文件標籤化配置Bean的相關屬性。

|屬性|描述|對應註解|
|-|-|-|
class|此項必填,指定要創建Bean的類(全路徑)|無|
id|全局唯一 指定bean的唯一標示符|無|
name|全局唯一 指定bean的唯一標示符|@Bean的name屬性|
scope|創建bean的作用域|@Scope|
singleton|是否單例|@Scope(value=SCOPE_SINGLETON)|
depends-on|用來表明依賴關係|@DependsOn|
depends-check|依賴檢查|無|
autowire|自動裝配 默認NO|@Bean的autowire屬性|
init-method|對象初始化後調用的方法|@Bean 的initMethod屬性|
destroy-method|對象銷燬前調用的方法|@Bean 的destroyMethod|
lazy-init|容器啓動時不會初始化,只有使用時初始化|@Lazy|
primary|容器中有多個相同類型的bean時,autowired時優先使用primary=true|@Primary|
factory-method|工廠創建對象的方法|無|
factory-bean|工廠bean|無|

**2)基於註解的配置元數據**

```java
@Component 標識一個被Spring管理的對象
@Respository 標識持久層對象
@Service 標識業務層對象
@Controller 標識表現層對象
```

Spring Boot項目中Contrller層、Service層、Dao層均採用註解的方式,在對應類上加相對應的@Controller,@Service,@Repository,@Component等註解。

需要注意的是使用@ComponentScan註解,若是Spring Boot框架項目,掃描組件的默認路徑與Application.class同級包。

**3)基於Java的配置元數據**

默認情況下,方法名即爲Bean名稱。Java使用Configuration類配置bean,</bean>對應Spring註解@Bean。

目前常用的方式是第二種和第三種,也經常結合使用。

HTTP1.0 和 HTTP1.1 有什麼區別?

**1)長連接(Persistent Connection)**

HTTP1.0規定瀏覽器與服務器只保持短暫的連接,瀏覽器的每次請求都需要與服務器建立一個TCP連接,服務器完成請求處理後立即斷開TCP連接,服務器不跟蹤每個客戶也不記錄過去的請求。

HTTP1.1支持長連接,在請求頭中有Connection:Keep-Alive。在一個TCP連接上可以傳送多個HTTP請求和響應,減少了建立和關閉連接的消耗和延遲。

**2)節省帶寬**

HTTP1.0中存在一些浪費帶寬的現象,例如客戶端只是需要某個對象的一部分,而服務器卻將整個對象傳輸過去,並且不支持斷點續傳功能。

HTTP1.1支持只發送header信息,不攜帶其他任何body信息,如果服務器認爲客戶端有權限請求服務器,則返回100狀態碼,客戶端接收到100狀態碼後把請求body發送到服務器;如果返回401狀態碼,客戶端無需發送請求body節省帶寬。

**3)HOST域**

HTTP1.0沒有host域,HTTP1.0中認爲每臺服務器都綁定一個唯一的IP地址,因此,請求消息中的URL並沒有傳遞主機名(hostname)。

HTTP1.1的請求消息和響應消息都支持host域,且請求消息中若是host域會報告400 Bad Request錯誤。一臺物理服務器上可以同時存在多個虛擬主機(Multi-homed Web Servers),並且它們可以共享一個IP地址。

**4)緩存處理**

HTTP1.0中主要使用header裏的If-Modified-Since,Expires來做爲緩存判斷的標準。

HTTP1.1引入了更多的緩存控制策略如Entity tag、If-Unmodified-Since、If-Match、If-None-Match等更多可供選擇的緩存頭來控制緩存策略。

**5)錯誤通知管理**

HTTP1.1中新增24個錯誤狀態響應碼,比如409(Conflict)表示請求的資源與資源的當前狀態發生衝突;410(Gone)表示服務器上的某個資源被永久性的刪除。
HTTP1.1 和 HTTP2.0 有什麼區別?

**1)多路複用**

HTTP2.0使用了多路複用的技術,做到同一個連接併發處理多個請求,而且併發請求的數量比HTTP1.1大了好幾個數量級。

HTTP1.1可以建立多個TCP連接來支持處理更多併發的請求,但是創建TCP連接本身也是有開銷的。

**2)頭部數據壓縮**

HTTP1.1中HTTP請求和響應都是由狀態行、請求/響應頭部、消息主體三部分組成。

一般而言,消息主體都會經過gzip壓縮或本身傳輸的就是壓縮過後的二進制文件,但狀態行和頭部卻沒有經過任何壓縮,直接以純文本傳輸。

隨着Web功能越來越複雜,每個頁面產生的請求數也越來越多,導致消耗在頭部的流量越來越多,尤其是每次都要傳輸UserAgent、Cookie等不會頻繁變動的內容,完全是一種浪費資源的體現。

HTTP1.1不支持header數據的壓縮,而HTTP2.0使用HPACK算法對header的數據進行壓縮,壓縮的數據體積小,在網絡上傳輸更快。

**3)服務器推送**

服務端推送是一種在客戶端請求前發送數據的機制。

網頁中使用了許多資源:HTML、樣式表、腳本、圖片等,在HTTP1.1中這些資源每一個都必須明確地請求,這是一個很慢的過程。

瀏覽器從獲取HTML開始,然後在它解析和評估頁面時獲取更多的資源,因爲服務器必須等待瀏覽器做每一個請求,網絡經常是空閒和未充分使用的。

HTTP2.0引入了server push,允許服務端推送資源給瀏覽器,在瀏覽器明確請求前,不用客戶端再次創建連接發送請求到服務器端獲取,客戶端可以直接從本地加載這些資源,不用再通過網絡。
Spring Boot 支持哪幾種內嵌容器?

Spring Boot支持的內嵌容器有Tomcat(默認)、Jetty、Undertow和Reactor Netty(v2.0+),藉助可插拔(SPI)機制的實現,開發者可以輕鬆進行容器間的切換。
什麼是 Spring Boot Stater?

Spring Boot在配置上相比Spring要簡單許多,其核心在於Spring Boot Stater。

Spring Boot內嵌容器支持Tomcat、Jetty、Undertow等應用服務的starter啓動器,在應用啓動時被加載,可以快速的處理應用所需要的一些基礎環境配置。

starter解決的是依賴管理配置複雜的問題,可以理解成通過pom.xml文件配置很多jar包組合的maven項目,用來簡化maven依賴配置,starter可以被繼承也可以依賴於別的starter。

比如spring-boot-starter-web包含以下依賴:

```java
org.springframework.boot:spring-boot-starter
org.springframework.boot:spring-boot-starter-tomcat
org.springframework.boot:spring-boot-starter-validation
com.fasterxml.jackson.core:jackson-databind
org.springframework:spring-web
org.springframework:spring-webmvc
```

starter負責配與Sping整合相關的配置依賴等,使用者無需關心框架整合帶來的問題。

比如使用Sping和JPA訪問數據庫,只需要項目包含spring-boot-starter-data-jpa依賴就可以完美執行。
Spring Boot Stater 有什麼命名規範?

1)Spring Boot官方項目命名方式

前綴:spring-boot-starter-*,其中*是指定類型的應用程序名稱。

格式:spring-boot-starter-{模塊名}

舉例:spring-boot-starter-web、spring-boot-starter-jdbc

2)自定義命名方式

後綴:*-spring-boot-starter

格式:{模塊名}-spring-boot-starter

舉例:mybatis-spring-boot-starter
Spring Boot 啓動器都有哪些?

Spring Boot在org.springframework.boot組下提供了以下應用程序啓動器:

|名稱|描述|
|-|-|
|spring-boot-starter|核心啓動器,包括自動配置支持,日誌記錄和YAML|
|spring-boot-starter-activemq|使用Apache ActiveMQ的JMS消息傳遞啓動器|
|spring-boot-starter-amqp|使用Spring AMQP和Rabbit MQ的啓動器 |
|spring-boot-starter-aop|使用Spring AOP和AspectJ進行面向方面編程的啓動器|
|spring-boot-starter-artemis|使用Apache Artemis的JMS消息傳遞啓動器 |
|spring-boot-starter-batch|使用Spring Batch的啓動器 |
|spring-boot-starter-cache|使用Spring Framework的緩存支持的啓動器|
|spring-boot-starter-data-cassandra|使用Cassandra分佈式數據庫和Spring Data Cassandra的啓動器 |
|spring-boot-starter-data-cassandra-reactive|使用Cassandra分佈式數據庫和Spring Data Cassandra Reactive的啓動器|
|spring-boot-starter-data-couchbase|使用Couchbase面向文檔的數據庫和Spring Data Couchbase的啓動器|
|spring-boot-starter-data-couchbase-reactive|使用Couchbase面向文檔的數據庫和Spring Data Couchbase Reactive的啓動器 |
|spring-boot-starter-data-elasticsearch|使用Elasticsearch搜索和分析引擎以及Spring Data Elasticsearch的啓動器|
|spring-boot-starter-data-jdbc|使用Spring Data JDBC的啓動器|
|spring-boot-starter-data-jpa|將Spring Data JPA與Hibernate結合使用的啓動器|
|spring-boot-starter-data-ldap|使用Spring Data LDAP的啓動器|
|spring-boot-starter-data-mongodb|使用MongoDB面向文檔的數據庫和Spring Data MongoDB的啓動器 |
|spring-boot-starter-data-mongodb-reactive|使用MongoDB面向文檔的數據庫和Spring Data MongoDB Reactive的啓動器 |
|spring-boot-starter-data-neo4j|使用Neo4j圖形數據庫和Spring Data Neo4j的啓動器工具 |
|spring-boot-starter-data-r2dbc|使用Spring Data R2DBC的啓動器|
|spring-boot-starter-data-redis|使用Redis鍵值數據存儲與Spring Data Redis和Lettuce客戶端的啓動器|
|spring-boot-starter-data-redis-reactive|將Redis鍵值數據存儲與Spring Data Redis Reacting和Lettuce客戶端一起使用的啓動器 |
|spring-boot-starter-data-rest|使用Spring Data REST在REST上公開Spring數據存儲庫的啓動器|
|spring-boot-starter-freemarker|使用FreeMarker視圖構建MVC Web應用程序的啓動器 |
|spring-boot-starter-groovy-templates|使用Groovy模板視圖構建MVC Web應用程序的啓動器|
|spring-boot-starter-hateoas|使用Spring MVC和Spring HATEOAS構建基於超媒體的RESTful Web應用程序的啓動器 |
|spring-boot-starter-integration|使用Spring Integration的啓動器|
|spring-boot-starter-jdbc|結合使用JDBC和HikariCP連接池的啓動器 |
|spring-boot-starter-jersey|使用JAX-RS和Jersey構建RESTful Web應用程序的啓動器。的替代品spring-boot-starter-web|
|spring-boot-starter-jooq|使用jOOQ訪問SQL數據庫的啓動器。替代spring-boot-starter-data-jpa或spring-boot-starter-jdbc |
|spring-boot-starter-json|讀寫JSON啓動器 |
|spring-boot-starter-jta-atomikos|使用Atomikos的JTA交易啓動器|
|spring-boot-starter-mail|使用Java Mail和Spring Framework的電子郵件發送支持的啓動器|
|spring-boot-starter-mustache|使用Mustache視圖構建Web應用程序的啓動器|
|spring-boot-starter-oauth2-client|使用Spring Security的OAuth2 / OpenID Connect客戶端功能的啓動器|
|spring-boot-starter-oauth2-resource-server|使用Spring Security的OAuth2資源服務器功能的啓動器|
|spring-boot-starter-quartz|啓動器使用Quartz Scheduler |
|spring-boot-starter-rsocket|用於構建RSocket客戶端和服務器的啓動器 |
|spring-boot-starter-security|使用Spring Security的啓動器 |
|spring-boot-starter-test|用於使用包括JUnit Jupiter,Hamcrest和Mockito在內的庫測試Spring Boot應用程序的啓動器|
|spring-boot-starter-thymeleaf|使用Thymeleaf視圖構建MVC Web應用程序的啓動器|
|spring-boot-starter-validation|初學者,可將Java Bean驗證與Hibernate Validator結合使用|
|spring-boot-starter-web|使用Spring MVC構建Web(包括RESTful)應用程序的啓動器。使用Tomcat作爲默認的嵌入式容器 |
|spring-boot-starter-web-services|使用Spring Web Services的啓動器 |
|spring-boot-starter-webflux|使用Spring Framework的反應式Web支持構建WebFlux應用程序的啓動器|
|spring-boot-starter-websocket|使用Spring Framework的WebSocket支持構建WebSocket應用程序的啓動器|


除應用程序啓動器外,以下啓動程序還可用於添加生產環境上線功能:
|名稱|描述|
|-|-|
|spring-boot-starter-actuator|使用Spring Boot Actuator的程序,該啓動器提供了生產環境上線功能,可幫助您監視和管理應用程序|


Spring Boot還包括以下啓動程序,如果想排除或替換啓動器,可以使用這些啓動程序:
|名稱|描述|
|-|-|
|spring-boot-starter-jetty|使用Jetty作爲嵌入式servlet容器的啓動器。替代spring-boot-starter-tomcat|
|spring-boot-starter-log4j2|使用Log4j2進行日誌記錄的啓動器。替代spring-boot-starter-logging|
|spring-boot-starter-logging|使用Logback進行日誌記錄的啓動器。默認記錄啓動器|
|spring-boot-starter-reactor-netty|啓動器,用於將Reactor Netty用作嵌入式反應式HTTP服務器。|
|spring-boot-starter-tomcat|啓動器,用於將Tomcat用作嵌入式servlet容器。默認使用的servlet容器啓動器spring-boot-starter-web|
|spring-boot-starter-undertow|使用Undertow作爲嵌入式servlet容器的啓動器。替代spring-boot-starter-tomcat|

Spring Cloud 斷路器的作用是什麼?

分佈式架構中斷路器模式的作用基本類似的,當某個服務單元發生故障,類似家用電器發生短路後,通過斷路器的故障監控,類似熔斷保險絲,向調用方返回一個錯誤響應,不需要長時間的等待。這樣就不會出現因被調用的服務故障,導致線程長時間佔用而不釋放,避免了在分佈式系統中故障的蔓延。
Spring Cloud 核心組件有哪些?

Eureka:服務註冊與發現,Eureka服務端稱服務註冊中心,Eureka客戶端主要處理服務的註冊與發現。

Feign:基於Feign的動態代理機制,根據註解和選擇的機器,拼接請求url地址,發起請求。

Ribbon:負載均衡,服務間發起請求時基於Ribbon實現負載均衡,從一個服務的多臺機器中選擇一臺。

Hystrix:提供服務隔離、熔斷、降級機制,發起請求是通過Hystrix提供的線程池,實現不同服務調用之間的隔離,避免服務雪崩問題。

Zuul:服務網關,前端調用後端服務,統一由Zuul網關轉發請求給對應的服務。
Spring Cloud 如何實現服務的註冊?

1、當服務發佈時,指定對應的服務名,將服務註冊到註冊中心,比如Eureka、Zookeeper等。

2、註冊中心加@EnableEurekaServer註解,服務使用@EnableDiscoveryClient註解,然後使用Ribbon或Feign進行服務直接的調用發現。

**服務發現組件的功能**

1)服務註冊表

服務註冊表是一個記錄當前可用服務實例的網絡信息的數據庫,是服務發現機制的核心。

服務註冊表提供查詢API和管理API,使用查詢API獲得可用的服務實例,使用管理API實現註冊和註銷;

2)服務註冊

3)健康檢查
什麼是 Spring Cloud Config?

Spring Cloud Config爲分佈式系統中的外部配置提供服務器和客戶端支持,可以方便的對微服務各個環境下的配置進行實時更新、集中式管理。

Spring Cloud Config分爲Config Server和Config Client兩部分。

Config Server負責讀取配置文件,並且暴露Http API接口,Config Client通過調用Config Server的接口來讀取配置文件。
Spring Cloud Eureka 自我保護機制是什麼?

1)假設某一時間某個微服務宕機了,而Eureka不會自動清除,依然對微服務的信息進行保存。

2)在默認的情況系,Eureka Server在一定的時間內沒有接受到微服務的實例心跳(默認爲90秒),Eureka Server將會註銷該實例。

3)但是在網絡發生故障時微服務在Eureka Server之間是無法通信的,因爲微服務本身實例是健康的,此刻本不應該註銷這個微服務。那麼Eureka自我保護模式就解決了這個問題。

4)當Eureka Server節點在短時間內丟失過客戶端時包含發生的網絡故障,那麼節點就會進行自我保護。

5)一但進入自我保護模式,Eureka Server就會保護服務註冊表中的信息,不再刪除服務註冊表中的數據註銷任何微服務。

6)當網絡故障恢復後,這個Eureka Server節點會自動的退出自我保護機制。

7)在自我保護模式中Eureka Server會保護服務註冊表中的信息,不在註銷任何服務實例,當重新收到心跳恢復閥值以上時,這個Eureka Server節點就會自動的退出自我保護模式,這種設計模式是爲了寧可保留錯誤的服務註冊信息,也不盲目的註銷刪除任何可能健康的服務實例。

8)自我保護機制就是一種對網絡異常的安全保護實施,它會保存所有的微服務,不管健康或不健康的微服務都會保存,而不會盲目的刪除任何微服務,可以讓Eureka集羣更加的健壯、穩定。

9)在Eureka Server端可取消自我保護機制,但是不建議取消此功能。
常用的併發工具類有哪些?

**CountDownLatch閉鎖**

CountDownLatch是一個同步計數器,初始化時傳入需要計數線程的等待數,可能是等於或大於等待執行完的線程數。調用多個線程之間的同步或說起到線程之間的通信(不是互斥)一組線程等待其他線程完成工作後在執行,相當於加強的join。

**CyclicBarrier柵欄**

CyclicBarrier字面意思是柵欄,是多線程中重要的類,主要用於線程之間互相等待的問題,初始化時傳入需要等待的線程數。

作用:讓一組線程達到某個屏障被阻塞直到一組內最後一個線程達到屏蔽時,屏蔽開放,所有被阻塞的線程纔會繼續運行。

**Semophore信號量**

semaphore稱爲信號量是操作系統的一個概念,在Java併發編程中,信號量控制的是線程併發的數量。

作用:semaphore管理一系列許可每個acquire()方法阻塞,直到有一個許可證可以獲得,然後拿走許可證,每個release方法增加一個許可證,這可能會釋放一個阻塞的acquire()方法,然而並沒有實際的許可保證這個對象,semaphore只是維持了一個可獲取許可的數量,主要控制同時訪問某個特定資源的線程數量,多用在流量控制。

 

 

**Exchanger交換器**

Exchange類似於交換器可以在隊中元素進行配對和交換線程的同步點,用於兩個線程之間的交換。

具體來說,Exchanger類允許兩個線程之間定義同步點,當兩個線程達到同步點時,它們交換數據結構,因此第一個線程的數據結構進入到第二個線程當中,第二個線程的數據結構進入到第一個線程當中。
併發和並行有什麼區別?

並行(parallellism)是指兩個或者多個事件在同一時刻發生,而併發(parallellism)是指兩個或多個事件在同一時間間隔發生。

並行是在不同實體上的多個事件,而併發是在同一實體上的多個事件。

並行是在一臺處理器上同時處理多個任務(Hadoop分佈式集羣),而併發在多臺處理器上同時處理多個任務。
JSP 模版引擎如何解析 ${} 表達式?

目前開發中已經很少使用JSP模版引擎,JSP雖然是一款功能比較強大的模板引擎,並被廣大開發者熟悉,但它前後端耦合比較高。

其次是JSP頁面的效率沒有HTML高,因爲JSP是同步加載。而且JSP需要Tomcat應用服務器部署,但不支持Nginx等,已經快被時代所淘汰。

JSP頁面中使用\${表達式}展示數據,但是頁面上並沒有顯示出對應數據,而是把\${表達式}當作純文本顯示。

原因分析:這是由於jsp模版引擎默認會無視EL表達式,需要手動設置igNoreEL爲false。

```java
<%@ page isELIgnored="false" %>
```
什麼是服務熔斷?什麼是服務降級?

**熔斷機制**

熔斷機制是應對雪崩效應的一種微服務鏈路保護機制。

當某個微服務不可用或者響應時間過長時會進行服務降級,進而熔斷該節點微服務的調用,快速返回“錯誤”的響應信息。

當檢測到該節點微服務調用響應正常後恢復調用鏈路。

在Spring Cloud框架裏熔斷機制通過Hystrix實現,Hystrix會監控微服務間調用的狀況,當失敗的調用到一定閾值,缺省是5秒內調用20次,如果失敗,就會啓動熔斷機制。

**服務降級**

服務降級一般是從整體負荷考慮,當某個服務熔斷後,服務器將不再被調用,此時客戶端可以準備一個本地fallback回調,返回一個缺省值。這樣做目的是雖然水平下降,但是是可以使用,相比直接掛掉要強很多。

Spring Boot 和 Spring Cloud 之間有什麼聯繫?

Spring Boot是Spring推出用於解決傳統框架配置文件冗餘,裝配組件繁雜的基於Maven的解決方案,旨在快速搭建單個微服務。

Spring Cloud專注於解決各個微服務之間的協調與配置,整合並管理各個微服務,爲各個微服務之間提供配置管理、服務發現、斷路器、路由、事件總線等集成服務。

Spring Cloud是依賴於Spring Boot,而Spring Boot並不依賴與Spring Cloud。

Spring Boot專注於快速、方便的開發單個的微服務個體,Spring Cloud是關注全局的服務治理框架。
你都知道哪些微服務技術棧?

|微服務描述|技術名稱|
|-|-|
|服務開發|Springboot、Spring、SpringMVC|
|服務配置與管理|Netflix公司的Archaius、阿里的Diamond等|
|服務註冊與發現|Eureka、Consul、Zookeeper等|
|服務調用|REST、RPC、gRPC|
|服務熔斷器|Hystrix、Envoy等|
|負載均衡|Ribbon、Nginx等|
|服務接口調用(客戶端調用服務發簡單工具)|Feign等|
|消息隊列|kafka、RabbitMQ、ActiveMQ等|
|服務配置中心管理|SpringCloudConfig、Chef等|
|服務路由(API網關)|Zuul等|
|服務監控|Zabbix、Nagios、Metrics、Spectator等|
|全鏈路追蹤|Zipkin、Brave、Dapper等|
|服務部署|Docker、OpenStack、Kubernetes等|
|數據流操作開發包|SpringCloud Stream(封裝與Redis,Rabbit、Kafka等發送接收消息)|
|事件消息總線|Spring Cloud Bus|
接口和抽象類有什麼區別?

|比較方面|抽象類說明|接口說明|
|-|-|-|
|默認方法|抽象類可以有默認的方法實現|JDK1。8之前版本,接口中不存在方法的實現|
|實現方式|子類使用extends關鍵字來繼承抽象類。如果子類不是抽象類,子類需要提供抽象類中所聲明方法的實現|子類使用implements來實現接口,需要提供接口中所有聲明的實現。|
|構造器|抽象類中可以有構造器|接口中不能|
|和正常類區別|抽象類不能被實例化|接口則是完全不同的類型|
|訪問修飾符|抽象方法可以有public、protected、default等修飾|接口默認是public,不能使用其他修飾符|
|多繼承|一個子類只能存在一個父類|一個子類可以存在多個接口|
|添加新方法|抽象類中添加新方法,可以提供默認的實現,因此可以不修改子類現有的代碼|如果往接口中添加新方法,則子類中需要實現該方法|


什麼是線程死鎖?

線程死鎖是指多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由於線程被無限期地阻塞,因此程序不可能正常終止。

產生死鎖必須具備以下四個條件:

互斥條件:該資源任意一個時刻只由一個線程佔用;

請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源持有不釋放;

不剝奪條件:線程已獲得的資源,在末使用完之前不能被其他線程強行剝奪,只有使用完畢後才釋放資源;

循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。
如何避免線程死鎖?

只需要破壞產生死鎖的四個條件中任意一個就可以避免線程死鎖,但是互斥條件是沒有辦法破壞的,因爲鎖的意義就是想讓線程之間存在資源互斥訪問。

1)破壞請求與保持條件,一次性申請所有的資源;

2)破壞不剝奪條件,佔用部分資源的線程進一步申請其他資源時如果申請不到,使其主動釋放佔有的資源;

3)破壞循環等待條件,按序申請資源來預防線程死鎖,按某一順序申請資源,釋放資源則反序釋放。
父類中靜態方法能否被子類重寫?

父類中靜態方法不能被子類重寫。

重寫只適用於實例方法,不能用於靜態方法,而且子類當中含有和父類相同簽名的靜態方法,一般稱之爲隱藏。

```java
public class A {

 

 

public static String a = "這是父類靜態屬性";

 

 

public static String getA() {

 

return "這是父類靜態方法";


}

 

}
```

```java
public class B extends A{

 

 

public static String a = "這是子類靜態屬性";

 

 

public static String getA() {

 

return "這是子類靜態方法";


}

 

 

public static void main(String[] args) {

 

A a = new B();

 

System.out.println(a.getA());


}
}
```

如上述代碼所示,如果能夠被重寫,則輸出的應該是“這是子類靜態方法”。與此類似的是,靜態變量也不能被重寫。如果想要調用父類的靜態方法,應該使用類來直接調用。

什麼是不可變對象?有什麼好處?

不可變對象是指對象一旦被創建,狀態就不能再改變,任何修改都會創建一個新的對象。

比如String、Integer及其它包裝類。

不可變對象最大的好處是線程安全。
靜態變量和實例變量有什麼區別?

靜態變量:獨立存在的變量,只是位置放在某個類下,可以直接類名加點調用靜態變量名使用。並且是項目或程序一啓動運行到該類時就直接常駐內存。不需要初始化類再調用該變量。用關鍵字static聲明。靜態方法也是同樣,可以直接調用。

實例變量:相當於該類的屬性,需要初始化這個類後纔可以調用。如果這個類未被再次使用,垃圾回收器回收後這個實例也將不存在了,下次再使用時還需要初始化這個類纔可以再次調用。

1)存儲區域不同:靜態變量存儲在方法區屬於類所有,實例變量存儲在堆當中;

2)靜態變量與類相關,普通變量則與實例相關;

3)內存分配方式不同。

4)生命週期不同。

需要注意的是從JDK1.8開始用於實現方法區的PermSpace被MetaSpace取代。
Object 類都有哪些公共方法?

**1)equals(obj);**

判斷其他對象是否“等於”此對象。

**2)toString();**

表示返回對象的字符串。通常,ToString方法返回一個“以文本方式表示”此對象的字符串。結果應該是一個簡潔但信息豐富的表達,很容易讓人閱讀。建議所有子類都重寫此方法。

**3)getClass();
**
返回此對象運行時的類型。

**4)wait();**

表示當前線程進入等待狀態。

**5)finalize();**

用於釋放資源。

**6)notify();**

喚醒在該對象上等待的某個線程。

**7)notifyAll();**

喚醒在該對象上等待的所有線程。

**8)hashCode();**

返回對象的哈希代碼值。用於哈希查找,可以減少在查找中使用equals的次數,重寫equals方法一般都要重寫hashCode方法。這個方法在一些具有哈希功能的Collection中用到。

**9)clone();**

實現對象的淺複製,實現Cloneable接口才可以調用這個方法,否則拋出CloneNotSupportedException異常。
Java 創建對象有哪幾種方式?

|創建對象|構造方法說明|
|-|-|
|使用new關鍵字|調用構造方法|
|使用Class類的newInstance方法|調用構造方法|
|使用Constructor類的newInstance方法|調用構造方法|
|使用clone方法|沒有調用構造方法|
|使用反序列化|沒有調用構造方法|
a==b 與 a.equals(b) 有什麼區別?

a==b 與 a.equals(b) 有什麼區別?


假設a和b都是對象

a==b是比較兩個對象內存地址,當a和b指向的是堆中的同一個對象纔會返回true。

a.equals(b)是比較的兩個值內容,其比較結果取決於equals()具體實現。

多數情況下需要重寫這個方法,如String類重寫equals()用於比較兩個不同對象,但是包含的字母相同的比較:

```java
public boolean equals(Object obj) {


if (this == obj) {// 相同對象直接返回true

 

return true;


}


if (obj instanceof String) {

 

String anotherString = (String)obj;

 

int n = value.length;

 

if (n == anotherString.value.length) {

 


char v1[] = value;

 


char v2[] = anotherString.value;

 


int i = 0;

 


while (n-- != 0) {

 

 

if (v1[i] != v2[i])

 

 


return false;

 

 

i++;

 


}

 


return true;

 

}


}


return false;
}
```
Object 中 equals() 和 hashcode() 有什麼聯繫?

Java的基類Object提供了一些方法,其中equals()方法用於判斷兩個對象是否相等,hashCode()方法用於計算對象的哈希碼。equals()和hashCode()都不是final方法,都可以被重寫(overwrite)。

hashCode()方法是爲對象產生整型的hash值,用作對象的唯一標識。

hashCode()方法常用於基於hash的集合類,如Hashtable、HashMap等,根據Java規範使用equal()方法來判斷兩個相等的對象,必須具有相同的hashcode。

將對象放入到集合中時,首先要判斷放入對象的hashcode是否已經存在,不存在則直接放入集合。

如果hashcode相等,然後通過equal()方法判斷要放入對象與集合中的其他對象是否相等,使用equal()判斷不相等,則直接將該元素放入集合中,反之不放入集合中。
hashcode() 中可以使用隨機數字嗎?

hashcode()中不可以使用隨機數字不行,這是因爲對象的hashcode值必須是相同的。
Java 中 & 和 && 有什麼區別?

&是位操作

&&是邏輯運算符

邏輯運算符具有短路特性,而&不具備短路特性。

來看一下代碼執行結果:

```java
public class Test{

static String name;

public static void main(String[] args){
if(name!=null & name.equals("")){
System.out.println("ok");
}else{
System.out.println("error");
}
}
}
```

執行結果:

```shell
Exception in thread "main" java.lang.NullPointerException


at com.jingxuan.JingXuanApplication.main(JingXuanApplication.java:25)
```

上述代碼執行時拋出空指針異常,若果&替換成&&,則輸出日誌是error。
一個 .java 類文件中可以有多少個非內部類?

一個.java類文件中只能出現一個public公共類,但是可以有多個default修飾的類。如果存在兩個public修飾的類時,會報如下錯誤:

```java
The public type Test must be defined in its own file
```
Java 中如何正確退出多層嵌套循環?

1)使用lable標籤和break方式;

lable是跳出循環標籤。

break lable;是跳出循環語句。

當執行跳出循環語句時會跳出循環標籤下方循環的末尾後面。

```java
lable:
for(int i=0;i<3;i++){


for(int j=0;j<3;j++){

 

System.out.println(i);

 

if(i == 2) {

 


break lable;

 

}

 

 


}
}
```
上述代碼在執行過程中,當i=2時,執行跳出循環語句,控制檯只輸出i=0和i=1的結果,執行繼續for循環後面的代碼。

```shell
0
0
0
1
1
1
2
執行for後面的程序代碼
```

2)通過在外層循環中添加標識符,比如定義布爾類型bo = false,當bo=true跳出循環語句。
淺拷貝和深拷貝有什麼區別?

淺拷貝是指被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺拷貝僅僅複製所考慮的對象,而不復制它所引用的對象。

深拷貝是指被複制對象的所有變量都含有與原來的對象相同的值,而那些引用其他對象的變量將指向被複制過的新對象,並且不再是原有的那些被引用的對象。換言之,深拷貝把要複製的對象所引用的對象都複製了一遍。
Java 中 final關鍵字有哪些用法?

Java代碼中被final修飾的類不可以被繼承。

Java代碼中被final修飾的方法不可以被重寫。

Java代碼中被final修飾的變量不可以被改變,如果修飾引用類型,那麼表示引用類型不可變,引用類型指向的內容可變。

Java代碼中被final修飾的方法,JVM會嘗試將其內聯,以提高運行效率。

Java代碼中被final修飾的常量,在編譯階段會存入常量池中。
String s = new String("abc"); 創建了幾個String對象?

String s = new String("abc"); 創建了2個String對象。

一個是字符串字面常數,在字符串常量池中。

一個是new出來的字符串對象,在堆中。
String 和 StringBuffer 有什麼區別?

String是不可變對象,每次對String類型進行操作都等同於產生了一個新的String對象,然後指向新的String對象。因此儘量避免對String進行大量拼接操作,否則會產生很多臨時對象,導致GC開始工作,影響系統性能。

StringBuffer是對象本身操作,而不是產生新的對象。因此在有大量拼接的情況下,建議使用StringBuffer。

String是線程不安全的,而StringBuffer是線程安全的。

需要注意是Java從JDK5開始,在編譯期間進行了優化。如果是無變量的字符串拼接時,那麼在編譯期間值都已經確定了的話,javac工具會直接把它編譯成一個字符常量。比如:

```java
String str = "關注微信公衆號" + "“Java精選”" + ",面試經驗、專業規劃、技術文章等各類精品Java文章分享!";
```

在編譯期間會直接被編譯成如下:

```java
String str = "關注微信公衆號“Java精選”,面試經驗、專業規劃、技術文章等各類精品Java文章分享!";
```
Java 中 3*0.1 == 0.3 返回值是什麼?

3*0.1==0.3返回值是false

這是由於在計算機中浮點數的表示是誤差的。所以一般情況下不進行兩個浮點數是否相同的比較。而是比較兩個浮點數的差點絕對值,是否小於一個很小的正數。如果條件滿足,就認爲這兩個浮點數是相同的。

```java
System.out.println(3*0.1 == 0.3);
System.out.println(3*0.1);

System.out.println(4*0.1==0.4);
System.out.println(4*0.1);
```
執行結果如下:
```shell
false
0.30000000000000004
true
0.4
```

分析:3\*0.1的結果是浮點型,值是0.30000000000000004,但是4\*0.1結果值是0.4。這個是二進制浮點數算法的計算原因。

a=a+b 和 a+=b 有什麼區別嗎?

+=操作符會進行隱式自動類型轉換,a+=b隱式的將相加操作結果類型強制轉換爲持有結果的類型,而a=a+b則不會自動進行類型轉換。

```java
byte a = 127;
byte b = 127;
a = a + b;
a += b;
```
a = a + b; 編譯報錯:
```java
Type mismatch: cannot convert from int to byte
```
Java 中線程阻塞都有哪些原因?

阻塞指的是暫停一個線程的執行以等待某個條件發生(如某資源就緒),Java提供了大量方法來支持阻塞。

|方法名|方法說明
|-|-|
|sleep()|sleep()方法允許指定以毫秒爲單位的一段時間作爲參數,它使得線程在指定的時間內進入阻塞狀態,不能得到CPU時間,指定的時間一過,線程重新進入可執行狀態。典型地,sleep()被用在等待某個資源就緒的情形:測試發現條件不滿足後,讓線程阻塞一段時間後重新測試,直到條件滿足爲止|
|suspend()和resume()|兩個方法配套使用,suspend()使得線程進入阻塞狀態,並且不會自動恢復,必須其對應的resume()被調用,才能使得線程重新進入可執行狀態。典型地,suspend()和resume()被用在等待另一個線程產生的結果的情形:測試發現結果還沒有產生後,讓線程阻塞,另一個線程產生了結果後,調用resume()使其恢復。|
|yield()|yield()使當前線程放棄當前已經分得的CPU時間,但不使當前線程阻塞,即線程仍處於可執行狀態,隨時可能再次分得CPU時間。調用yield()的效果等價於調度程序認爲該線程已執行了足夠的時間從而轉到另一個線程|
|wait()和notify()|兩個方法配套使用,wait()使得線程進入阻塞狀態,它有兩種形式,一種允許指定以毫秒爲單位的一段時間作爲參數,另一種沒有參數,前者當對應的notify()被調用或者超出指定時間時線程重新進入可執行狀態,後者則必須對應的notify()被調用。|

![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20210526161612514.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FmcmVvbg==,size_16,color_FFFFFF,t_70)

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