「乾貨」Java後端開發高頻面試題

JVM

說一下垃圾回收機制?

Java 語言中一個顯著的特點就是引入了垃圾回收機制,在編寫程式的時候不再需要考慮記憶體管理。垃圾回收機制可以有效的防止記憶體洩露,提高記憶體的記憶體率。

垃圾回收器通常是作為一個單獨的低階執行緒執行,不可預知的情況下對堆中已經死亡的或者長時間沒有使用的物件進行清理和回收。

回收機制的演算法有:標記清除演算法、複製演算法、標記壓縮演算法等等。

描述一下垃圾回收的流程?

首先有三個代,新生代、老年代、永久代。

在新生代有三個區域:一個Eden區和兩個Survivor區。當一個例項被建立了,首先會被儲存Eden 區中。

具體過程是這樣的:

一個物件例項化時,先去看Eden區有沒有足夠的空間。

如果有,不進行垃圾回收,物件直接在Eden區儲存。

如果Eden區記憶體已滿,會進行一次minor gc。

然後再進行判斷Eden區中的記憶體是否足夠。

如果不足,則去看存活區的記憶體是否足夠。

如果記憶體足夠,把Eden區部分活躍物件儲存在存活區,然後把物件儲存在Eden區。

如果記憶體不足,查詢老年代的記憶體是否足夠。

如果老年代記憶體足夠,將部分存活區的活躍物件存入老年代。然後把Eden區的活躍物件放入存活區,物件依舊儲存在Eden區。

如果老年代記憶體不足,會進行一次full gc,之後老年代會再進行判斷 記憶體是否足夠,如果足夠 還是那些步驟。

如果不足,會丟擲OutOfMemoryError(記憶體溢位異常)。

解釋一下JVM的記憶體模型?

Java記憶體模型決定一個執行緒對共享變數的寫入何時對另一個執行緒可見。從抽象的角度來看,定義了執行緒和主記憶體之間的抽象關係。

執行緒之間的共享變數儲存在主記憶體中,每個執行緒都有一個私有的本地記憶體(並不真實存在),本地記憶體中儲存的是在主記憶體中共享變數的副本。

有兩條規定:

執行緒對共享變數的所有操作都必須在自己的工作記憶體中進行,不能直接從主記憶體中讀寫。

執行緒的工作記憶體是私有的,其他執行緒無法訪問,執行緒變數值的傳遞透過主記憶體來完成。

GC回收的是堆記憶體還是棧記憶體?

主要管理的是堆記憶體。

什麼時候新生代會轉換為老年代?

Eden區滿時,進行Minor GC時。

物件體積太大, 新生代無法容納時。

虛擬機器對每個物件定義了一個物件年齡(Age)計數器。當年齡增加到一定的臨界值時,就會晉升到老年代中。

如果在Survivor區中相同年齡的物件的所有大小之和超過Survivor空間的一半,包括比這個年齡大的物件就都可以直接進入老年代。

建立佔大記憶體的物件分配到哪一代?

如果新建立的物件佔用記憶體很大,則直接分配到老年代

新生代2個Survivor區的好處?

解決了記憶體碎片化問題。整個過程中,永遠有一個Survivor區是空的,另一個非空的Survivor區是無碎片的。

遇到過OOM怎麼解決?

我們可以修改虛擬機器的引數,獲取Heap Dump的檔案,字尾名是。hprof。

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\jvm

之後可以使用JDK自帶的一個工具jvisualvm來進行排查和定位。

Java基礎

int和Integer的區別?

int是基本資料型別,Integer是它的包裝類。

Integer儲存的是物件的引用,int儲存的變數值。

Integer預設是null,int預設是0。

Integer變數必須例項化後才能使用,而int變數不需要。

Java的基本資料型別和大小?

單位:位元組boolean(1) = byte(1) < short(2) = char(2) < int(4) = float(4) < long(8) = double(8)

Java類衝突怎麼解決(jar包衝突)?

使用

mvn: dependency tree

檢視衝突的jar

然後在pom檔案裡邊 使用

exclusion

標籤排除掉這些衝突的jar包

介面和抽象類的區別?

介面中所有的方法隱含的都是抽象的。而抽象類則可以同時包含抽象和非抽象的方法。

類可以實現很多個介面,但是隻能繼承一個抽象類。

Java介面中宣告的變數預設都是final的。抽象類可以包含非final的變數。

Java介面中的成員函式預設是public的。抽象類的成員函式可以是private,protected或者是public。

多執行緒

多執行緒的建立方式

繼承Thread類

實現Runnable介面

直接使用執行緒池

實現Runnable介面這種方式更受歡迎,已經繼承別的類的情況下只能實現介面。

Sleep(0)表示什麼?

觸發作業系統立刻重新進行一次CPU競爭,競爭的結果也許是當前執行緒仍然獲得CPU控制權,也許會換成別的執行緒獲得CPU控制權。

執行緒有三個狀態,就緒態,執行態,等待態。Sleep(n)方法是讓當前執行緒在n秒內不會參與CPU競爭。執行緒進入等待佇列,n秒之後再次進入就緒佇列。

Sleep(0)是讓執行緒直接進入就緒狀態。

