1. 程式人生 > >java面試題庫(長期)

java面試題庫(長期)

本文內容來自網際網路各種面試例項,以及自己的面試經歷,主要是中級開發的面試題

初中級java面試主要分為幾個部分:

0、序
1、java基礎
2、 java多執行緒
3、 jvm知識
4、 spring等框架知識
5、 常用實踐,如session同步
6、 其他知識,例如tomcat
7、 筆試演算法

序言

首先,外貌要乾淨整潔,這個是必須的。其次守時,既不能晚點也不能早到,最好在約定時間的前十分鐘
面試主要分為幾個部分,首先是java基礎,這類佔比較少;其次是對java及spring框架的的深入理解,如多執行緒,ioc,apo,spring bean的生命週期,這類佔比較重

;再往後就是常用的工具的理解,如jvm的常用配置,年輕代老年代,gc,tomcat等容器怎麼處理請求,這類問題佔比適中;最後就是一些廣度的問題(實際的經驗),對自己專案的理解,用到了那些工具,遇到了哪些問題,解決的方法
最後,一定要做一些面試的準備,刷面試題、練習面試,建議至少提前一個月做準備,機會是留給有準備的人的

1、java基礎

1.1、List、Set、Map的異同
List(列表)
List的元素以線性方式儲存,可以存放重複物件,List主要有以下兩個實現類:

ArrayList : 長度可變的陣列,可以對元素進行隨機的訪問,向ArrayList中插入與刪除元素的速度慢。 JDK8 中ArrayList擴容的實現是通過grow()方法裡使用語句newCapacity = oldCapacity + (oldCapacity >> 1)(即1.5倍擴容)計算容量,然後呼叫Arrays.copyof()方法進行對原陣列進行復制。
LinkedList

: 採用連結串列資料結構,插入和刪除速度快,但訪問速度慢。

Set(集合)
Set中的物件不按特定(HashCode)的方式排序,並且沒有重複物件,Set主要有以下兩個實現類:

HashSet: HashSet按照雜湊演算法來存取集合中的物件,存取速度比較快。當HashSet中的元素個數超過陣列大小*loadFactor(預設值為0.75)時,就會進行近似兩倍擴容(newCapacity = (oldCapacity << 1) + 1)。
TreeSet :TreeSet實現了SortedSet介面,能夠對集合中的物件進行排序。

Map(對映)
Map是一種把鍵物件和值物件對映的集合,它的每一個元素都包含一個鍵物件和值物件。 Map主要有以下兩個實現類:

HashMap:HashMap基於散列表實現,其插入和查詢<K,V>的開銷是固定的,可以通過構造器設定容量和負載因子來調整容器的效能。
LinkedHashMap:類似於HashMap,但是迭代遍歷它時,取得<K,V>的順序是其插入次序,或者是最近最少使用(LRU)的次序。
TreeMap:TreeMap基於紅黑樹實現。檢視<K,V>時,它們會被排序。TreeMap是唯一的帶有subMap()方法的Map,subMap()可以返回一個子樹。

比較 List Set Map
繼承介面 Collection Collection
常見實現類 AbstractList(其常用子類有ArrayList、LinkedList、Vector) AbstractSet(其常用子類有HashSet、LinkedHashSet、TreeSet) HashMap、HashTable
常見方法 add( )、remove( )、clear( )、get( )、contains( )、size( ) add( )、remove( )、clear( )、contains( )、size( ) put( )、get()、remove( )、clear( )、containsKey( )、containsValue( )、keySet( )、values( )、size( )
元素 可重複 不可重複(用equals()判斷) 不可重複
順序 有序 無序(實際上由HashCode決定)
執行緒安全 Vector執行緒安全 Hashtable執行緒安全

1.2、String、StringBuffer、StringBuilder的使用

1.3、HashMap、TreeMap、LinkedHashMap的特點

1.4、HashMap內部結構、演算法
底層實現:HashMap底層整體結構是一個數組,陣列中的每個元素又是一個連結串列。每次新增一個物件(put)時會產生一個連結串列物件(Object型別),Map中的每個Entry就是陣列中的一個元素(Map.Entry就是一個<Key,Value>),它具有由當前元素指向下一個元素的引用,這就構成了連結串列。
儲存原理:當向HsahMap中新增元素的時候,先根據HashCode重新計算Key的Hash值,得到陣列下標,如果陣列該位置已經存在其他元素,那麼這個位置的元素將會以連結串列的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾,如果陣列該位置元素不存在,那麼就直接將該元素放到此陣列中的該位置。
去重原理:不同的Key算到陣列下標相同的機率很小,新建一個<K,V>放入到HashMap的時候,首先會計算Key的陣列下標,如果陣列該位置已經存在其他元素,則比較兩個Key,若相同則覆蓋寫入,若不同則形成連結串列。
讀取原理:從HashMap中讀取(get)元素時,首先計算Key的HashCode,找到陣列下標,然後在對應位置的連結串列中找到需要的元素。
擴容機制:當HashMap中的元素個數超過陣列大小*loadFactor(預設值為0.75)時,就會進行2倍擴容(oldThr << 1)。

