必收藏的Java面試題
目錄
Java 面試題
一. 容器部分
二. 多線程部分
三. SpringMvc部分
四. Mybatis部分
五. MySQL部分
六. Redis部分
七. RabbitMQ部分
八. JVM虛擬機部分
九. 算法知識部分
十. 其他面試部分
更新
容器部分面試題
Java 容器都有哪些
Collection
的子類List
、Set
List
的子類ArrayList
、LinkedList
等Set
的子類HashSet
、TreeSet
等Map
的子類HashMap
、TreeMao
等
Collecion 和 Collections 有什么區別
java.util,Collection
是一個集合的頂級接口,它提供了對集合對象進行基本操作的通用接口方法,Collection 接口的意義是為各種具體的集合提供了最大化的統一操作方式,其直接繼承接口的有 List 和 Setjava.util.Collections
是一個包裝類(工具類),它包含了各種有關集合操作的靜態多態方法。此類不能被實例化,用於對集合中元素進行排序、搜索以及線程安全等各種操作,服務於 Java 的 Collection 框架
List、Set、Map 之間的區別
- List、Set 都繼承 Collection 接口,而 Map 則不是
- List 是一個有序集合,元素可重復,可有多個NULL值。可以使用各種循環遍歷集合,因為它是有序的
- Set 是一個無序集合,元素不可重復,重復元素會被覆蓋掉(注意:元素雖然無放入順序,但元素在 Set 中的位置是由該元素的 HashCode 決定的,其位置是固定的,加入 Set 的 Object 必須定義
equals()
方法),Set 只能用迭代,因為它是無序的- Map 是由一系列鍵值對組成的集合,提供了 key 和 value 的映射。在 Map 中保證 key 與 value 一一對應的關系。一個 key 對應一個 value ,不能存在相同的 key,但 value 可以相同
- Set 與 List 相比較
- Set 檢索元素效率較低,刪除和插入效率高,因為刪除和插入不會引起元素的位置變化
- List 可動態增長,查找元素效率高,但是刪除和插入效率低,因為刪除或插入一條元素,會引起其他元素位置變化
- Map 適合存儲鍵值對的數據
HashMap 和 HashTable 的區別
- 繼承的父類不同,但二者都實現了 Map接口
- HashTable 繼承自
Dictionary
類- HashMap 繼承自
AbstractMap
類
- 線程安全不同
- HashMap 在缺省的情況下是非Synchronize的
- HashTable 的方法是Synchronize的
- 在多線程下直接使用 HashTable 不需要自己為它的方法實現同步。但使用 HashMap 時需要手動增加同步處理
Map m = Collections.synchronizeMap(hashMap);
- 是否提供 contains() 方法
- HashMap 把 HashTable 的 contains() 方法去掉了,改成了 containsValue() 和 containsKey(),因為 contains 容易讓人誤解
- HashTable 則保留了 contains()、containsValue()、containsKey() 三個方法,其中 contains() 與 containsValue() 功能相同
- key 和 value 是否可以為 null 值
- HashTable 中,key 和 value 都不能為 null 值
- HashMap 中,null 作為鍵,但這樣的鍵只有一個。可以有多個 value 為 null 值的鍵。當 get() 方式返回 null 有可能是 HashMap 中沒有該鍵,也有可能返回的 value 為 null。所以 HashMap 用
containsKey()
方法判斷是否存在鍵
- 遍歷方式不同
- HashTable 與 HashMap 都是使用 Iterator 迭代器遍歷,而由於歷史的原因,HashTable 還使用了
Enumeration
的方式
- hash 值不同
- 哈希值的使用不同,HashTable直接使用對象的HashCode,而 HashMap 重新計算哈希值
- hashCode是jdk根據對象的地址或者字符串或者數字算出來的int類型的數值
- HashTable 使用的取模運算
- HashMap 使用的與運算,先用
hash & 0x7FFFFFFF
后,再對 length 取模,&0x7FFFFFFF
的目的是為了將負的 hash 值轉化為正值,因為 hash 值有可能為負數,而&0x7FFFFFFF
后,只有符號外改變,而后面的位都不變
- 內部實現使用的數組初始化和擴容方式不同
- HashTable 在不指定容量的情況下默認是11,而 HashMap 為16,HashTable 不要求底層數組的容量一定要是2的整數次冪,而 HashMap 底層數組則一定為2的整數次冪
- HashTable 擴容時,將容量變成原來的2倍+1 (old * 2 + 1),而 HashMap 則直接改為原來的2倍 (old * 2)
如何決定使用 HashMap 還是 TreeMap
- 如果需要得到一個有序的 Map 集合就應該使用 TreeMap (因為 HashMap 的排序順序不是固定的)除此之外,由於 HashMap 有比 TreeMap 更好的性能,在不需要使用排序的情況下使用 HashMap 會更好
HashMap 的實現原理
- 利用key的hashCode重新hash計算出當前對象的元素在數組中的下標
存儲時,如果出現hash值相同的key,此時有兩種情況。(1)如果key相同,則覆蓋原始值;(2)如果key不同(出現沖突),則將當前的key-value放入鏈表中
獲取時,直接找到hash值對應的下標,在進一步判斷key是否相同,從而找到對應值。
理解了以上過程就不難明白HashMap是如何解決hash沖突的問題,核心就是使用了數組的存儲方式,然后將沖突的key的對象放入鏈表中,一旦發現沖突就在鏈表中做進一步的對比。
HashSet 的實現原理
- HashSet 實際上是一個
HashMap
實例,都是一個存放鏈表的數組。它不保證存儲元素的迭代順序;此類允許使用 null 元素。HashSet 中不允許有重復元素,這是因為 HashSet 是基於 HashMap 實現的,HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是統一的一個固定對象private static final Object PRESENT = new Object();
- HashSet 中 add() 方法調用的是底層 HashMap 中的 put() 方法,而如果是在 HashMap 中調用 put() ,首先會判斷 key 是否存在,如果 key 存在則修改 value 值,如果 key 不存在這插入這個 key-value。而在 set 中,因為 value 值沒有用,也就不存在修改 value 值的說法,因此往 HashSet 中添加元素,首先判斷元素(也就是key)是否存在,如果不存在這插入,如果存在着不插入,這樣 HashSet 中就不存在重復值。所以判斷 key 是否存在就要重寫元素的類的 equals() 和 hashCode() 方法,當向 Set 中添加對象時,首先調用此對象所在類的 hashCode() 方法,計算次對象的哈希值,此哈希值決定了此對象在Set中存放的位置;若此位置沒有被存儲對象則直接存儲,若已有對象則通過對象所在類的 equals() 比較兩個對象是否相同,相同則不能被添加。
ArrayList 與 LinkList 的區別是什么
- AarrayList 是動態數組構成 LinkList 是鏈表組成
- AarrayList 常用於經常查詢的集合,因為 LinkList 是線性存儲方式,需要移動指針從前往后查找
- LinkList 常用於新增和刪除的集合,因為 ArrayList 是數組構成,刪除某個值會對下標影響,需要進行數據的移動
- AarrayList 自由度較低,需要手動設置固定的大小,但是它的操作比較方便的,①直接創建②添加對象③根據下標進行使用
- LinkList 自由度較高,能夠動態的隨數組的數據量而變化
- ArrayList 主要開銷在List需要預留一定空間
- LinkList 主要開銷在需要存儲結點信息以及結點指針信息
如何實現數組與 List 之間的轉換
- List to Array : 可以使用 List 的
toArray()
方法,傳入一個數組的類型例如Stirng[] strs = strList.toArray(new String[strList.size()]);
- Array to List : 可以使用
java.util.Arrays
的asList()
方法 例如List<String> strList = Arrays.asList(strs);
ArrayList 與 Vector 的區別是什么
- ArrayList 是非線程安全的,而 Vector 使用了
Synchronized
來實現線程同步的- ArrayList 在性能方面要優於 Vector
- ArrayList 和 Vector 都會根據實際情況來動態擴容的,不同的是 ArrayList 擴容到原大小的1.5倍,而 Vector 擴容到原大小的2倍
Array 與 ArrayList 有什么區別
- Array 是數組,當定義數組時,必須指定數據類型及數組長度
- ArrayList 是動態數組,長度可以動態改變,會自動擴容,不使用泛型的時候,可以添加不同類型元素
在 Queue 中 poll() 與 remove() 有什么區別
- poll() 和 remove() 都是從隊列頭刪除一個元素,如果隊列元素為空,remove() 方法會拋出
NoSuchElementException
異常,而 poll() 方法只會返回 null
哪些集合類是線程安全的
- Vector :比 ArrayList 多了同步化機制(線程安全)
- HashTable :比 HashMap 多了線程安全
- ConcurrentHashMap :是一種高效但是線程安全的集合
- Stack :棧,繼承於 Vector 也是線程安全
迭代器 Iterator 是什么
Iterator
是集合專用的遍歷方式Iterator<E> iterator()
: 返回此集合中元素的迭代器,通過集合的iterator()
方法得到,所以Iterator
是依賴於集合而存在的
Iterator 怎么使用 ? 有什么特點
Iterator 的使用方法
java.lang.Iterable
接口被java.util.Collection
接口繼承,java.util.Collection
接口的iterator()
方法返回一個Iterator
對象next()
方法獲取集合中下一個元素hasNext()
方法檢查集合中是否還有元素remove()
方法將迭代器新返回的元素刪除
Iterator 的特點
- Iterator 遍歷集合過程中不允許線程對集合元素進行修改
- Iterator 遍歷集合過程中可以用
remove()
方法來移除元素,移除的元素是上一次Iterator.next()
返回的元素- Iterator 的
next()
方法是通過游標指向的形式返回Iterator下一個元素
Iterator 與 LinkIterator 有什么區別
- 使用范圍不同
- Iterator 適用於所有集合, Set、List、Map以及這些集合的子類型,而 ListIterator 只適用於 List 及其子類型
- ListIterator 有 add() 方法,可以向 List 中添加元素,而 Iterator 不能
- ListIterator 和 Iterator 都有 hasNext() 和 next() 方法,來實現順序向后遍歷。而 ListIterator 有 hasPrevious() 和 previous() 方法,可以實現逆向遍歷,但是 Iterator 不能
- ListIterator 可以使用 nextIdnex() 和 previousIndex() 方法定位到當前索引位置,而 Iterator 不能
- 它們都可以實現 remove() 刪除操作,但是 ListIterator 可以使用 set() 方法實現對象修改,而 Iterator 不能
怎么確保一個集合不能被修改
- 可以采用
java.util.Collections
工具類Collections.unmodifiableMap(map)
Collections.unmodifiableList(list)
Collections.unmodifiableSet(set)
- 如諾修改則會報錯
java.lang.UnsupportedOperationException
多線程部分面試題
並發和並行有什么區別
- 並發:不同的代碼塊交替執行
- 並行:不同的代碼塊同時執行
- 個人理解
- 並發就是放下手頭的任務A去執行另外一個任務B,執行完任務B后,再回來執行任務A,就比如說吃飯時來電話了,去接電話,打完電話后又回來吃飯
- 並行就是執行A的同時,接受到任務B,然后我一起執行,就比如說吃飯時來電話了,一邊吃飯一邊打電話
線程和進程的區別
- 根本區別 :進程是操作系統資源分配的基本單位,而線程是任務調度和執行的基本單位
- 在操作系統中能同時運行多個進程,進程中會執行多個線程
- 線程是操作系統能夠進行運算調度的最小單位
守護線程是什么
- JVM內部的實現是如果運行的程序只剩下守護線程的話,程序將終止運行,直接結束。所以守護線程是作為輔助線程存在的
創建線程有哪幾種方式
- 繼承Thread類創建線程類
- 定義
Thread
類的子類,並重寫該類的run()
方法- 創建
Thread
子類的實例,即創建了線程對象- 調用線程對象的
start()
方法來啟動該線程
- 實現Runnable接口創建線程類
- 創建
runnable
接口的實現類,並重寫該接口的run()
方法- 創建
Runnable
實現類的實例,並依此實例作為Thread的target
來創建Thread
對象,該Thread
對象才是真正的線程對象- 調用線程對象的
start()
方法來啟動該線程
- 通過 Callable 和 Future 創建線程
- 創建
Callable
接口的實現類,並重寫call()
方法,該call()
方法將作為線程執行體,並且有返回值- 創建
Callable
實現類的實例,使用FutureTask
類來包裝Callable
對象,該FutureTask
對象封裝了該Callable
對象的call()
方法的返回值- 使用
FutureTask
對象作為Thread
對象的target
創建並啟動新線程- 調用
FutureTask
對象的get()
方法來獲得子線程執行結束后的返回值
runnable 和 callable 有什么區別
- 相同點
- 都是接口
- 都可以編寫多線程程序
- 都是采用
Thread.start()
啟動線程
- 不同點
Runnable
沒有返回值,Callable
可以返回執行結果,是個泛型和Future
、FutureTask
配合可以用來獲取異步執行的結果Callable
接口的call()
方法允許拋出異常,Runnable
的run()
方法異常只能在內部消化,不能往上繼續拋注意
Callalble
接口支持返回執行結果,需要調用FutureTask.get()
得到,此方法會阻塞主進程的繼續往下執行,如果不調用不會阻塞
線程有哪些狀態
- 新建 就緒 運行 阻塞 死亡
sleep() 和 wait() 的區別
- 最大區別是sleep() 休眠時不釋放同步對象鎖,其他線程無法訪問 而wait()休眠時,釋放同步對象鎖其他線程可以訪問
- sleep() 執行完后會自動恢復運行不需要其他線程喚起.而 wait() 執行后會放棄對象的使用權,其他線程可以訪問到,需要其他線程調用 Monitor.Pulse() 或者 Monitor.PulseAll() 來喚醒或者通知等待隊列
線程的 run() 和 start() 的區別
- start() 是來啟動線程的
- run() 只是 Thread 類中一個普通方法,還是在主線程中執行
notify() 和 notifyAll() 有什么區別
notify()
方法隨機喚醒對象的等待池中的一個線程,進入鎖池notifyAll()
喚醒對象的等待池中的所有線程,進入鎖池。
創建線程池有哪幾種方式
- 利用
Executors
創建線程池
newCachedThreadPool()
,它是用來處理大量短時間工作任務的線程池newFixedThreadPool(int nThreads)
,重用指定數目nThreads
的線程newSingleThreadExecutor()
,它的特點在於工作線程數目限制為1newSingleThreadScheduledExecutor()
和newScheduledThreadPool(int corePoolSize)
,創建的是個ScheduledExecutorService
,可以進行定時或周期性的工作調度,區別在於單一工作線程還是多個工作線程。newWorkStealingPool(int parallelism)
,這是一個經常被人忽略的線程池,Java 8
才加入這個創建方法,其內部會構建ForkJoinPool
,利用Work-Stealing
算法,並行地處理任務,不保證處理順序
線程池都有哪些狀態
- 運行 RUNNING:線程池一旦被創建,就處於
RUNNING
狀態,任務數為 0,能夠接收新任務,對已排隊的任務進行處理- 關閉 SHUTDOWN:不接收新任務,但能處理已排隊的任務。調用線程池的
shutdown()
方法,線程池由RUNNING
轉變為SHUTDOWN
狀態- 停止 STOP:不接收新任務,不處理已排隊的任務,並且會中斷正在處理的任務。調用線程池的
shutdownNow()
方法,線程池由(RUNNING
或SHUTDOWN
) 轉變為STOP
狀態- 整理 TIDYING:
SHUTDOWN
狀態下,任務數為 0, 其他所有任務已終止,線程池會變為 TIDYING 狀態,會執行terminated()
方法。線程池中的terminated()
方法是空實現,可以重寫該方法進行相應的處理- 線程池在
SHUTDOWN
狀態,任務隊列為空且執行中任務為空,線程池就會由 SHUTDOWN 轉變為TIDYING
狀態- 線程池在
STOP
狀態,線程池中執行中任務為空時,就會由STOP
轉變為TIDYING
狀態
- 終止 TERMINATED:線程池徹底終止。線程池在 TIDYING 狀態執行完 terminated() 方法就會由 TIDYING 轉變為
TERMINATED
狀態
線程池中的 submit() 和 execute() 有什么區別
- 兩個方法都可以向線程池提交任務
execute()
方法的返回類型是void
,它定義在Executor
接口中- 而
submit()
方法可以返回持有計算結果的Future
對象,它定義在ExecutorService
接口中
在Java程序中怎么確保多線程運行安全
- 使用synchronied關鍵字
- 使用volatile 關鍵字,防止指令重排,所有對該變量讀寫都是直接操作共享內存
- lock鎖機制
lock()
與unlock()
- 使用線程安全的類 比如
StringBuffer
、HashTable
、Vector
等線程安全問題主要是:原子性,可見性,有序性
synchronized 和 volatile 的作用什么?有什么區別
- 作用
- synchronized 表示只有一個線程可以獲取作用對象的鎖,執行代碼,阻塞其他線程
- volatile 表示變量在
CPU
的寄存器中是不確定的,必須從主存中讀取。保證多線程環境下變量的可見性,禁止指令重排序
- 區別
synchronized
可以作用於變量、方法、對象。volatile
只能作用於變量synchronized
可以保證線程間的有序性、原子性和可見性。volatile
只保證了可見性和有序性,無法保證原子性synchronized
線程阻塞,volatile
線程不阻塞
synchronized 和 Lock 有什么區別
synchronized
是一個Java
關鍵字,在jvm
層面上,而Lock
是一個類synchronized
以獲取鎖的線程執行完同步代碼,釋放鎖,如果線程中發生異常,jvm
會讓線程釋放鎖。而Lock
必須在finally
中釋放鎖,否則容易造成線程死鎖Lock
可以查看鎖的狀態,而synchronized
不能- 在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時
Lock
的性能要遠遠優於synchronized
。所以說,在具體使用時要根據適當情況選擇synchronized
是非公平鎖,而Lock
是可公平鎖
Spring Mvc面試題部分
為什么要是使用 Spring
- 輕量
非侵入型框架
- 控制反轉
IOC
,促進了松耦合- 面向切面編程
AOP
- 容器 采用Java Bean 代替 沉重的
EJB
容器- 方便集成 可以和很多框架相互集成如
Mybatis
、Shiro
- 豐富的功能 Spring 已經寫好了很多常的模板如
JDBC抽象類
、事務管理
、JMS
、JMX
、Web Service
...等
解釋一下什么是 aop
AOP
是面向切面編程,是OOP
編程的有效補充AOP
包括Aspect
切面,Join Point
連接點,Advice
通知,Ponitcut
切點其中
Advice
通知包括5中模式
Before advice
:方法執行前‘增強’After returning advice
:方法執行后‘增強’After throwing advice
:方法執行中拋出異常時‘增強’After finally advice
:方法執行完后‘增強’Around advice
:環繞增強‘以上四種的整合’
解釋一下什么是 ioc
- IOC是控制反轉,是將代碼本身的控制權移到外部Spring容器中進行集中管理,也是為了達到松耦合
Spring 主要有哪些模塊
- Spring Core
- 框架基礎部分,提供了
IOC
容器,對Bean
進行集中管理
- Spring Context
- 基於
Bean
,提供上下文信息
- Spring Dao
- 提供了
JDBC
的抽象層,它可消除冗長的JDBC
編碼,提供了聲明性事務管理方法
- Spring ORM
- 提供了
對象/關系
映射常用的API
集成層,如Mybatis
、Hibernate
等
- Spring Aop
- 提供了
AOP
面向切面的編程實現
- Spring Web
- 提供了
Web
開發的信息上下文,可與其他的Web
集成開發
- Spring Web Mvc
- 提供了
Web
應用的Model - View - Controller
全功能的實現
Spring 常用的注入方式有哪些
- 構造方法注入
- Setter方法注入
不過值得一提的是:Setter注入時如果寫了帶參構造方法,那么無參構造方法必須也要寫上,否則注入失敗
- 基於注解得注入
基本上有五種常用注冊
Bean
的注解
@Component
:通用注解@Controller
:注冊控制層Bean@Service
:注冊服務層Bean@Repository
:注冊Dao層Bean@Configuration
:注冊配置類
Spring 中的 bean 是線程安全的嗎
- 線程不安全
- Spring容器中的Bean是否線程安全,容器本身並沒有提供Bean的線程安全策略,因此可以說Spring容器中的Bean本身不具備線程安全的特性
Spring 支持幾種 bean 的作用域
singleton
:單例,默認的作用域prototype
:原型,每次都會創建新對象request
:請求,每次Http請求都會創建一個新對象session
:會話,同一個會話創建一個實例,不同會話使用不同實例global-session
:全局會話,所有會話共享一個實例
- 后面三種只有在Web應用中使用Spring才有效
Spring 自動裝配 bean 有哪些方式
default
:默認方式和‘no’效果一樣no
:不自動配置,需要使用 節點或參數byName
:根據名稱進行裝配byType
:根據類型進行裝配constructor
:根據構造函數進行裝配
Spring 事務實現方式有哪些
- 編程式事務管理對基於
POJO
的應用來說是唯一選擇,我們需要在代碼中調用beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是編程式事務管理- 基於 TransactionProxyFactoryBean的聲明式事務管理
- 基於 @Transactional 的聲明式事務管理
- 基於Aspectj AOP配置事務
Spring 的事務隔離是什么
Spring Mvc 的運行流程
- 用戶發送一個請求至前端控制器
DispatcherServlet
DispatcherServlet
收到請求調用處理器映射器HandlerMapping
- 處理器映射器根據請求
url
找到具體的處理器,生成處理器執行鏈HandlerExecutionChain
(包括處理器對象和處理器攔截器)一並返回給DispatcherServlet
DispatcherServlet
根據處理器Handler獲取處理器適配器HandlerAdapter
執行HandlerAdapter
處理一系列的操作- 執行處理器
Handler
(Controller
,也叫頁面控制器)Handler
執行完成返回ModelAndView
到HandlerAdapter
HandlerAdapter
將Handler
執行結果ModelAndView
返回到DispatcherServlet
DispatcherServlet``將ModelAndView
傳給ViewReslover
視圖解析器ViewReslover
解析后返回具體View
DispatcherServlet
對View
進行渲染視圖(即將模型數據model
填充至視圖中)DispatcherServlet
響應用戶
Spring Mvc 有哪些組件
DispatcherServlet
:前端控制器HandlerMapping
:處理器映射器HandlerAdapter
:處理器適配器HandlerInterceptor
:攔截器ViewResolver
:視圖解析器MultipartResolver
:文件上傳處理器HandlerExceptionResolver
:異常處理器DataBinder
:數據轉換HttpMessageConverter
:消息轉換器FlashMapManager
:頁面跳轉參數管理器HandlerExecutionChain
:處理程序執行鏈RequestToViewNameTranslator
:請求轉視圖翻譯器ThemeResolver
:LocaleResolver
:語言環境處理器
@RequestMapping 的作用是什么
@RequestMapping
是一個注解,用來標識http
請求地址與Controller
類的方法之間的映射- 指定 http 請求的類型使用
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
@Autowired 與 @Resource 的區別
@Autowired
為Spring
提供的注解,需要導入包org.springframework.beans.factory.annotation.Autowired
,采取的策略為按照類型注入@Resource
注解由J2EE提供,需要導入包javax.annotation.Resource
,默認按照ByName
自動注入
Mybatis部分面試題
Mybatis 中 #{} 和 ${} 的區別
- #{} 表示一個占位符
可以有效的防止SQL注入
- ${} 表示拼接SQL串
可以用於動態判斷字段
order by ${field} desc
可以動態修改fieId來達到動態根據字段降序查詢
Mybatis 有幾種分頁方式
- 原始分割
取出數據后,進行手動分割
- LIMIT關鍵字
修改執行SQL語句
- RowBounds實現分頁
將
PageInfo
信息封裝成RowBounds
,調用DAO層方法
- Mybatis的Interceptor實現
原理就是執行SQL語句前,在原SQL語句后加上limit關鍵字,不用自己去手動添加
RowBounds 是一次性查詢全部結果嗎?為什么
RowBounds
表面是在“所有”數據中檢索數據,其實並非是一次性查詢出所有數據,因為 MyBatis 是對 jdbc 的封裝,在 jdbc 驅動中有一個 Fetch Size 的配置,它規定了每次最多從數據庫查詢多少條數據,假如你要查詢更多數據,它會在你執行 next()的時候,去查詢更多的數據。就好比你去自動取款機取 10000 元,但取款機每次最多能取 2500 元,所以你要取 4 次才能把錢取完。只是對於 jdbc 來說,當你調用 next()的時候會自動幫你完成查詢工作。這樣做的好處可以有效的防止內存溢出。
Mybatis 邏輯分頁和物理分頁的區別是什么
- 邏輯分頁是一次性查詢很多數據,然后再在結果中檢索分頁的數據。這樣做弊端是需要消耗大量的內存、有內存溢出的風險、對數據庫壓力較大。
- 物理分頁是從數據庫查詢指定條數的數據,彌補了一次性全部查出的所有數據的種種缺點,比如需要大量的內存,對數據庫查詢壓力較大等問題
Myabtis 是否支持延遲加載,延遲加載的原理是什么
- MyBatis 支持延遲加載,設置
lazyLoadingEnabled=true
即可- 延遲加載的原理的是調用的時候觸發加載,而不是在初始化的時候就加載信息。比如調用
a.getB().getName()
,這個時候發現a.getB()
的值為null
,此時會單獨觸發事先保存好的關聯 B 對象的 SQL,先查詢出來 B,然后再調用a.setB(b)
,而這時候再調用a.getB(). getName()
就有值了,這就是延遲加載的基本原理
Mybatis 一級緩存和二級緩存
- 一級緩存
- 基於
PerpetualCache
的HashMap
本地緩存,它的聲明周期是和SQLSession
一致的,有多個SQLSession
或者分布式的環境中數據庫操作,可能會出現臟數據- 當
Session flush
或close
之后,該Sessio
中的所有Cache
就將清空,默認一級緩存是開啟的
- 二級緩存
- 也是基於
PerpetualCache
的HashMap
本地緩存,不同在於其存儲作用域為Mapper
級別的,如果多個SQLSession
之間需要共享緩存,則需要使用到二級緩存- 二級緩存可自定義存儲源,如 Ehcache。默認不打開二級緩存,要開啟二級緩存,使用二級緩存屬性類需要實現
Serializable
序列化接口(可用來保存對象的狀態)
- 擴展
- 開啟二級緩存后的查詢流程:
二級緩存 -> 一級緩存 -> 數據庫
- 緩存更新機制:當某一個作用域(一級緩存 Session/二級緩存 Mapper)進行了C/U/D 操作后,默認該作用域下所有 select 中的緩存將被 clear
Mybatis 和 Hibernate 有哪些區別
- Mybatis 更加靈活,可以自己寫SQL語句
- 也正是自己寫了很多SQL語句,所以移植性比較差
- Mybatis 入門比較簡單,使用門檻也低
- hibernate 可以自行把二級緩存更換為第三方的
Mybatis 有哪些執行器
SimpleExecutor
:每執行一次UPDATE\SELECT
就開啟一個Statement
對象,用完后立即關閉ReuseExecutor
:執行 UPDATE\SELECT,以 SQL 作為 key 查找
Statement對象,存在就使用,不存在就創建,用完后不關閉
Statement對象,而是放置於 Map 內供下一次使用。簡言之,就是重復使用
Statement`對象BatchExecutor
:執行UPDATE
(沒有 select,jdbc 批處理不支持 select),將所有 SQL 都添加到批處理中addBatch()
,等待統一執行executeBatch()
,它緩存了多個Statement
對象,每個Statement
對象都是addBatch()
完畢后,等待逐一執行executeBatch()
批處理,與 jdbc 批處理相同
Mybatis 分頁插件的實現原理是什么
- 分頁插件的基本原理是使用 MyBatis 提供的插件接口,實現自定義插件,在插件的攔截方法內攔截待執行的 SQL,然后重寫 SQL,根據 dialect 方言,添加對應的物理分頁語句和物理分頁參數
Mybatis 如何編寫一個自定義插件
MyBatis 自定義插件針對 MyBatis 四大對象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)進行攔截
- Executor:攔截內部執行器,它負責調用 StatementHandler 操作數據庫,並把結果集通過 ResultSetHandler 進行自動映射,另外它還處理了二級緩存的操作
. StatementHandler:攔截 SQL 語法構建的處理,它是 MyBatis 直接和數據庫執行 SQL 腳本的對象,另外它也實現了 MyBatis 的一級緩存- ParameterHandler:攔截參數的處理
- ResultSetHandler:攔截結果集的處理
MyBatis 插件要實現 Interceptor 接口
- setProperties 方法是在 MyBatis 進行配置插件的時候可以配置自定義相關屬性,即:接口實現對象的參數配置
- plugin 方法是插件用於封裝目標對象的,通過該方法我們可以返回目標對象本身,也可以返回一個它的代理,可以決定是否要進行攔截進而決定要返回一個什么樣的目標對象,官方提供了示例:return Plugin. wrap(target, this)
- intercept 方法就是要進行攔截的時候要執行的方法
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
官方插件實現:
@Intercepts({@Signature(type = Executor. class, method = "query",
args = {MappedStatement. class, Object. class, RowBounds. class, ResultHandler. class})})
public class TestInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation. getTarget(); //被代理對象
Method method = invocation. getMethod(); //代理方法
Object[] args = invocation. getArgs(); //方法參數
// do something . . . . . . 方法攔截前執行代碼塊
Object result = invocation. proceed();
// do something . . . . . . . 方法攔截后執行代碼塊
return result;
}
public Object plugin(Object target) {
return Plugin. wrap(target, this);
}
}
具體案例請看 Mybatis 實現自定義插件 通俗易懂
MySQL部分面試題
數據庫的三范式是什么
- 第一范式:又稱1NF,它指的bai是在一個應du用中的數據都可以組織zhi成由行和列的表格形式,且表格的任意一dao個行列交叉點即單元格,都不可再划分為行和列的形式,實際上任意一張表格都滿足1NF
- 第二范式:又稱2NF,它指的是在滿足1NF的基礎上,一張數據表中的任何非主鍵字段都全部依賴於主鍵字段,沒有任何非主鍵字段只依賴於主鍵字段的一部分。即,可以由主鍵字段來唯一的確定一條記錄。比如學號+課程號的聯合主鍵,可以唯一的確定某個成績是哪個學員的哪門課的成績,缺少學號或者缺少課程號,都不能確定成績的意義
- 第三范式:又稱3NF,它是指在滿足2NF的基礎上,數據表的任何非主鍵字段之間都不產生函數依賴,即非主鍵字段之間沒有依賴關系,全部只依賴於主鍵字段。例如將學員姓名和所屬班級名稱放在同一張表中是不科學的,因為學員依賴於班級,可將學員信息和班級信息單獨存放,以滿足3NF
如何獲取當前數據庫的版本
- 進入MySQL輸入
select version();
- 進入cmd輸入
mysql -V
說一下 ACID 是什么
- ACID,是指在可靠數據庫管理系統(DBMS)中,事務(transaction)所應該具有的四個特性:
原子性(Atomicity)
、一致性(Consistency)
、隔離性(Isolation)
、持久性(Durability)
- 原子性:意味着數據庫中的事務執行是作為原子。即不可再分,整個語句要么執行,要么不執行
- 一致性:即在事務開始之前和事務結束以后,數據庫的完整性約束沒有被破壞
- 隔離性:事務的執行是互不干擾的,一個事務不可能看到其他事務運行時,中間某一時刻的數據
- 持久性,意味着在事務完成以后,該事務所對數據庫所作的更改便持久的保存在數據庫之中,並不會被回滾
char 和 varchar 的區別
- 長度不同
- char 長度設置后不可變
- varchar 長度可動態改變
- 效率不同
- char 類型每次修改的數據長度相同,效率更高
- varchar 類型每次修改的數據長度不同,效率更低
- 存儲不同
- char 類型存儲的時候是初始預計字符串再加上一個記錄字符串長度的字節,占用空間較大
- varchar 類型存儲的時候是實際字符串再加上一個記錄字符串長度的字節,占用空間較小
float 和 double 的區別
- 在數據庫中的所有計算都是使用雙精度完成的,使用float(單精度)會有誤差,出現意想不到的結果
- MySQL浮點型和定點型可以用類型名稱后加(M,D)來表示,M表示該值的總共長度,D表示小數點后面的長度,M和D又稱為精度和標度,如float(7,4)的可顯示為-999.9999,MySQL保存值時進行四舍五入,如果插入999.00009,則結果為999.0001。FLOAT和DOUBLE在不指定精度時,默認會按照實際的精度來顯示,而DECIMAL在不指定精度時,默認整數為10,小數為0
MySQL 內連接、左連接、右連接有什么區別
inner join ... on
內連接:只顯示兩表中有關聯的數據left join ... on
左連接:顯示左表所有數據,右表沒有對應的數據用NULL補齊,多了的數據刪除right join ... on
右連接:顯示右表所有數據,左表沒有對應的數據用NULL對齊,多了的數據刪除
MySQL 的索引是怎么實現的
- 由於B+Tree數據結構的優勢,目前mysql基本都采用B+Tree方式實現索引
- MySQL索引實現的數據結構:兩種存儲引擎都使用B+Tree(B-Tree的變種)作為索引結構
- MyISAM索引葉子節點存放的是數據的地址,主鍵索引與輔助索引除了值得唯一性在結構上完全一樣。InnoDB索引葉子節點存放的內容因索引類型不同而不同,主鍵索引葉子節點存放的是數據本身,輔助索引葉子節點上存放的是主鍵值
MySQL 索引設計原則
- 適合索引的列是出現在where子句中的列,或者連接子句中指定的列
- 基數較小的類,索引效果較差,沒有必要在此列建立索引
- 使用短索引,如果對長字符串列進行索引,應該指定一個前綴長度,這樣能夠節省大量索引空間
- 不要過度索引。索引需要額外的磁盤空間,並降低寫操作的性能。在修改表內容的時候,索引會進行更新甚至重構,索引列越多,這個時間就會越長。所以只保持需要的索引有利於查詢即可
怎么驗證 MySQL 的索引是否滿足需求
- 使用explain函數驗證索引是否有效
事務的隔離級別
- Read uncommitted (讀未提交):最低級別
- Read committed (讀已提交):讀已提交,可避免臟讀情況發生。
- Repeatable Read(可重復讀):確保事務可以多次從一個字段中讀取相同的值,在此事務持續期間,禁止其他事務對此字段的更新,可以避免臟讀和不可重復讀,仍會出現幻讀問題
- Serializable (串行化):最嚴格的事務隔離級別,要求所有事務被串行執行,不能並發執行,可避免臟讀、不可重復讀、幻讀情況的發生
MySQL 常用的引擎
InnoDB 和 Myisam 都是用 B+Tree 來存儲數據的
- InnoDB 支持事務,且支持四種隔離級別(讀未提交、讀已提交、可重復讀、串行化),默認的為可重復讀.
- Myisam 只支持表鎖,且不支持事務.Myisam 由於有單獨的索引文件,在讀取數據方面的性能很高.
MySQL 的行鎖、表鎖、頁鎖
- 行級鎖
是Mysql中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。行級鎖能大大減少數據庫操作的沖突。其加鎖粒度最小,但加鎖的開銷也最大。行級鎖分為共享鎖 和 排他鎖。
- 行級鎖的特點
開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,並發度也最高。
- 表級鎖
表級鎖是MySQL中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少,被大部分MySQL引擎支持。最常使用的MYISAM與INNODB都支持表級鎖定。表級鎖定分為表共享讀鎖(共享鎖)與表獨占寫鎖(排他鎖)
- 表級鎖的特點
開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發出鎖沖突的概率最高,並發度最低。
- 頁級鎖
頁級鎖是MySQL中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但沖突多,行級沖突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。BDB支持頁級鎖
- 頁級鎖的特點
開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,並發度一般
- 擴展
- MyISAM和MEMORY采用表級鎖(tabl-level locking)
- BDB采用頁面鎖(page-level locking)或表級鎖,默認為頁面鎖
- InnoDB支持行級鎖(row-level locking)和表級鎖,默認為行級鎖
樂觀鎖和悲觀鎖
- 樂觀鎖認為一般情況下數據不會造成沖突,所以在數據進行提交更新時才會對數據的沖突與否進行檢測。如果沒有沖突那就OK;如果出現沖突了,則返回錯誤信息並讓用戶決定如何去做.常見的做法有兩種:版本號控制及時間戳控制
- 悲觀鎖,正如其名,它指的是對數據被外界(包括當前系統的其它事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處於鎖定狀態。悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排它性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)常見做法:
select ... for update
悲觀鎖語法鎖住記錄 兩個事務同時修改的話,事務A先執行事務B就會被阻塞,事務A執行update完后,事務B就會看到事務A執行完后更新的結果
MySQL 問題排查都有哪些手段
- 使用 show processlist 命令查看當前所有連接信息
- 使用 explain 命令查詢 SQL 語句執行計划
- 開啟慢查詢日志,查看慢查詢的 SQL
如何做 MySQL 的性能優化
- 創建索引 盡量避免全盤掃描 首先考慮在 where 和 order by 涉及的列上創建索引
- 避免在索引上使用計算 注意就是IN關鍵字不走索引,它是走全盤掃描
- 使用預編譯防止 sql 注入
- 盡量將多條 SQL語句壓縮到一條 SQL 語句中
- 最最最好的就是少用 * , 應該寫成要查詢的字段名,盡量避免在 where 條件中判斷 null
- 盡量不用like 的前置百分比
- 對於連續的數值,能用 between 就不要用 in
- 在新建臨時表時,如果一次性插入數據量較大.可以用 select into 代替 create table
- 選擇正確的存儲引擎
- 垂直/水平分割、分庫分表、讀寫分離
Redis部分面試題
Redis 是什么?有什么優點?都有哪些使用場景
- Reids 是完全開源免費的,用C語言編寫的,遵守BSD協議,
是一個高性能的(key/value)分布式內存數據庫,基於內存運行
並支持持久化的NoSQL數據庫,是當前最熱門的NoSql數據庫之一,
也被人們稱為數據結構服務器- 優點
- Redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啟的時候可以再次加載進行使用
- Redis不僅僅支持簡單的key-value類型的數據,同時還提供list,set,zset,hash等數據結構的存儲
- Redis支持數據的備份,即master-slave模式的數據備份
- 應用場景
- 內存存儲和持久化:redis支持異步將內存中的數據寫到硬盤上,同時不影響繼續服務
- 取最新N個數據的操作,如:可以將最新的10條評論的ID放在Redis的List集合里面
- 模擬類似於HttpSession這種需要設定過期時間的功能
- 發布、訂閱消息系統
- 定時器、計數器
Redis 為什么是單線程的
- Redis是基於內存的操作,CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機器內存的大小或者網絡帶寬。既然單線程容易實現,而且CPU不會成為瓶頸
Redis 的緩存預熱
- 在項目配置文件中生命自定義的
key
,在項目啟動時會判斷redis是否存在key
,如果沒有就會創建一個key
傳入null
值- 數據加熱的含義就是在正式部署之前,我先把可能的數據先預先訪問一遍,這樣部分可能大量訪問的數據就會加載到緩存中
redis 緩存雪崩是什么,怎么解決 ?
緩存雪崩是指,緩存層出現了錯誤,不能正常工作了.於是所有的請求都會達到存儲層,存儲層的調用量會暴增,造成存儲層也會掛掉的情況.
解決方案
- redis 高可用 就是搭建 redis 集群,其中一台redis掛掉后 可以使用其他的 redis
- 限流降級 就是每一個 key 只能一個線程來查詢數據和緩存,其他線程等待
- 數據預熱 數據加熱的含義就是在正式部署之前,我先把可能的數據先預先訪問一遍,這樣部分可能大量訪問的數據就會加載到緩存中.在即將發生大並發訪問前手動觸發加載緩存不同的 key ,設置不同的過期時間,讓緩存失效的時間點盡量均勻.*
緩存穿透是什么?如何解決
就是訪問redis數據庫,查不到數據,就是沒有命中,會去持久化數據庫查詢,還是沒有查到.假如高並發的情況下,持久化數據庫一下增加了很大壓力,就相當於出現了緩存穿透
解決方案
- 緩存空對象 在存儲層命中失敗后,即使返回空對象也將其緩存,並設置一個過期時間,之后訪問的這個數據將會從緩存中取出,很好的保護了后端數據源,這樣也會有出現問題 例如空值被緩存也就會增加大量的緩存空間,設置了過期時間還是會存在緩存層和存儲層的數據會有一段時間窗口的不一致,這對於需要保持一致性的業務會有影響
- 布隆過濾器 對所有可能查詢的參數以 hash 形式存儲,查詢時發現值不存在就直接丟棄,不會去持久層查詢
Redis 支持的數據類型有哪些
- String、List、Set、Hash、ZSet這5種
Redis 支持的 Java 客戶端有哪些
- Redisson、Jedis、lettuce 等等,官方推薦使用 Redisson
Jedis 與 Redisson 有哪些區別
- Jedis 和 Redisson 都是Java中對Redis操作的封裝。Jedis 只是簡單的封裝了 Redis 的API庫,可以看作是Redis客戶端,它的方法和Redis 的命令很類似。Redisson 不僅封裝了 redis ,還封裝了對更多數據結構的支持,以及鎖等功能,相比於Jedis 更加大。但Jedis相比於Redisson 更原生一些,更靈活
怎么保證緩存與數據庫數據的一致性
- 對刪除緩存進行重試,數據的一致性要求越高,我越是重試得快。
- 定期全量更新,簡單地說,就是我定期把緩存全部清掉,然后再全量加載。
- 給所有的緩存一個失效期
Redis 持久化有幾種方式
- 快照方式(RDB, Redis DataBase)將某一個時刻的內存數據,以二進制的方式寫入磁盤
- 文件追加方式(AOF, Append Only File),記錄所有的操作命令,並以文本的形式追加到文件中
- 混合持久化方式,Redis 4.0 之后新增的方式,混合持久化是結合了 RDB 和 AOF 的優點,在寫入的時候,先把當前的數據以 RDB 的形式寫入文件的開頭,再將后續的操作命令以 AOF 的格式存入文件,這樣既能保證 Redis 重啟時的速度,又能簡單數據丟失的風險
Redis 怎么實現分布式鎖
SET key value [EX seconds] [PX milliseconds] [NX|XX]
EX second
:設置鍵的過期時間為second秒PX millisecond
:設置鍵的過期時間為millisecond毫秒NX
:只在鍵不存在時,才對鍵進行設置操作XX
:只在鍵已經存在時,才對鍵進行設置操作- SET操作成功完成時,返回
OK
,否則返回nil
Redis 分布式鎖有什么缺陷
- 死鎖
- 設置鎖的過期時間,且需要保證setNx和設置過期時間操作的原子性
- 錯位解鎖
- 加鎖時記錄當前線程ID,解鎖時判斷ID是否一致
- 解鎖時,查詢redis里記錄鎖的ID,以及刪除redis中鎖的記錄,這兩步操作可以使用lua腳本保持原子性
- 業務並發執行問題
- 加鎖成功后開啟守護線程,當臨近過期時間,業務還未完成時,開始續時,重復此步驟直到業務完成
Redis 如何做內存優化
- 縮減鍵值對象:滿足業務要求下 key 越短越好;value 值進行適當壓縮
- 共享對象池:即 Redis 內部維護[0-9999]的整數對象池,開發中在滿足需求的前提下,盡量使用整數對象以節省內存
- 盡可能使用散列表(hashes)
- 編碼優化,控制編碼類型
- 控制 key 的數量
Redis 淘汰策略有哪些
noeviction
: 不刪除策略, 達到最大內存限制時, 如果需要更多內存, 直接返回錯誤信息。 大多數寫命令都會導致占用更多的內存(有極少數會例外, 如 DEL )allkeys-lru
: 所有key通用; 優先刪除最近最少使用(less recently used ,LRU) 的 keyvolatile-lru
: 只限於設置了expire
的部分; 優先刪除最近最少使用(less recently used ,LRU) 的 keyallkeys-random
: 所有key通用; 隨機刪除一部分 keyvolatile-random
: 只限於設置了 expire 的部分; 隨機刪除一部分 keyvolatile-ttl
: 只限於設置了 expire 的部分; 優先刪除剩余時間(time to live,TTL) 短的key
Redis 常見的問題有哪些? 該如何解決
- 緩存和數據庫雙寫一致性問題
- 就是如果對數據有強一致性要求,不能放緩存。我們所做的一切,只能保證最終一致性
- 采取正確更新策略,先更新數據庫,再刪緩存。其次,因為可能存在刪除緩存失敗的問題,提供一個補償措施即可,例如利用消息隊列
- 緩存穿透問題
- 利用互斥鎖,緩存失效的時候,先去獲得鎖,得到鎖了,再去請求數據庫。沒得到鎖,則休眠一段時間重試
- 采用異步更新策略,無論 Key 是否取到值,都直接返回。Value 值中維護一個緩存失效時間,緩存如果過期,異步起一個線程去讀數據庫,更新緩存。需要做緩存預熱(項目啟動前,先加載緩存)操作
- 提供一個能迅速判斷請求是否有效的攔截機制,比如,利用布隆過濾器,內部維護一系列合法有效的 Key。迅速判斷出,請求所攜帶的 Key 是否合法有效。如果不合法,則直接返回
- 緩存雪崩問題
- 給緩存的失效時間,加上一個隨機值,避免集體失效
- 使用互斥鎖,但是該方案吞吐量明顯下降
- 雙緩存。我們有兩個緩存,緩存 A 和緩存 B。緩存 A 的失效時間為 20 分鍾,緩存 B 不設失效時間。自己做緩存預熱操作(從A中讀不到,就去B讀,返回數據時需要異步啟動一個更新線程,更新線程同時更新緩存 A 和緩存 B)
- 緩存的並發競爭問題
- 如果對這個 Key 操作,不要求順序
- 這種情況下,准備一個分布式鎖,大家去搶鎖,搶到鎖就做 set 操作即可,比較簡單。
- 如果對這個 Key 操作,要求順序
- 假設有一個 key1,系統 A 需要將 key1 設置為 valueA,系統 B 需要將 key1 設置為 valueB,系統 C 需要將 key1 設置為 valueC
- 期望按照 key1 的 value 值按照 valueA > valueB > valueC 的順序變化。這種時候我們在數據寫入數據庫的時候,需要保存一個時間戳。
- 系統A key 1 {valueA 3:00}
- 系統B key 1 {valueB 3:05}
- 系統C key 1 {valueC 3:10}
- 那么,假設這會系統 B 先搶到鎖,將 key1 設置為{valueB 3:05}。接下來系統 A 搶到鎖,發現自己的 valueA 的時間戳早於緩存中的時間戳,那就不做 set 操作了,以此類推。
- 其他方法,比如利用隊列,將 set 方法變成串行訪問也可以。總之,靈活變通。
RabbitMQ部分面試題
RabbitMq 的使用場景有哪些
- 多個應用之間的耦合
- 跨系統的異步通信
- 流量削峰
- 比如:注冊用戶、發送激活郵箱、訂單下單等
RabbitMq 有哪些重要的角色
- 生產者:消息的創建者,負責創建和推送數據到消息服務器
- 消費者:消息的接收方,負責處理數據和確認消息
- 代理:就是RabbiMQ本身,不生產不消費,只是快遞消息
RabbitMq 有哪些重要的組件
- ConnectionFactory(連接管理器):應用程序與Rabbit之間建立連接的管理器,程序代碼中使用
- Channel(信道):消息推送使用的通道
- Exchange(交換器):用於接受、分配消息
- Queue(隊列):用於存儲生產者的消息
- RoutingKey(路由鍵):用於把生成者的數據分配到交換器上
- BindingKey(綁定鍵):用於把交換器的消息綁定到隊列上
RabbitMQ的消息存儲方式
RabbitMQ 對於 queue 中的 message 的保存方式有兩種方式:disc 和 ram.如果采用disc,則需要對 exchange/queue/delivery mode 都要設置成 durable 模式. Disc 方式的好處是當 RabbitMQ 失效了, message 仍然可以在重啟之后恢復.而使用 ram 方式, RabbitMQ 處理 message 的效率要高很多, ram 和 disc 兩種方式的效率比大概是 3:1.所以如果在有其它 HA 手段保障的情況下,選用 ram 方式是可以提高消息隊列的工作效率的.
RabbitMq 中 vhost 的作用是什么
- vhost本質上是一個mini版的RabbitMQ服務器,擁有自己的隊列、綁定、交換器和權限控制
- 從 RabbitMQ 的全局角度 vhost可以作為不同權限隔離的手段(一個典型的例子,不同的應用可以跑在不同的vhost中)
RabbitMq 的消息是怎么發送的
- 生產者把生產的小心通過channel發送到Exchange上,Exchange通過綁定的router key來選擇Queue,消費者監聽到Queue上有新的消息,就消費調此消息
RabbitMq 怎么保證消息的穩定性
- 提供了事務的功能,通過將 channel 設置為 confirm(確認模式)
RabbitMq 怎么避免丟失消息
- 消息持久化
- 消費端的ack簽收機制
- 設置集群鏡像模式
- 消息補償機制
要保證消息持久化成功的條件有哪些
- 聲明隊列必須設置持久化 durable 設置為 true
- 消息推送投遞模式必須設置持久化,deliveryMode 設置為 2(持久)
- 消息已經到達持久化交換器
- 消息已經到達持久化隊列
以上四個條件都滿足才能保證消息持久化成功
RabbitMq 持久化有什么缺點
- 持久化的缺點就是降低了服務器的吞吐量,因為使用的是磁盤而非內存存儲,從而降低了吞吐量。可盡量使用 ssd 硬盤來緩解吞吐量的問題
RabbitMq 有幾種廣播方式
fanout
廣播模式:所有bind到此exchange的queue都可以接收消息direct
直接交換:通過routingKey和exchange決定的那個唯一的queue可以接收消息topic
通配符模式:所有符合routingKey(此時可以是一個表達式)的routingKey所bind的queue可以接收消息
RabbitMq 怎么實現延遲消息隊列
- 通過消息過期后進入死信交換器,再由交換器轉發到延遲消費隊列,實現延遲功能
- 使用 RabbitMQ-delayed-message-exchange 插件實現延遲功能。
RabbitMq 集群有什么用
- 高可用:某個服務器出現問題,整個 RabbitMQ 還可以繼續使用
- 高容量:集群可以承載更多的消息量。
RabbitMq 節點的類型有哪些
- 磁盤節點:消息會存儲到磁盤
- 內存節點:消息都存儲在內存中,重啟服務器消息丟失,性能高於磁盤類型
RabbitMq 集群搭建需要注意哪些問題
- 各節點之間使用
–link
連接,此屬性不能忽略- 各節點使用的 erlang cookie 值必須相同,此值相當於
秘鑰
的功能,用於各節點的認證- 整個集群中必須包含一個磁盤節點
RabbitMq 每個節點是其他節點的完整拷貝嗎
不是
- 如果每個節點都擁有所有隊列的完全拷貝,這樣新增節點,不但沒有新增存儲空間,反而增加了更多的冗余數據
- 如果每條消息都需要完整拷貝到每一個集群節點,那新增節點並沒有提升處理消息的能力,最多是保持和單節點相同的性能甚至是更糟
RabbitMq 集群中唯一一個磁盤節點崩潰了會發生什么
- 唯一磁盤節點崩潰了,集群是可以保持運行的,但不能更改任何東西
- 不能創建隊列
- 不能創建交換器
- 不能創建綁定
- 不能添加用戶
- 不能更改權限
- 不能添加和刪除集群節點
RabbitMq 對集群停止順序有要求嗎
- RabbitMQ 對集群的停止的順序是有要求的,應該先關閉內存節點,最后再關閉磁盤節點。如果順序恰好相反的話,可能會造成消息的丟失
JVM部分面試題
JVM 主要的組成部分?及其作用
類加載器(Class Loader)
:加載類文件到內存。Class loader只管加載,只要符合文件結構就加載,至於能否運行,它不負責,那是有Exectution Engine 負責的執行引擎(Execution Engine)
:也叫解釋器,負責解釋命令,交由操作系統執行本地庫接口(Native Interface)
:本地接口的作用是融合不同的語言為java所用- 運行時數據區(Runtime Data Area)
JVM 運行時數據區是什么
堆
是java對象的存儲區域,任何用new字段分配的java對象實例和數組,都被分配在堆上,java堆可用-Xms和-Xmx進行內存控制,常量池
:運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池中存放,jdk1.7以后,運行時常量池從方法區移到了堆上方法區
:用於存儲已被虛擬機加載的類信息,常量,靜態變量,即是編譯器編譯后的代碼等數據虛擬機棧
:虛擬機棧中執行每個方法的時候,都會創建一個棧楨用於存儲局部變量表,操作數棧,動態鏈接,方法出口等信息本地方法棧
:與虛擬機發揮的作用相似,相比於虛擬機棧為Java方法服務,本地方法棧為虛擬機使用的Native方法服務,執行每個本地方法的時候,都會創建一個棧幀用於存儲局部變量表,操作數棧,動態鏈接,方法出口等信息程序計數器
:指示Java虛擬機下一條需要執行的字節碼指令
堆和棧的區別
- 棧內存用來存儲局部變量和方法調用
- 而堆內存用來存儲 Java 中的對象.無論是成員變量,局部變量,還是類變量.,它們指向的對象都存儲在堆內存中.
- 棧內存歸屬單個線程,一個棧對應一個線程,其中儲存的變量只能在該線程中可以訪問到,也可以理解為私有內存
- 而堆內存中的對象 所有線程均可見,堆內存中對象可以被所有線程訪問到
- 棧內存要遠小於堆內存
隊列和棧是什么?有什么區別
- 隊列和棧是兩種不同的數據結構
- 操作名稱不同
- 隊列的插入稱為入隊,隊列的刪除稱為出隊。棧的插入稱為進棧,棧的刪除稱為出棧
- 可操作的方式不同
- 隊列是在隊尾入隊,隊頭出隊,即兩邊都可操作。而棧的進棧和出棧都是在棧頂進行的,無法對棧底直接進行操作。
- 隊列先進先出 FIFO,棧是后進先出 LIFO
類加載器有哪些?什么是雙親委派模型
- 啟動類加載器 Bootstrap ClassLoader:加載<JAVA_HOME>\lib目錄下核心庫
- 擴展類加載器 Extension ClassLoader:加載<JAVA_HOME>\lib\ext目錄下擴展包
- 應用程序類加載器 Application ClassLoader: 加載用戶路徑(classpath)上指定的類庫
雙親委派的意思是如果一個類加載器需要加載類,那么首先它會把這個類請求委派給父類加載器去完成,每一層都是如此。一直遞歸到頂層,當父加載器無法完成這個請求時,子類才會嘗試去加載。這里的雙親其實就指的是父類,沒有mother。父類也不是我們平日所說的那種繼承關系,只是調用邏輯是這樣
類加載的執行過程
- 加載
- 加載指的是把class字節碼文件從各個來源通過類加載器裝載入內存中
- 字節碼來源:一般的加載來源包括從本地路徑下編譯生成的.class文件,從jar包中的.class文件,從遠程網絡,以及動態代理實時編譯類加載器。一般包括啟動類加載器,擴展類加載器,應用類加載器,以及用戶的自定義類加載器
- 鏈接
- 驗證
主要是為了保證加載進來的字節流符合虛擬機規范,不會造成安全錯誤,包括對於文件格式的驗證
- 准備
主要是為類變量(注意,不是實例變量)分配內存,並且賦予初值。特別需要注意,初值,不是代碼中具體寫的初始化的值,而是Java虛擬機根據不同變量類型的默認初始值:8種基本類型的初值,默認為0;引用類型的初值則為null;
- 解析
將常量池內的符號引用替換為直接引用的過程。在解析階段,虛擬機會把所有的類名,方法名,字段名這些符號引用替換為具體的內存地址或偏移量,也就是直接引用
- 初始化
這個階段主要是對類變量初始化,是執行類構造器的過程
怎么判斷對象是否可以收回
- 引用計數算法
- 判斷對象的引用數量
- 通過判斷對象的引用數量來決定對象是否可以被回收
- 每個對象實例都有一個引用計數器,被引用則+1,完成引用則-1
- 任何引用計數為0的對象實例可以被當作垃圾收集
- 優缺點
- 優點:執行效率高,程序執行受影響較小
- 缺點:無法檢測出循環引用的情況,導致內存泄漏
- 可達性分析算法
- 通過判斷對象的引用鏈是否可達來決定對象是否可以被回收
- 可以作為GC Root對象的對象有
- 虛擬機棧中引用的對象(棧幀中的本地變量表)
- 方法區中的常量引用對象
- 方法區中類靜態屬性引用對象
- 本地方法棧中JNI(Native方法)的引用對象
- 活躍線程中的引用對象
Java 中有哪些引用類型
- 強引用(strongreference)
就是指在程序代碼之中普遍存在的,類似“Object obj=new Object()” 這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象實例
- 軟引用(softreference)
是用來描述一些還有用但並非必需的對象。對於軟引用關聯着的對象, 在系統將要發生內存溢出異常之前,將會把這些對象實例列進回收范圍之中進行 第二次回收。如果這次回收還沒有足夠的內存,才會拋出內存溢出異常。在 JDK 1.2 之后,提供了
SoftReference
類來實現軟引用
- 弱引用(weakreference)
也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱 引用關聯的對象實例只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時, 無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象實例。在 JDK 1.2 之 后,提供了
WeakReference
類來實現弱引用
- 虛引用(phantomreference)
也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系。一個對象實例是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用 來取得一個對象實例。為一個對象設置虛引用關聯的唯一目的就是能在這個對象 實例被收集器回收時收到一個系統通知。在 JDK 1.2 之后,提供了 PhantomReference 類來實現虛引用
JVM 中垃圾回收算法
- 標記-清除算法
標記-清除算法采用從根集合(GC Roots)進行掃描,對存活的對象進行標記,標記完畢后,再掃描整個空間中未被標記的對象,進行回收,如下圖所示。標記-清除算法不需要進行對象的移動,只需對不存活的對象進行處理,在存活對象比較多的情況下極為高效,但由於標記-清除算法直接回收不存活的對象,因此會造成內存碎片
- 復制除算法
復制算法的提出是為了克服句柄的開銷和解決內存碎片的問題。它開始時把堆分成 一個對象 面和多個空閑面, 程序從對象面為對象分配空間,當對象滿了,基於copying算法的垃圾 收集就從根集合(GC Roots)中掃描活動對象,並將每個 活動對象復制到空閑面(使得活動對象所占的內存之間沒有空閑洞),這樣空閑面變成了對象面,原來的對象面變成了空閑面,程序會在新的對象面中分配內存
- 標記-整理(壓縮)算法
標記-整理算法采用標記-清除算法一樣的方式進行對象的標記,但在清除時不同,在回收不存活的對象占用的空間后,會將所有的存活對象往左端空閑空間移動,並更新對應的指針。標記-整理算法是在標記-清除算法的基礎上,又進行了對象的移動,因此成本更高,但是卻解決了內存碎片的問題
JVM 有哪些垃圾回收器
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:CMS、Serial Old、Parallel Old
- 整堆收集器: G1
介紹一下 CMS 垃圾回收器
- CMS收集器 :一種以獲取最短回收停頓時間為目標的收集器
- 特點:基於標記-清除算法實現。並發收集、低停頓
- 應用場景:適用於注重服務的響應速度,希望系統停頓時間最短,給用戶帶來更好的體驗等場景下。如web程序、b/s服務
- CMS收集器的運行過程分為下列4步:
- 初始標記:標記GC Roots能直接到的對象,速度很快但是仍存在Stop The World問題
- 並發標記:進行GC Roots Tracing 的過程,找出存活對象且用戶線程可並發執行
- 重新標記:為了修正並發標記期間因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄。仍然存在Stop The World問題
- 並發清除:對標記的對象進行清除回收
- CMS收集器的內存回收過程是與用戶線程一起並發執行的
- CMS收集器的缺點:
- 對CPU資源非常敏感
- 無法處理浮動垃圾,可能出現Concurrent Model Failure失敗而導致另一次Full GC的產生
- 因為采用標記-清除算法所以會存在空間碎片的問題,導致大對象無法分配空間,不得不提前觸發一次Full GC
CMS收集器的工作過程圖:
新生代垃圾回收器和老生代垃圾回收器有哪些?有什么區別
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:CMS、Serial Old、Parallel Old
- 區別:
新生代垃圾回收器一般采用的是復制算法,復制算法的優點是效率高,缺點是內存利用率低;老年代回收器一般采用的是標記-整理的算法進行垃圾回收
簡述分代垃圾回收器是怎么工作的
- 分代回收器有兩個分區:老生代和新生代,新生代默認的空間占比總空間的 1/3,老生代的默認占比是 2/3。 新生代使用的是復制算法,新生代里有 3 個分區:Eden、To Survivor、From Survivor,它們的默認占比是 8:1:1
- 執行流程
- 把 Eden + From Survivor 存活的對象放入 To Survivor 區
- 清空 Eden 和 From Survivor 分區; From Survivor 和 To Survivor 分區交換,From Survivor 變 To Survivor,To Survivor 變 From Survivor
- 每次在 From Survivor 到 To Survivor 移動時都存活的對象,年齡就 +1,當年齡到達 15(默認配置是 15)時,升級為老生代。大對象也會直接進入老生代
- 老生代當空間占用到達某個值之后就會觸發全局垃圾收回,一般使用標記整理的執行算法
- 以上這些循環往復就構成了整個分代垃圾回收的整體執行流程
JVM 調優的工具有哪些
- jconsole
jdk自帶的工具
:是一個基於JMX(java management extensions)的GUI性能監測工具(jdk/bin目錄下點擊jconsole.exe即可啟動)- VisualVM:它提供了一個可視界面,用於查看 Java 虛擬機 (Java Virtual Machine, JVM) 上運行的基於 Java 技術的應用程序(Java 應用程序)的詳細信息(jdk/bin目錄下面雙擊jvisualvm.exe既可使用)
- MAT
第三方調優工具
:一個基於Eclipse的內存分析工具,是一個快速、功能豐富的Java heap分析工具,它可以幫助我們查找內存泄漏和減少內存消耗(MAT以eclipse 插件的形式來安裝)- GChisto:專業分析gc日志的工具,可以通過gc日志來分析:Minor GC、full gc的時間、頻率等等,通過列表、報表、圖表等不同的形式來反應gc的情況(配置好本地的jdk環境之后,雙擊GChisto.jar,在彈出的輸入框中點擊 add 選擇gc.log日志)
- gcviewer:分析小工具,用於可視化查看由Sun / Oracle, IBM, HP 和 BEA Java 虛擬機產生的垃圾收集器的日志
JVM 調優的參數有哪些
算法題
其他部分面試題
Api 接口如何實現 ?
在類里使用 implements 關鍵字實現 Api 接口
MySQL 鏈接數據庫常用的幾種方式 ?
- Mybatis 框架
- Hibernate 框架
- JDBC 技術
- c3p0 連接池
- dbcp 連接池
SpringBoot 如何集成 Redis ?
在 pom.xml 文件引入 redis 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在 application 配置文件中 書寫 redis 配置
spring.redis.host=127.0.0.1
#Redis服務器連接端口
spring.redis.port=6379
#Redis服務器連接密碼(默認為空)
#spring.redis.password=
SpringCloud 的優點 ?
- 服務之間采用Restful等輕量級通訊
- 精准的制定優化服務方案,提高系統的可維護性
- 服務之間拆分細致,資源可重復利用,提高開發效率
SpringCloud 用了哪些組件 ?
- netflix Eureka 注冊中心
- netflix Ribbon 負載均衡
- netflix Zuul 網關
- netflix Hystrix 熔斷器
- feign 服務調用
List 和 Set 的區別
- List 允許有多個重復對象,而 Set 不允許有重復對象
- List 允許有多個NULL值,而 Set 只允許有一個NULL值
- List 是一個有序的容器,輸出順序即是輸入順序
- Set 是一個無序的容器無法保證每個元素的順序,但是可以用 TreeSet 通過 Comparator 或者 Comparable 維護排序順序
- List的實現類有 ArrayList、LinkList、Vector 其中 ArrayList 最常用於查詢較多,隨意訪問.LinkList 同於新增和刪除較多的情況,Vector 表示底層數組,線程安全象
- Set的實現類有 HashSet、TreeSet、LinkedHashSet 其中基於 HashMap 實現的 HashSet 最為流行,TreeSet 是一個有序的Set容器象
擴展
Map的實現類有HashMap、HashTable、TreeMap
Java 中 static 的作用
- 表示全局或靜態的意思、用來修飾成員變量和成員方法,也可以形成靜態代碼塊
- 達到了不用實例化就可以使用被 public static 修飾的變量或方法
什么單例模式 ?
保證整個項目中一個類只有一個對象的實例,實現這種功能就叫做單例模式
- 單例模式的好處:
- 單例模式節省公共資源
- 單例模式方便控制
- 如何保證是單利模式 ?
- 構造私有化
- 以靜態方法返回實例
- 確保對象實例只有一個
- 單例模式有哪幾個 ?
- 餓漢模式
把對象創建好,需要使用的時候直接拿到就行
- 懶漢模式
等你需要的時候在創建對象,后邊就不會再次創建
SpringBoot 常用的幾個注解 ?
- @SpringBootApplication SpringBoot的核心注解 啟動類
- @EnableAutoConfiguration 開啟SpringBoot自動配置
- @RestController 在控制層 是@ResponseBody注解與@Controller注解的合集
- @RequestMapper 處理請求地址映射的注解
- @RequestParam 獲取url上傳過來的參數
- @Configuration 聲明配置類
- @Component 通用注解
- @Service 業務邏輯層
Java 八大數據類型
char 字符型 byte 字節型 boolean 布爾型
float 單浮點型 double 雙浮點型
int 整數型 short 短整數型 long 長整數型
MySQL分頁和升序降序如何實現 ?
- 可以用 limit
select
name
,age
,sex
from t_student limit(0,5);
- 升序 order by xx asc
select
name
,age
,sex
from t_student order byage
asc;
- 降序 order by xx desc
select
name
,age
,sex
from t_student order byage
desc;
maven 是干什么的,它有什么好處 ?
- maven 專門構建和管理Java項目的工具
- maven的好處在於可以將項目過程規范化、自動化、高效化以及強大的可擴展性
MySQL 如何添加索引 ?
- PRIMARY KEY (主鍵索引)
- 添加INDEX(普通索引) ALTER TABLE
table_name
ADD INDEX index_name (column
)- 添加UNIQUE(唯一索引) ALTER TABLE
table_name
ADD UNIQUE index_name (column
)- 添加FULLTEXT(全文索引) ALTER TABLE
table_name
ADD FULLTEXT (column
)- 添加多列索引 ALTER TABLE
table_name
ADD INDEX index_name (column1
,column2
,column3
)
MySQL 索引的實現方式?
MySQL 索引底層的實現方式是 B+Tree也就是B+樹 具體查看 B+Tree實現方式
Vue的數據雙向綁定原理
使用v-mode屬性, 它的原理是利用了Object.defineProperty()方法重新定義了對象獲取屬性值(get)和設置屬性值(set)的操作來實現的
ActiveMQ的消息存儲方式
- 采取先進先出模式,同一時間,消息只會發送給某一個消費者,只有當該消息被消費並告知已收到時,它才能在代理的存儲中被刪除.
- 對於持久性訂閱來說,每一個消費者都會獲取消息的拷貝.為了節約空間,代理的存儲介質中只存儲了一份消息,存儲介質的持久訂閱對象為其以后的被存儲的消息維護了一個指針,消費者消費時,從存儲介質中復制一個消息.消息被所有訂閱者獲取后才能刪除.
KahaDB消息存儲
基於文件的消息存儲機制,為了提高消息存儲的可靠性和可恢復性,它整合了一個事務日志.KahaDB擁有高性能和可擴展性等特點.由於KahaDB使用的是基於文件的存儲,所以不需要使用第三方數據庫