Sleep與Wait區別?

sleep是執行緒類(Thread)的方法,導致此執行緒暫停執行指定時間,把執行機會給其他執行緒,但是監控狀態依然保持,到時後會自動恢復。呼叫sleep不會釋放物件鎖。

wait是Object類的方法,物件呼叫wait方法導致本執行緒放棄物件鎖,進入等待池,只有針對此物件發出notify方法(或notifyAll)後本執行緒才進入鎖池準備搶奪物件鎖。

Thread。Join方法

Thread。Join把指定的執行緒加入到當前執行緒,可以將兩個交替執行的執行緒合併為順序執行的執行緒。

比如線上程B中呼叫了執行緒A的Join()方法,直到執行緒A執行完畢後,才會繼續執行執行緒B。

Lock鎖與synchronized的區別?

Lock能完成synchronized的所有功能。

Lock有比synchronized更精確的執行緒語義和更好的效能。synchronized會自動釋放鎖,而Lock一定要求程式設計師手工釋放,並且必須在finally從句中釋放。

Lock可以知道是不是已經獲取到鎖,而synchronized無法知道。

執行緒不安全的問題在多執行緒怎麼解決?

可以使用synchronized、lock、volatile來實現同步。

談談你對volatile的理解?

volatile是輕量級的synchronized,比它的執行成本更低,因為它不會引起執行緒的上下文切換,它保證了共享變數的

可見性

可見性

的意思是當一個執行緒修改一個變數時,另外一個執行緒能讀到這個修改的值。如果一個欄位被宣告成volatile,java執行緒記憶體模型確保所有執行緒看到這個變數的值是一致的。還有就是它透過新增記憶體屏障的方式禁止指令的重排序。

如何實現主執行緒等待子執行緒執行完後再繼續執行?

我們可以使用join方法,在主執行緒內部呼叫子執行緒。join方法。

CountDownLatch實現

這是一個屬於JUC的工具類,從1。5開始。主要用到方法是countDown() 和 await()。

await()方法阻塞當前執行緒,直到計數器等於0。

countDown()方法將計數器減一。

思路:我們可以在建立CountDownLatch物件,然後將此物件透過構造引數傳遞給子執行緒,在開啟子執行緒後主執行緒呼叫await()方法阻塞主執行緒,子執行緒呼叫countDown()方法計數器減一。

簡單說一下synchronize

synchronize是java中的關鍵字,可以用來修飾例項方法、靜態方法、還有程式碼塊;主要有三種作用:可以確保原子性、可見性、有序性。

原子性就是能夠保證同一時刻有且只有一個執行緒在操作共享資料,其他執行緒必須等該執行緒處理完資料後才能進行。

可見性就是當一個執行緒在修改共享資料時,其他執行緒能夠看到。

有序性就是,被synchronize鎖住後的執行緒相當於單執行緒,在單執行緒環境jvm的重排序是不會改變程式執行結果的,可以防止重排序對多執行緒的影響。

synchronried的底層原理

synchronized的底層原理是跟monitor有關,也就是檢視器鎖,每個物件都有一個關聯的monitor,當Synchronize獲得monitor物件的所有權後會進行兩個指令:加鎖指令跟減鎖指令。

monitor裡面有個計數器,初始值是從0開始的。如果一個執行緒想要獲取monitor的所有權,就看看它的計數器是不是0,如果是0的話,那麼就說明沒人獲取鎖,那麼它就可以獲取鎖了,然後將計數器+1,也就是執行monitorenter加鎖指令;monitorexit減鎖指令是跟在程式執行結束和異常裡的,如果不是0的話,就會陷入一個堵塞等待的過程,直到為0等待結束。

有幾種執行緒池?並且詳細描述一下執行緒池的實現過程

newFixedThreadPool建立一個指定大小的執行緒池。每當提交一個任務就建立一個執行緒,如果工作執行緒數量達到執行緒池初始的最大數,則將提交的任務存入到等待佇列中。

newCachedThreadPool建立一個可快取的執行緒池。這種型別的執行緒池特點是:

工作執行緒的建立數量幾乎沒有限制(其實也有限制的,數目為Interger。 MAX_VALUE), 這樣可靈活的往執行緒池中新增執行緒。

如果長時間沒有往執行緒池中提交任務,即如果工作執行緒空閒了指定的時間(預設為1分鐘),則該工作執行緒將自動終止。終止後,如果你又提交了新的任務,則執行緒池重新建立一個工作執行緒。

newSingleThreadExecutor建立一個單執行緒的Executor,即只建立唯一的工作者執行緒來執行任務,如果這個執行緒異常結束,會有另一個取代它,保證順序執行(我覺得這點是它的特色)。

newScheduleThreadPool建立一個定長的執行緒池,而且支援定時的以及週期性的任務執行,類似於Timer。(這種執行緒池原理暫還沒完全瞭解透徹)

執行緒池的主要引數和處理流程

主要引數有:

執行緒池核心執行緒數大小

最大執行緒數

儲存的佇列

拒絕策略

空閒執行緒存活時長