1.5、concurrent包下面有那幾大類
atomic
locks
Executor
Queue
Dueue
ConcurrentXX
Scheduled
Callable
Future

2、java多執行緒

2.1、lock和synchronized

synchronized是java中的一個關鍵字,也就是說是Java語言內建的特性。那麼為什麼會出現Lock呢?

  在上面一篇文章中,我們瞭解到如果一個程式碼塊被synchronized修飾了,當一個執行緒獲取了對應的鎖,並執行該程式碼塊時,其他執行緒便只能一直等待,等待獲取鎖的執行緒釋放鎖,而這裡獲取鎖的執行緒釋放鎖只會有兩種情況:

1)獲取鎖的執行緒執行完了該程式碼塊,然後執行緒釋放對鎖的佔有;

2)執行緒執行發生異常,此時JVM會讓執行緒自動釋放鎖。

  那麼如果這個獲取鎖的執行緒由於要等待IO或者其他原因(比如呼叫sleep方法)被阻塞了,但是又沒有釋放鎖,其他執行緒便只能乾巴巴地等待,試想一下,這多麼影響程式執行效率。

  因此就需要有一種機制可以不讓等待的執行緒一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。

  再舉個例子:當有多個執行緒讀寫檔案時,讀操作和寫操作會發生衝突現象,寫操作和寫操作會發生衝突現象,但是讀操作和讀操作不會發生衝突現象。

  但是採用synchronized關鍵字來實現同步的話,就會導致一個問題:

  如果多個執行緒都只是進行讀操作,所以當一個執行緒在進行讀操作時,其他執行緒只能等待無法進行讀操作。

  因此就需要一種機制來使得多個執行緒都只是進行讀操作時,執行緒之間不會發生衝突,通過Lock就可以辦到。

  另外,通過Lock可以知道執行緒有沒有成功獲取到鎖。這個是synchronized無法辦到的。

  總結一下,也就是說Lock提供了比synchronized更多的功能。但是要注意以下幾點:

  1)Lock不是Java語言內建的,synchronized是Java語言的關鍵字,因此是內建特性。Lock是一個類,通過這個類可以實現同步訪問;

  2)Lock和synchronized有一點非常大的不同,採用synchronized不需要使用者去手動釋放鎖,當synchronized方法或者synchronized程式碼塊執行完之後,系統會自動讓執行緒釋放對鎖的佔用;而Lock則必須要使用者去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。

Lock是一個介面,而synchronized是關鍵字。
synchronized會自動釋放鎖,而Lock必須手動釋放鎖。
Lock可以讓等待鎖的執行緒響應中斷,而synchronized不會,執行緒會一直等待下去。
通過Lock可以知道執行緒有沒有拿到鎖,而synchronized不能。
Lock能提高多個執行緒讀操作的效率。
synchronized能鎖住類、方法和程式碼塊,而Lock是塊範圍內的

第一大不足:由於我們沒辦法設定synchronized關鍵字在獲取鎖的時候等待時間,所以synchronized可能會導致執行緒為了加鎖而無限期地處於阻塞狀態。
第二大不足:使用synchronized關鍵字等同於使用了互斥鎖,即其他執行緒都無法獲得鎖物件的訪問權。這種策略對於讀多寫少的應用而言是很不利的,因為即使多個讀者看似可以併發執行,但他們實際上還是序列的,並將最終導致併發效能的下降。

雖然synchronized已經作為一個關鍵字被固化在Java語言中了,但它只提供了一種相當保守的執行緒安全策略,且該策略開放給程式設計師的控制能力極弱。

2.2、單機上一個執行緒池正在處理服務,如果忽然斷電了怎麼辦(正在處理和阻塞佇列裡的請求怎麼處理)?

2.3、為什麼要使用執行緒池?
在Java中,如果每當一個請求到達就建立一個新執行緒,開銷是相當大的。在實際使用中,每個請求建立新執行緒的伺服器在建立和銷燬執行緒上花費的時間和消耗的系統資源,甚至可能要比花在處理實際的使用者請求的時間和資源要多得多。除了建立和銷燬執行緒的開銷之外,活動的執行緒也需要消耗系統資源。如果在一個JVM裡建立太多的執行緒,可能會導致系統由於過度消耗記憶體或“切換過度”而導致系統資源不足。為了防止資源不足,伺服器應用程式需要一些辦法來限制任何給定時刻處理的請求數目,儘可能減少建立和銷燬執行緒的次數,特別是一些資源耗費比較大的執行緒的建立和銷燬,儘量利用已有物件來進行服務,這就是“池化資源”技術產生的原因。
2.4、執行緒池有什麼作用?
執行緒池主要用來解決執行緒生命週期開銷問題和資源不足問題。通過對多個任務重用執行緒,執行緒建立的開銷就被分攤到了多個任務上了,而且由於在請求到達時執行緒已經存在,所以消除了執行緒建立所帶來的延遲。這樣,就可以立即為請求服務,使應用程式響應更快。另外,通過適當地調整執行緒池中的執行緒數目可以防止出現資源不足的情況。

1.使用new Thread()建立執行緒的弊端:  
每次通過new Thread()建立物件效能不佳。  
執行緒缺乏統一管理,可能無限制新建執行緒,相互之間競爭,及可能佔用過多系統資源導致宕機或oom。  
缺乏更多功能,如定時執行、定期執行、執行緒中斷。  

2.使用Java執行緒池的好處:
重用存在的執行緒,減少物件建立、消亡的開銷,提升效能。  
可有效控制最大併發執行緒數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。  
提供定時執行、定期執行、單執行緒、併發數控制等功能。  

2.5、說說幾種常見的執行緒池及使用場景。
場景:
單個任務處理時間短
將需處理的任務數量大

2.6、執行緒池都有哪幾種工作佇列?
ArrayBlockingQueue 陣列型阻塞佇列
LinkedBlockingQueue 連結串列型阻塞佇列
DelayQueue 延時佇列
SynchronousQueue 同步佇列
PriorityBlockingQueue 優先阻塞佇列

2.7、怎麼理解無界佇列和有界佇列?
有界佇列:
1.初始的poolSize < corePoolSize,提交的runnable任務,會直接做為new一個Thread的引數,立馬執行 。
2.當提交的任務數超過了corePoolSize,會將當前的runable提交到一個block queue中,。
3.有界佇列滿了之後,如果poolSize < maximumPoolsize時,會嘗試new 一個Thread的進行救急處理,立馬執行對應的runnable任務。
4.如果3中也無法處理了,就會走到第四步執行reject操作。

與有界佇列相比,除非系統資源耗盡,否則無界的任務佇列不存在任務入隊失敗的情況。當有新的任務到來,系統的執行緒數小於corePoolSize時,則新建執行緒執行任務。當達到corePoolSize後,就不會繼續增加,若後續仍有新的任務加入,而沒有空閒的執行緒資源,則任務直接進入佇列等待。若任務建立和處理的速度差異很大,無界佇列會保持快速增長,直到耗盡系統記憶體。

2.8、執行緒池中的幾種重要的引數及流程說明
中止:Abort策略,預設策略,新任務提交時直接丟擲未檢查的異常RejectedExecutionException,該異常可由呼叫者捕獲。
拋棄:Discard策略,新提交的任務被拋棄。
拋棄最舊的:DiscardOldest策略,佇列的是“隊頭”的任務,然後嘗試提交新的任務。(不適合工作佇列為優先佇列場景)
呼叫者執行: CallerRuns策略,為調節機制,既不拋棄任務也不丟擲異常,而是將某些任務回退到呼叫者。不會線上程池的執行緒中執行新的任務,而是在呼叫exector的執行緒中執行新的任務。

2.9、執行緒池中的幾種重要的引數及流程說明

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
       ...
    }

corePoolSize- 池中所儲存的執行緒數,包括空閒執行緒。需要注意的是在初建立執行緒池時執行緒不會立即啟動,直到有任務提交才開始啟動執行緒並逐漸時執行緒數目達到corePoolSize。若想一開始就建立所有核心執行緒需呼叫prestartAllCoreThreads方法。
maximumPoolSize-池中允許的最大執行緒數。需要注意的是當核心執行緒滿且阻塞佇列也滿時才會判斷當前執行緒數是否小於最大執行緒數,並決定是否建立新執行緒。
keepAliveTime - 當執行緒數大於核心時,多於的空閒執行緒最多存活時間
unit - keepAliveTime 引數的時間單位。
workQueue - 當執行緒數目超過核心執行緒數時用於儲存任務的佇列。主要有3種類型的BlockingQueue可供選擇:無界佇列,有界佇列和同步移交。將在下文中詳細闡述。從引數中可以看到,此佇列僅儲存實現Runnable介面的任務。
threadFactory - 執行程式建立新執行緒時使用的工廠。
handler - 阻塞佇列已滿且執行緒數達到最大值時所採取的飽和策略。java預設提供了4種飽和策略的實現方式:中止、拋棄、拋棄最舊的、呼叫者執行。將在下文中詳細闡述。