當需要任務大於核心執行緒數時候,就開始把任務往儲存任務的佇列裡,當儲存佇列滿了的話,就開始增加執行緒池建立的執行緒數量,如果當執行緒數量也達到了最大,就開始執行拒絕策略,比如說記錄日誌,直接丟棄,或者丟棄最老的任務,或者交給提交任務的執行緒執行。

當一個執行緒完成時,它會從佇列中取下一個任務來執行。當一個執行緒無事可做,且超過一定的時間(keepAliveTime)時,如果當前執行的執行緒數大於核心執行緒數,那麼這個執行緒會停掉了。

集合

List,Set,Map的區別?

List

是一個有序的集合,裡面可以儲存重複的元素。

Set

是一個不能儲存相同元素的集合。

Map

是一個透過鍵值對的方式儲存元素的,鍵不能重複。

HashMap具體如何實現的?

HashMap底層是基於陣列+連結串列實現的,透過新增鍵的hashcode與上陣列的長度來得到這個元素在陣列中的位置,如果這個位置沒有資料,那麼就把這個資料當做第一個節點。如果這個位置有了連結串列,那麼在JDK1。7的時候使用的是頭插法,在JDK1。8的時候使用尾插法。

HashMap在JDK1。8的版本中引入了紅黑樹結構做最佳化,當連結串列元素個數大於等於8時,連結串列轉換成樹結構;連結串列元素個數小於等於6時,樹結構還原成連結串列。

JDK1。8的時候為什麼選擇8和6作為轉換點?

因為紅黑樹的平均查詢長度是log(n),長度為8的時候,平均查詢長度為3,如果繼續使用連結串列,平均查詢長度為8/2=4,顯然樹的效率更高一些。

連結串列長度如果是小於等於6,6/2=3,雖然速度也很快的,但是樹和連結串列相互轉換的時間也不會太短。還有選擇6和8,中間有個差值7可以有效防止連結串列和樹頻繁轉換。

HashMap的擴容機制?

HashMap底層是陣列,在第一次put的時候會初始化,發生第一次擴容到16。它有一個負載因子是0。75,下一次擴容的時候就是當前陣列大小*0。75。擴大容量為原來的2倍。

concurrentmap為什麼是執行緒安全的?

ConcurrentHashMap大部分的邏輯程式碼和HashMap是一樣的,主要透過synchronized和來保證節點在插入擴容的時候是執行緒安全的。

ConcurrentHashMap的擴容核心邏輯主要是給不同的執行緒分配不同的陣列下標,然後每個執行緒處理各自下表區間的節點。同時處理節點複用了hashMap的邏輯,透過位執行,可以知道節點擴容後的位置,要麼在原位置,要麼在原位置+oldlength位置,最後直接賦值即可。

ConcurrentHashMap的原理?

ConcurrentHashMap的資料結構是由一個Segment陣列和多個HashEntry組成的。HashEntry封裝的就是每一個鍵值對。,每一個Segment元素儲存的是HashEntry陣列 + 連結串列。Segment陣列的意義就是將一個大的table分割成多個小的table來進行加鎖,Segment本身可以充當鎖的角色。ConcurrentHashMap在put的時候需要進行兩次hash,第一次需要確定在Segment陣列的位置,第二次hash是確定在HashEntry陣列中的位置。同樣在get的時候也需要經過兩次hash。

框架

Springmvc的執行流程

客戶端的所有請求都交給前端控制器DispatcherServlet來處理,它會負責呼叫系統的其他模組來真正處理使用者的請求。

DispatcherServlet收到請求後,將根據請求的資訊(包括URL、HTTP協議方法、請求頭、請求引數、Cookie等)以及HandlerMapping的配置找到處理該請求的Handler(任何一個物件都可以作為請求的Handler)。

在這個地方Spring會透過HandlerAdapter對該處理器進行封裝。

HandlerAdapter是一個介面卡,它用統一的介面對各種Handler中的方法進行呼叫。

Handler完成對使用者請求的處理後,會返回一個ModelAndView物件給DispatcherServlet,ModelAndView顧名思義,包含了資料模型以及相應的檢視的資訊。

ModelAndView的檢視是邏輯檢視,DispatcherServlet還要藉助ViewResolver完成從邏輯檢視到真實檢視物件的解析工作。

當得到真正的檢視物件後,DispatcherServlet會利用檢視物件對模型資料進行渲染。

客戶端得到響應,可能是一個普通的HTML頁面,也可以是XML或JSON字串,還可以是一張圖片或者一個PDF檔案。

SpringMVC的常用註解

@Component

: 會被spring容器識別,並轉為bean。

@Repository

: 對Dao實現類進行註解。

@Service

: 對業務邏輯層進行註解。

@Controller

: 表明這個類是Spring MVC裡的Controller,將其宣告為Spring的一個Bean,Dispatch Servlet會自動掃描註解了此註解的類,並將Web請求對映到註解了@RequestMapping的方法上。

@RequestMapping

: 用來對映Web請求(訪問路徑和引數)、處理類和方法的。它可以註解在類和方法上。註解在方法上的@RequestMapping路徑會繼承註解在類上的路徑。

@RequestBody

: 可以將整個返回結果以某種格式返回,如json或xml格式。

@PathVariable

: 用來接收路徑引數,如/news/001,可接收001作為引數,此註解放置在引數前。

@RequestParam

:用於獲取傳入引數的值。

@RestController

:是一個組合註解,組合了@Controller和@ResponseBody,意味著當只開發一個和頁面互動資料的控制的時候,需要使用此註解。

Spring MVC怎麼將資料儲存到session中?

我一般都是使用Servlet-Api,在處理請求的方法引數列表中,新增一個HTTPSession物件,之後SpringMVC就可以自動注入進來了。在方法體中呼叫session。setAttribute就可以了。

過濾器和攔截器的區別?

攔截器是基於java的反射機制的,而過濾器是基於函式回撥。

攔截器不依賴servlet容器,過濾器依賴servlet容器。

攔截器只能對action請求起作用,而過濾器則可以對幾乎所有的請求起作用。

攔截器可以訪問action上下文、值棧裡的物件,而過濾器不能訪問。

在action的生命週期中,攔截器可以多次被呼叫,而過濾器只能在容器初始化時被呼叫一次。

攔截器可以獲取IOC容器中的各個bean,而過濾器就不行,這點很重要,在攔截器裡注入一個service,可以呼叫業務邏輯。

請簡要說明一下IOC和AOP是什麼?

依賴注入的三種方式:

介面注入

Construct注入

Setter注入

控制反轉與依賴注入是同一個概念,引入IOC的目的:

脫開、降低類之間的耦合

倡導面向介面程式設計、實施依賴倒換原則

提高系統可插入、可測試、可修改等特性。

具體做法:

將bean之間的依賴關係儘可能地轉換為關聯關係

將對具體類的關聯儘可能地轉換為對Java interface的關聯,而不是與具體的服務物件相關聯

Bean例項具體關聯相關Java interface的哪個實現類的例項,在配置資訊的元資料中描述

由IoC元件(或稱容器)根據配置資訊,例項化具體bean類、將bean之間的依賴關係注入進來。

AOP是面向切面程式設計,可以說是面向物件程式設計的補充和完善。OOP引入封裝、繼承、多型等概念來建立一種物件層次結構,但是這些都是縱向的關係,但並不適合定義橫向的關係,例如日誌功能。日誌程式碼往往橫向地散佈在所有物件層次中,但是這與核心的業務程式碼確沒有關係。

AOP利用“橫切”的技術,把那些與業務無關,但是卻為業務模組所共同呼叫的邏輯部分封裝起來,便於減少系統的重複程式碼,降低模組之間的耦合度,並提高了系統的維護性。

AOP把軟體系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處基本相似,比如日誌還有事物。AOP的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。

Spring攔截器的執行順序

Springmvc的攔截器實現HandlerInterceptor介面後,會有三個抽象方法需要實現,分別為方法前執行preHandle,方法後postHandle,頁面渲染後afterCompletion。

當兩個攔截器都實現放行操作時,順序為preHandle 1,preHandle 2,postHandle 2,postHandle 1,afterCompletion 2,afterCompletion 1

當第一個攔截器preHandle返回false,也就是對其進行攔截時,第二個攔截器是完全不執行的,第一個攔截器只執行preHandle部分。

當第一個攔截器preHandle返回true,第二個攔截器preHandle返回false,順序為preHandle 1,preHandle 2 ,afterCompletion 1

總結:

preHandle 按攔截器定義順序呼叫postHandler 按攔截器定義逆序呼叫afterCompletion 按攔截器定義逆序呼叫postHandler 在攔截器鏈內所有攔截器返成功呼叫afterCompletion 只有preHandle返回true才呼叫

Spring 核心功能

spring 框架中核心元件有三個:Core、Context 和 Beans。其中最核心的元件就是Beans, Spring提供的最核心的功能就是Bean Factory。

Spring 解決了的最核心的問題就是把物件之間的依賴關係轉為用配置檔案來管理,也就是Spring的依賴注入機制。這個注入機制是在Ioc 容器中進行管理的。

@Autowired 和@Resource區別是什麼?

共同點:兩者都可以寫在欄位和setter方法上。兩者如果都寫在欄位上,那麼就不需要再寫setter方法。

@Autowired註解是按照型別(byType)裝配依賴物件。當有且僅有一個匹配的Bean時,Spring將其注入@Autowired標註的變數中。如果我們想使用按照名稱(byName)來裝配,可以結合@Qualifier註解一起使用。

@Resource預設按照ByName自動注入。@Resource有兩個重要的屬性:name和type,而Spring將@Resource註解的name屬性解析為bean的名字,而type屬性則解析為bean的型別。所以,如果使用name屬性,則使用byName的自動注入策略,而使用type屬性時則使用byType自動注入策略。如果既不制定name也不制定type屬性,這時將透過反射機制使用byName自動注入策略。

SpringBootApplication是怎麼載入的?

SpringBoot配置多套環境

可以定義多個配置檔案,比如開發,測試,上線。 我們可以在SpringBoot中定義多個application。properties。 我一般都用-名字做區別,比如:

application-dev。propertiesapplication-test。propertiesapplication-prod。properties

之後我們需要在預設的配置檔案裡面宣告一下啟用哪些配置檔案。