2.10、執行緒和cpu核心數的關係
執行緒數=Ncpu / (1-阻塞係數)
IO密集型,阻塞係數接近於1
計算密集型,阻塞係數接近於0

3、jvm知識

3.1、happened-before原則
1.程式順序規則:一個執行緒中的每個操作,happens-before於該執行緒中的任意後續操作
2.監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的加鎖
3.volatile變數規則:對一個volatile域的寫,happens-before於任意後續對這個volatile域的讀
4.傳遞性:如果A happens-before B,且B happens-before C,那麼A happens-before C
5.start規則:如果執行緒A執行操作ThreadB.start()(啟動執行緒B),那麼A執行緒的ThreadB.start()操作happens-before於執行緒B中的任意操作
6.join規則:如果執行緒A執行操作ThreadB.join()併成功返回,那麼執行緒B中的任意操作happens-before於執行緒A從ThreadB.join()操作成功返回

3.2、類的載入
image
https://blog.csdn.net/wen7280/article/details/53856790

類裝載的條件:

Java虛擬機器不會無條件的裝載Class型別。

Java虛擬機器規定:一個類或者介面在初次使用時,必須進行初始化。

這裡的使用指的是主動使用,主動使用有以下幾種情況:

當建立一個類的例項時,比如使用new關鍵字,或者通過反射、克隆、反序列化方式。
當呼叫類的靜態方法時,即當使用了位元組碼invokestatic指令
當使用類或者介面的靜態欄位時(final常量除外,此種情況只會載入類而不會進行初始化),即使用getstatic或者putstatic指令(可以使用jclasslib軟體檢視生成的位元組碼檔案)
當使用java.lang.reflect包中的方法反射類的方法時
當初始化子類時,必須先初始化父類
作為啟動虛擬機器、含有main方法的那個類

除了以上情況屬於主動使用外,其他情況均屬於被動使用,被動使用不會引起類的初始化,只是載入了類卻沒有初始化。

4、spring等框架知識

4.1、spring mvc處理請求
image

SpringMVC核心處理流程:

1、DispatcherServlet前端控制器接收發過來的請求,交給HandlerMapping處理器對映器

2、HandlerMapping處理器對映器,根據請求路徑找到相應的HandlerAdapter處理器介面卡(處理器介面卡就是那些攔截器或Controller)

3、HandlerAdapter處理器介面卡,處理一些功能請求,返回一個ModelAndView物件(包括模型資料、邏輯檢視名)

4、ViewResolver檢視解析器,先根據ModelAndView中設定的View解析具體檢視

5、然後再將Model模型中的資料渲染到View上

4.2、Springbootapplication註解的原理

image

http://majunwei.com/view/201708231840127244.html

5、常用實踐,session同步

5.1、session同步
使用redis作為session持久化儲存。首先使用者連線進來,把session放在本地一份,redis一份,在本地有記錄的情況下使用本地快取(設定極小的時間過期,如2s)。當用戶再次連線進來,在本地記錄過期,或者本地沒有session的情況下,使用redis的記錄,

5.2、redis的簡單介紹

5.3、快取的使用

6、其他知識,如tomcat

6.1、tomcat如何處理請求
http://images2017.cnblogs.com/blog/1131840/201712/1131840-20171215142449996-360033997.jpg
Tomcat的兩個核心元件:Connector 和 Container
1.Connector元件
Connector元件將在某個指定的埠上偵聽客戶請求,接收瀏覽器發過來的tcp連線請求,建立一個Request和一個Response物件分別用於和其你去端交換資料,然後會產生一個執行緒來處理這個請求並把產生的Request和Response物件傳給Engine,從Engine中獲得響應並返回給客戶端。
Tomcat有兩個經典的Connector,一個直接偵聽來自瀏覽器的HTTP請求,另外一個偵聽來自其他的WebServer的請求。Cotote HTTP/1.1 Connector在埠8080處偵聽來自客戶瀏覽器的HTTP請求,Coyote JK2 Connector在埠8009處偵聽其他WebServer的Servlet/JSP請求。 Connector 最重要的功能就是接收連線請求然後分配執行緒讓 Container來處理這個請求,所以這必然是多執行緒的,多執行緒的處理是 Connector 設計的核心
2.Container元件
Container元件的體系結構如下:
http://images2017.cnblogs.com/blog/1131840/201712/1131840-20171215142450871-601807957.jpg
Container