spring。profiles。active=test

使用java -jar 方式啟動的時候也可以新增引數指定配置檔案啟動

java -jar mm。jar ——spring。profiles。active=dev

SpringBoot的啟動方式

執行帶有main方法類。

透過命令列 java -jar 的方式。

透過spring-boot-plugin的方式,這種方式需要安裝Maven的外掛,然後透過命令

mvn spring-boot:run

啟動專案。

SpringBoot的啟動方式

前臺啟動命令:

java -jar XXX。jar

後臺啟動命令:

java -jar xxx。jar &

,後臺啟動同時也可以制定控制檯的輸出標準,將日誌輸出到制定檔案

使用Docker的方式啟動SpringBoot應用,需要將jar製作成docker映象。

MyBatis核心類

Mybatis中dao層和xml配置怎麼建立關係的

Mybatis常用標籤

#{ } 和${ }的區別?

#{}

:這種方式是使用的預編譯的方式,一個#{}就是一個佔位符。相當於jdbc的佔位符PrepareStatement。設定值的時候會加上引號。

${}

:這種方式是直接拼接的方式,不對數值做預編譯。存在sql注入的現象。設定值的時候不會加上引號。

MyBatis的二級快取

MyBatis一級快取最大的共享範圍就是一個SqlSession內部,那麼如果多個SqlSession需要共享快取,則需要開啟二級快取,開啟二級快取後,會使用CachingExecutor裝飾Executor進入一級快取的查詢流程前,先在CachingExecutor進行二級快取的查詢。

當二級快取開啟後,同一個名稱空間(namespace) 所有的操作語句,都影響著一個共同的 cache,也就是二級快取被多個 SqlSession 共享,是一個全域性的變數。當開啟快取後,資料的查詢執行的流程就是 二級快取 -> 一級快取 -> 資料庫。預設二級快取不開啟,需要在MyBatis的全域性配置檔案中進行配置。

MySQL

Mysql的儲存引擎

MySQL 支援多種型別的資料庫引擎,可分別根據各個引擎的功能和特性為不同的資料庫處理任務提供各自不同的適應性和靈活性。在 MySQL 中,可以利用 SHOW ENGINES 語句來顯示可用的資料庫引擎和預設引擎。

MySQL 提供了多個不同的儲存引擎,包括處理事務安全表的引擎和處理非事務安全表的引擎。在 MySQL 中,不需要在整個伺服器中使用同一種儲存引擎,針對具體的要求,可以對每一個表使用不同的儲存引擎。

MySQL 5。7 支援的儲存引擎有 InnoDB、MyISAM、Memory、Merge、Archive、Federated、CSV、BLACKHOLE 等。

InnoDB和MyISAM的區別

InnoDB支援事務,MyISAM不支援

InnoDB支援行級鎖而MyISAM僅僅支援表鎖。但是InnoDB可能出現死鎖。

InnoDB的關注點在於:併發寫、事務、更大資源。而MyISAM的關注點在於:節省資源、消耗少、簡單業務

InnoDB比MyISAM更安全,但是MyISAM的效率要比InnoDB高

在MySQL5。7的時候,預設就是InnoDb作為預設的儲存引擎了

Sql執行計劃

使用EXPLAIN關鍵字可以模擬最佳化器執行SQL查詢語句,從而知道MySQL是如何處理你的SQL語句的。分析查詢語句或是表結構的效能瓶頸。

Explain + SQL語句

透過Explain,我們可以獲取以下資訊:

表的讀取順序

哪些索引可以使用

資料讀取操作的操作型別

哪些索引被實際使用

表之間的引用

每張表有多少行被物理查詢

EXPLAIN SELECT * FROM USER;

「乾貨」Java後端開發高頻面試題

顯示的結果一般不會全部去關注,比較關注的有:

id

是查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序。

id相同,執行順序由上至下。

如果是子查詢,id的序號會遞增。id越大優先順序越高,越先被執行。

id如果相同,可以認為是一組,從上往下順序執行。在所有組中,id值越大,優先順序越高,越先執行。

id號每個號碼,表示一趟獨立的查詢。一個sql的查詢趟數越少越好。

第二個是

type

,顯示的是訪問型別。如果是

All

就代表是全表掃描。需要進行最佳化。一般來說,得保證查詢至少達到range級別,最好能達到ref。

然後是

possible_keys

:sql所用到的索引

還有一個就是

key

,這個就是實際使用的索引。如果為NULL,則沒有使用索引。

然後

key_len

表示索引中使用的位元組數,可透過該列計算查詢中使用的索引的長度。key_len欄位能夠幫你檢查where條件是否充分的利用上了索引。key_len越長,查詢效率越高。

rows

列顯示MySQL認為它執行查詢時必須檢查的行數。行數越少,效率越高!

資料庫最佳化

選取最適用的欄位屬性

使用連線查詢代替子查詢

為合適的欄位建立索引

在上線後資料庫要增加幾個欄位,你們是怎麼做的

可以使用alter新增列,這樣原有的資料不會改變,新增的欄位值是null。 還可以使用Navicat或者SQLyog這些視覺化工具修改表的結構,效果和上面的一樣

資料庫索引的最佳化建議以及有哪些注意點

使用複合索引的效果會大於使用單個欄位索引(但是要注意順序)

查詢條件時要按照索引中的定義順序進行匹配。如果索引了多列,要遵守最左字首法則。指的是查詢從索引的最左前列開始並且不跳過索引中的列。

不在索引列上做任何操作(計算、函式、(自動or手動)型別轉換),會導致索引失效而轉向全表掃描

儲存引擎不能使用索引中範圍條件右邊的列,範圍查詢的列在定義索引的時候,應該放在最後面。

mysql 在使用不等於(!= 或者<>)的時候無法使用索引會導致全表掃描

is not null 也無法使用索引,但是is null是可以使用索引的

like以萬用字元開頭(‘%abc。。。’)mysql索引失效會變成全表掃描的操作

字串不加單引號索引失效(型別轉換導致索引失效)

MySQL鎖

MyISAM支援表鎖,InnoDB支援表鎖和行鎖,預設行鎖。

表級鎖:開鎖小,加鎖快,不會出現死鎖。鎖的粒度大,發生鎖衝突的機率最高。併發量最低。

行級鎖:開銷大,加鎖慢,會出現死鎖,鎖的粒度小,容易發生衝突的機率小,併發度最高

併發事務處理帶來的問題

更新丟失:多個事務對同一行資料進行更新,最後提交的更新會覆蓋其他事務的更新。

髒讀:事務A讀取到事務B已經修改但未提交的資料,還在這個資料基礎上做了修改。此時,如果事務B回滾了,事務A的資料無效,不符合一致性要求。

不可重讀:事務A讀取到了事務B已經提交的修改資料,不符合隔離性。

幻讀:事務A讀取了事務B提交的新增資料,不符合隔離性。

事務的隔離級別

「乾貨」Java後端開發高頻面試題

MySQL的事務隔離級別

Redis

Redis雪崩,穿透產生原因及怎麼解決

一般的快取系統,都是按照 key 去快取查詢,如果不存在對應的 value,就應該去後端資料庫查。一些惡意的請求會故意查詢不存在的 key,請求量很大,就會對後端系統造成很大的壓力。 解決穿透的一種辦法是對介面做校驗,然後也可以對查詢結果為空的情況也進行快取,快取時間設定短一點,或者該 key 對應的資料 insert 了之後清理快取。

快取雪崩就是當快取伺服器重啟或者大量快取集中在某一個時間段失效,這樣在失效的時候,會給後端系統帶來很大壓力。導致系統崩潰。我們可以做二級快取,A1為原始快取,A2為複製快取,A1失效時,可以訪問A2,A1快取失效時間設定為短期,A2設定為長期。或者我們對不同的key,設定不同的過期時間,讓快取失效的時間點儘量均勻。

Redis的資料型別

String字串:字串型別是 Redis 最基礎的資料結構,首先鍵都是字串型別,而且 其他幾種資料結構都是在字串型別基礎上構建的,我們常使用的 set key value 命令就是字串。常用在快取、計數、共享Session、限速等。

Hash雜湊:在Redis中,雜湊型別是指鍵值本身又是一個鍵值對結構,雜湊可以用來存放使用者資訊,比如實現購物車。

List列表(雙向連結串列):列表(list)型別是用來儲存多個有序的字串。可以做簡單的訊息佇列的功能。

Set集合:集合(set)型別也是用來儲存多個的字串元素,但和列表型別不一 樣的是,集合中不允許有重複元素,並且集合中的元素是無序的,不能透過索引下標獲取元素。利用 Set 的交集、並集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。

Sorted Set有序集合(跳錶實現):Sorted Set 多了一個權重引數 Score,集合中的元素能夠按 Score 進行排列。可以做排行榜應用,取 TOP N 操作。

Redis的持久化

Redis為了保證效率,資料快取在了記憶體中,但是會週期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案中,以保證資料的持久化。Redis的持久化策略有兩種:1。 RDB:快照形式是直接把記憶體中的資料儲存到一個dump的檔案中,定時儲存,儲存策略。當Redis需要做持久化時,Redis會fork一個子程序,子程序將資料寫到磁碟上一個臨時RDB檔案中。 當子程序完成寫臨時檔案後,將原來的RDB替換掉。1。 AOF:把所有的對Redis的伺服器進行修改的命令都存到一個檔案裡,命令的集合。使用AOF做持久化,每一個寫命令都透過write函式追加到appendonly。aof中。aof的預設策略是每秒鐘fsync一次,在這種配置下,就算髮生故障停機,也最多丟失一秒鐘的資料。 缺點是對於相同的資料集來說,AOF的檔案體積通常要大於RDB檔案的體積。根據所使用的fsync策略,AOF的速度可能會慢於RDB。Redis預設是快照RDB的持久化方式。對於主從同步來說,主從剛剛連線的時候,進行全量同步(RDB);全同步結束後,進行增量同步(AOF)。

redis是單執行緒,但為什麼快

純記憶體操作

單執行緒操作,避免了頻繁的上下文切換

合理高效的資料結構

採用了非阻塞I/O多路複用機制(有一個檔案描述符同時監聽多個檔案描述符是否有資料到來)