Container是容器的父介面,該容器的設計用的是典型的責任鏈的設計模式,它由四個自容器元件構成,分別是Engine、Host、Context、Wrapper。這四個元件是負責關係,存在包含關係。通常一個Servlet class對應一個Wrapper,如果有多個Servlet則定義多個Wrapper,如果有多個Wrapper就要定義一個更高的Container,如Context。 Context定義在父容器 Host 中,其中Host 不是必須的,但是要執行 war 程式,就必須要 Host,因為 war 中必有 web.xml 檔案,這個檔案的解析就需要 Host 了,如果要有多個 Host 就要定義一個 top 容器 Engine 了。而 Engine 沒有父容器了,一個 Engine 代表一個完整的 Servlet 引擎。

Engine

Engine 容器比較簡單,它只定義了一些基本的關聯關係 Host 容器

Host

Host 是 Engine 的字容器,一個 Host 在 Engine 中代表一個虛擬主機,這個虛擬主機的作用就是執行多個應用,它負責安裝和展開這些應用,並且標識這個應用以便能夠區分它們。它的子容器通常是 Context,它除了關聯子容器外,還有就是儲存一個主機應該有的資訊。

Context

Context 代表 Servlet 的 Context,它具備了 Servlet 執行的基本環境,理論上只要有 Context 就能執行 Servlet 了。簡單的 Tomcat 可以沒有 Engine 和 Host。Context 最重要的功能就是管理它裡面的 Servlet 例項,Servlet 例項在 Context 中是以 Wrapper 出現的,還有一點就是 Context 如何才能找到正確的 Servlet 來執行它呢? Tomcat5 以前是通過一個 Mapper 類來管理的,Tomcat5 以後這個功能被移到了 request 中,在前面的時序圖中就可以發現獲取子容器都是通過 request 來分配的

Wrapper

Wrapper 代表一個 Servlet,它負責管理一個 Servlet,包括的 Servlet 的裝載、初始化、執行以及資源回收。Wrapper 是最底層的容器,它沒有子容器了,所以呼叫它的 addChild 將會報錯。 Wrapper 的實現類是 StandardWrapper,StandardWrapper 還實現了擁有一個 Servlet 初始化資訊的 ServletConfig,由此看出 StandardWrapper 將直接和 Servlet 的各種資訊打交道。

說明:除了上述元件外,Tomcat中還有其他重要的元件,如安全元件security、logger日誌元件、session、mbeans、naming等其他元件。這些元件共同為Connector和Container提供必要的服務。

完整請求過程如下:
http://images2017.cnblogs.com/blog/1131840/201712/1131840-20171215142451699-1297214931.jpg
1.使用者在瀏覽器中輸入網址localhost:8080/test/index.jsp,請求被髮送到本機埠8080,被在那裡監聽的Coyote HTTP/1.1 Connector獲得;

2.Connector把該請求交給它所在的Service的Engine(Container)來處理,並等待Engine的迴應;

3.Engine獲得請求localhost/test/index.jsp,匹配所有的虛擬主機Host;

4.Engine匹配到名為localhost的Host(即使匹配不到也把請求交給該Host處理,因為該Host被定義為該Engine的預設主機),名為localhost的Host獲得請求/test/index.jsp,匹配它所擁有的所有Context。Host匹配到路徑為/test的Context(如果匹配不到就把該請求交給路徑名為“ ”的Context去處理);

5.path=“/test”的Context獲得請求/index.jsp,在它的mapping table中尋找出對應的Servlet。Context匹配到URL Pattern為*.jsp的Servlet,對應於JspServlet類;

6.構造HttpServletRequest物件和HttpServletResponse物件,作為引數呼叫JspServlet的doGet()或doPost(),執行業務邏輯、資料儲存等;

7.Context把執行完之後的HttpServletResponse物件返回給Host;

8.Host把HttpServletResponse物件返回給Engine;

9.Engine把HttpServletResponse物件返回Connector;

10.Connector把HttpServletResponse物件返回給客戶Browser。

7、筆試演算法

7.1、

7.2、

7.3、

7.4、讓您做一個電商平臺,您如何設定一個在買家下訂單後的”第60秒“發簡訊通知賣家發貨,您需要考慮的是 像淘寶一樣的大併發量的訂單。
1、具有排序功能的佇列
2、Redis+定時器
3、佇列,死信
參考地址:https://mp.weixin.qq.com/s/Dzv-i8n7waJVac-N7MJCvA