redis存的資料過期了,資料會立即刪除嗎

不會,其實有三種不同的刪除策略:

立即刪除。在設定鍵的過期時間時,建立一個定時器,當過期時間達到時,立即執行刪除操作。

惰性刪除。key過期的時候不刪除,每次從資料庫獲取key的時候去檢查是否過期,若過期,則刪除,返回null。

定時刪除。每隔一段時間,對全部的鍵進行檢查,刪除裡面的過期鍵。

Redis 和 Mysql 資料庫資料如何保持一致性

先更新資料庫,再刪快取。資料庫的讀操作的速度遠快於寫操作的,所以髒資料很難出現。可以對非同步延時刪除策略,保證讀請求完成以後,再進行刪除操作。

Redis的應用場景

快取

共享Session

訊息佇列系統

分散式鎖

redis裡的hash型別怎麼模糊查詢

可以使用Java連線Redis,獲得指定hash的所有值,然後做正則驗證。

Redis常用命令

key    keys * 獲取所有的key    select 0 選擇第一個庫    move myString 1 將當前的資料庫key移動到某個資料庫,目標庫有,則不能移動    flush db      清除指定庫    randomkey     隨機key    type key      型別        set key1 value1 設定key    get key1    獲取key    mset key1 value1 key2 value2 key3 value3    mget key1 key2 key3    del key1   刪除key    exists key      判斷是否存在key    expire key 10   10過期    pexpire key 1000 毫秒    persist key     刪除過期時間string    set name cxx    get name    getrange name 0 -1        字串分段    getset name new_cxx       設定值,返回舊值    mset key1 key2            批次設定    mget key1 key2            批次獲取    setnx key value           不存在就插入(not exists)    setex key time value      過期時間(expire)    setrange key index value  從index開始替換value    incr age        遞增    incrby age 10   遞增    decr age        遞減    decrby age 10   遞減    incrbyfloat     增減浮點數    append          追加    strlen          長度    getbit/setbit/bitcount/bitop    位操作    hash    hset myhash name cxx    hget myhash name    hmset myhash name cxx age 25 note “i am notes”    hmget myhash name age note       hgetall myhash               獲取所有的    hexists myhash name          是否存在    hsetnx myhash score 100      設定不存在的    hincrby myhash id 1          遞增    hdel myhash name             刪除    hkeys myhash                 只取key    hvals myhash                 只取value    hlen myhash                  長度list    lpush mylist a b c  左插入    rpush mylist x y z  右插入    lrange mylist 0 -1  資料集合    lpop mylist  彈出元素    rpop mylist  彈出元素    llen mylist  長度    lrem mylist count value  刪除    lindex mylist 2          指定索引的值    lset mylist 2 n          索引設值    ltrim mylist 0 4         刪除key    linsert mylist before a  插入    linsert mylist after a   插入    rpoplpush list list2     轉移列表的資料    set    sadd myset redis     smembers myset       資料集合    srem myset set1         刪除    sismember myset set1 判斷元素是否在集合中    scard key_name       個數    sdiff | sinter | sunion 操作:集合間運算:差集 | 交集 | 並集    srandmember          隨機獲取集合中的元素    spop                 從集合中彈出一個元素    zset    zadd zset 1 one    zadd zset 2 two    zadd zset 3 three    zincrby zset 1 one              增長分數    zscore zset two                 獲取分數    zrange zset 0 -1 withscores     範圍值    zrangebyscore zset 10 25 withscores 指定範圍的值    zrangebyscore zset 10 25 withscores limit 1 2 分頁    Zrevrangebyscore zset 10 25 withscores  指定範圍的值    zcard zset  元素數量    Zcount zset 獲得指定分數範圍內的元素個數    Zrem zset one two        刪除一個或多個元素    Zremrangebyrank zset 0 1  按照排名範圍刪除元素    Zremrangebyscore zset 0 1 按照分數範圍刪除元素    Zrank zset 0 -1    分數最小的元素排名為0    Zrevrank zset 0 -1  分數最大的元素排名為0    Zinterstore    zunionstore rank:last_week 7 rank:20150323 rank:20150324 rank:20150325  weights 1 1 1 1 1 1 1        排序:    sort mylist  排序    sort mylist alpha desc limit 0 2 字母排序    sort list by it:* desc           by命令    sort list by it:* desc get it:*  get引數    sort list by it:* desc get it:* store sorc:result  sort命令之store引數:表示把sort查詢的結果集儲存起來

RabbitMq

怎麼防止重複消費

可能因為各種原因,導致了生產端傳送了多條一樣的訊息給消費端,但是,消費端也只能消費一條,不會多消費。可以使用

唯一ID + 指紋碼機制

防止訊息被重複消費。

指紋碼(就是時間戳 + 業務的一些規則, 來保證id + 指紋碼在同一時刻是唯一的,不會出現重複)。

唯一ID + 指紋碼機制,利用資料庫主鍵去重

select count(1) from t_order where id = 唯一ID + 指紋碼

如果不存在,則正常消費,消費完畢後將【唯一ID + 指紋碼】 寫入資料庫

如果存在,則證明訊息被消費過,直接丟棄。

Rabbitmq怎麼防止訊息丟失

將通道設定成confirm模式(傳送方確認模式),則所有在通道上釋出的訊息都會被指派一個唯一的ID。 一旦訊息被投遞到目的佇列後,或者訊息被寫入磁碟後(可持久化的訊息),通道會發送一個確認給生產者(包含訊息唯一ID)。

為什麼選擇使用MQ來實現同步

透過使用訊息佇列,我們可以非同步處理請求,從而緩解系統的壓力。同樣可以達到解耦的效果。

ElasticSearch

高亮你們是怎麼做的

SpringBoot整合ElasticSearch有一個searchSourceBuilder,透過鏈式呼叫一個highlighter方法,傳入一個HighlightBuilder物件並設定好查詢的列和高亮的標籤。

之後呼叫RestHighLevelClient物件的Search方法之後返回一個SearchResponse物件,之後可以呼叫response。getHits()。getHits();獲得擊中的結果陣列,陣列中每一個物件除了包含原始內容還包含了一個高亮結果集,是一個Map集合。

SpringBoot怎麼整合ElasticSearch

首先需要匯入

spring-boot-starter-data-elasticsearch

,在Spring官網的data專案裡面有詳細的文件介紹,官方強烈建議使用 High Level REST Client來操作ES。之後需要新增一個配置類,在官方文件有介紹。之後我們就可以透過Spring容器來管理獲取HighLevelRESTClient物件了。

其他

Maven的宣告週期?

Maven有三套生命週期,分別是clean、default、site,每個生命週期都包含了一些階段(phase)。三套生命週期相互獨立,但各個生命週期中的phase卻是有順序的,且後面的phase依賴於前面的phase。執行某個phase時,其前面的phase會依順序執行,但不會觸發另外兩套生命週期中的任何phase。

clean的生命週期:

pre-clean:執行清理前的工作;clean:清理上一次構建生成的所有檔案;post-clean:執行清理後的工作

default的生命週期:default生命週期是最核心的,它包含了構建專案時真正需要執行的所有步驟。

validateinitializegenerate-sourcesprocess-sourcesgenerate-resourcesprocess-resources    :複製和處理資原始檔到target目錄,準備打包;compile    :編譯專案的原始碼;process-classesgenerate-test-sourcesprocess-test-sourcesgenerate-test-resourcesprocess-test-resourcestest-compile    :編譯測試原始碼;process-test-classestest    :執行測試程式碼;prepare-packagepackage    :打包成jar或者war或者其他格式的分發包;pre-integration-testintegration-testpost-integration-testverifyinstall    :將打好的包安裝到本地倉庫,供其他專案使用;deploy    :將打好的包安裝到遠端倉庫,供其他專案使用;

site的生命週期:

pre-sitesite    :生成專案的站點文件;post-sitesite-deploy    :釋出生成的站點文件

cookie和session區別

cookie資料存放在客戶的瀏覽器上,session資料放在伺服器上。

cookie不是很安全,別人可以分析存放在本地的cookie並進行cookie欺騙,考慮到安全應當使用session。

session會在一定時間內儲存在伺服器上。當訪問增多,會比較佔用你伺服器的效能,考慮到減輕伺服器效能方面,應當使用cookie。

單個cookie儲存的資料不能超過4K,很多瀏覽器都限制一個站點最多儲存20個cookie。

可以考慮將登陸資訊等重要資訊存放為session,其他資訊如果需要保留,可以放在cookie中。

TCP三次握手,4次揮手的過程描述一下

握手過程:

主機A向主機B傳送請求連線資料報,其中包括A的序列號

seq=x

,請求連線的標誌位

SYN=1

主機B收到請求之後返回確認連線資料報,其中包括B的序列號

seq=y

,請求連線的標誌位

SYN=1

ACK=1

還有一個確認號,

ack=x+1

主機A收到了B的確認報文後再次做出確認,再發送一個數據報,其中包括:

ACK=1

seq=x+1

ack=y+1

至於為什麼要傳送第三條是因為在傳送第一條的時候,可能因為網路原因導致資料報滯留,那麼超過一定時間主機A會再次傳送請求連線的資料報文。之後主機B返回確認連線報文,如果主機A收到確認報文之後不傳送第三條報文告訴主機B自己已經收到了,那麼B其實是不知道的,這時候可能A第一次傳送的原本滯留的報文突然正常了,B就再次收到了請求連線的報文,但是實際上A已經連線了。

揮手過程:

客戶端向伺服器傳送一個請求斷開連線的資料報,終止位

FIN=1

,序列號

seq=u

伺服器收到請求後返回

ACK=1

seq=v

ack=u+1

。之後客戶端通往伺服器的單向連線就斷開了。

之後伺服器也需要和客戶端斷開連線,也是傳送了一個FIN

客戶端收到FIN後返回ACK,並將確認號設定為收到的序號+1

其實在客戶端斷開和伺服器的單向連線之後,伺服器仍然可以往客戶端傳送資料,需要處理一下事情。

客戶端需要最後等一段時間才能進入關閉狀態是因為:客戶端無法保證最後傳送的ACK報文會一定被對方收到,所以有時候需要重發可能丟失的ACK報文。