Java中高階面試題及答案【第三部分】

Java知音發表於2019-03-03

資料庫的三大正規化

1 、第一正規化(1NF)

在任何一個關聯式資料庫中,第一正規化(1NF)是對關係模式的基本要求,不滿足第一正規化(1NF)的資料庫就不是關聯式資料庫。

所謂第一正規化(1NF)是指資料庫表的每一列都是不可分割的基本資料項,同一列中不能有多個值,即實體中的某個屬性不能有多個值或者不能有重複的屬性。如果出現重複的屬性,就可能需要定義一個新的實體,新的實體由重複的屬性構成,新實體與原實體之間為一對多關係。

在第一正規化(1NF)中表的每一行只包含一個例項的資訊。簡而言之,第一正規化要求資料表中的每一列(每個欄位)必須是不可拆分的最小單元。

2、 第二正規化(2NF)

第二正規化(2NF)是在第一正規化(1NF)的基礎上建立起來的,即滿足第二正規化(2NF)必須先滿足第一正規化(1NF)。第二正規化(2NF)要求資料庫表中的每個例項或行必須可以被惟一地區分。為實現區分通常需要為表加上一個列,以儲存各個例項的惟一標識。

第二正規化(2NF)要求實體的屬性完全依賴於主關鍵字。所謂完全依賴是指不能存在僅依賴主關鍵字一部分的屬性,如果存在,那麼這個屬性和主關鍵字的這一部分應該分離出來形成一個新的實體,新實體與原實體之間是一對多的關係。為實現區分通常需要為表加上一個列,以儲存各個例項的惟一標識。簡而言之,第二正規化要求表中的所有列,都必須依賴於主鍵,而不能有任何一列與主鍵沒有關係。

3 、第三正規化(3NF)

滿足第三正規化(3NF)必須先滿足第二正規化(2NF)。第三正規化(3NF)要求一個資料庫表中不包含其它表中已包含的非主關鍵字資訊。簡而言之,第三正規化要求表中的每一列只與主鍵直接相關而不是間接相關,表中的每一列只能依賴於主鍵。

TCP和UDP的區別及其適用場景

首先說一下什麼是TCP和UDP:

TCP是傳輸控制協議,提供的是面向連線、可靠的位元組流服務。

UDP是使用者資料包協議,是一個簡單的面向資料包的運輸層協議。

TCP和UDP的區別:

  • TCP面向連線的運輸層協議,UDP無連線

  • TCP是可靠交付,UDP是盡最大努力交付

  • TCP面向位元組流,UDP面向報文

  • TCP是點對點連線的,UDP一對一,一對多,多對多都可以

  • TCP適合用於網頁,郵件等,UDP適合用於視訊,語音廣播等

TCP和UDP的適用場景:

整個資料要準確無誤的傳遞給對方,這往往用於一些要求可靠的應用,比如HTTP、HTTPS、FTP等傳輸檔案的協議,POP、SMTP等郵件傳輸的協議。

當對網路通訊質量要求不高的時候,要求網路通訊速度能儘量的快,比如視訊、廣播等,這時就可以使用UDP。

說一下Spring的核心模組

  • Spring Core【核心容器】:核心容器提供了Spring的基本功能。核心容器的核心功能是用IOC容器來管理類的依賴關係。

  • Spring AOP【面向切面】:Spring的AOP模組提供了面向切面程式設計的支援。SpringAOP採用的是純Java實現,採用基於代理的AOP實現方案,AOP代理由IOC容器負責生成、管理,依賴關係也一併由IOC容器管理。

  • Spring ORM【物件實體對映】:提供了與多個第三方持久層框架的良好整合。

  • Spring DAO【持久層模組】: Spring進一步簡化DAO開發步驟,能以一致的方式使用資料庫訪問技術,用統一的方式呼叫事務管理,避免具體的實現侵入業務邏輯層的程式碼中。

  • Spring Context【應用上下文】:它是一個配置檔案,為Spring提供上下文資訊,提供了框架式的物件訪問方法。

  • Spring Web【Web模組】:提供了基礎的針對Web開發的整合特性。

  • Spring MVC【MVC模組】:提供了Web應用的MVC實現。Spring的MVC框架並不是僅僅提供一種傳統的實現,它提供了一種清晰的分離模型。

(轉發)forward與(重定向)redirect的區別

  • forward是伺服器請求資源,伺服器直接訪問目標地址的URL,把那個URL的響應內容讀取過來,然後把這些內容再發給瀏覽器。瀏覽器根本不知道伺服器傳送的內容從哪裡來的,所以它的位址列還是原來的地址。

  • redirect是服務端根據邏輯,傳送一個狀態碼,告訴瀏覽器重新去請求那個地址,所以位址列顯示的是新的URL。

  • forward轉發頁面和轉發到的頁面可以共享request裡面的資料。

  • redirect不能共享資料。

  • redirect不僅可以重定向到當前應用程式的其他資源,還可以重定向到同一個站點上的其他應用程式中的資源,甚至是使用絕對URL重定向到其他站點的資源。

  • forward只能在同一個Web應用程式內的資源之間轉發請求。

  • forward是伺服器內部的一種操作。

  • redirect是伺服器通知客戶端,讓客戶端重新發起請求。

  • forward一般用於使用者登陸的時候根據角色轉發到相應的模組。

  • redirect一般用於使用者登出登陸時返回主頁面和跳轉到其它的網站等。

  • forward效率高。

  • redirect效率低。

redis常用的五種資料型別

1.String(字串)

String是簡單的 key-value 鍵值對,value 不僅可以是 String,也可以是數字。它是Redis最基本的資料型別,一個redis中字串value最多可以是512M。

2.Hash(雜湊)

Redis hash 是一個鍵值對集合,對應Value內部實際就是一個HashMap,Hash特別適合用於儲存物件。

3.List(列表)

Redis 列表是簡單的字串列表,按照插入順序排序。你可以新增一個元素導列表的頭部(左邊)或者尾部(右邊)。

底層實現為一個雙向連結串列,即可以支援反向查詢和遍歷,更方便操作,不過帶來了部分額外的記憶體開銷,Redis內部的很多實現,包括髮送緩衝佇列等也都是用的這個資料結構。

4.Set(集合)

Redis的Set是String型別的無序集合,它的內部實現是一個 value永遠為null的HashMap,實際就是通過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的原因。

5.zset(有序集合)

Redis zset 和 set 一樣也是String型別元素的集合,且不允許重複的成員,不同的是每個元素都會關聯一個double型別的分數,用來排序。

多執行緒中sleep()、 wait()、 yield()和 join()的用法與區別

1.sleep()方法

在指定時間內讓當前正在執行的執行緒暫停執行,但不會釋放“鎖標誌”。不推薦使用。

sleep()使當前執行緒進入阻塞狀態,在指定時間內不會執行。

2.wait()方法

在其他執行緒呼叫物件的notify或notifyAll方法前,當前執行緒處於等待狀態。執行緒會釋放掉它所佔有的“鎖標誌”,從而使別的執行緒有機會搶佔該鎖。

當前執行緒必須擁有當前物件鎖。如果當前執行緒不是此鎖的擁有者,會丟擲IllegalMonitorStateException異常。

喚醒當前物件鎖的等待執行緒使用notify或notifyAll方法,也必須擁有相同的物件鎖,否則也會丟擲IllegalMonitorStateException異常。

waite()和notify()必須在synchronized函式或synchronizedblock中進行呼叫。如果在non-synchronized函式和non-synchronizedblock中進行呼叫,雖然能編譯通過,但在執行時會發生IllegalMonitorStateException的異常。

3.yield方法

暫停當前正在執行的執行緒物件。

yield()只是使當前執行緒重新回到可執行狀態,所以執行yield()的執行緒有可能在進入到可執行狀態後馬上又被執行。

yield()只能使同優先順序或更高優先順序的執行緒有執行的機會。

4.join方法

等待該執行緒終止。

等待呼叫join方法的執行緒結束,再繼續執行。如:t.join();它主要用於等待t執行緒執行結束,若無此句,main則會執行完畢,導致結果不可預測。

執行緒和程式的區別?

程式和執行緒都是一個時間段的描述,是CPU工作時間段的描述,不過是顆粒大小不同。

程式是資源的分配和排程的一個獨立單元,而執行緒是CPU排程的基本單元。

同一個程式中可以包括多個執行緒,並且執行緒共享整個程式的資源(暫存器、堆疊、上下文),一個進行至少包括一個執行緒。

程式的建立呼叫fork或者vfork,而執行緒的建立呼叫pthread_create,程式結束後它擁有的所有執行緒都將銷燬,而執行緒的結束不會影響同個程式中的其他執行緒的結束。

執行緒是輕兩級的程式,它的建立和銷燬所需要的時間比程式小很多,所有作業系統中的執行功能都是建立執行緒去完成的。

執行緒中執行時一般都要進行同步和互斥,因為他們共享同一程式的所有資源。

執行緒有自己的私有屬性TCB,執行緒id,暫存器、硬體上下文,而程式也有自己的私有屬性程式控制塊PCB,這些私有屬性是不被共享的,用來標示一個程式或一個執行緒的標誌。

Array和ArrayList有何區別?什麼時候更適合用Array?

儲存內容比較:

Array陣列可以包含基本型別和物件型別,ArrayList只能包含物件型別。

Array陣列存放的一定是同種型別的元素,ArrayList可以存放任意物件(object的子類)。

空間大小比較:

Array的空間大小是固定的,空間不夠時也不能再次申請,所以需要事前確定合適的空間大小。

ArrayList的空間是動態增長的,如果空間不夠,它會建立一個空間比原空間大的新陣列,然後將所有元素複製到新陣列中,接著拋棄舊陣列。而且,每次新增新的元素的時候都會檢查內部陣列的空間是否足夠。(比較麻煩的地方)。

方法上的比較:

ArrayList作為Array的增強版,在方法上比Array更多樣化,比如新增全部addAll()、刪除全部removeAll()、返回迭代器iterator()等。

適用場景:

如果想要儲存一些在整個程式執行期間都會存在而且不變的資料,我們可以將它們放進一個全域性陣列裡;但是如果我們單純只是想要以陣列的形式儲存資料,而不對資料進行增加等操作,只是方便我們進行查詢的話,那麼,我們就選擇ArrayList。

而且還有一個地方是必須知道的,就是如果我們需要對元素進行頻繁的移動或刪除,或者是處理的是超大量的資料,那麼,使用ArrayList就真的不是一個好的選擇,因為它的效率很低,我們可以考慮選擇LinkedList。

Tomcat伺服器優化(記憶體,併發連線數,快取)

  • 記憶體優化:主要是對Tomcat啟動引數進行優化,我們可以在Tomcat啟動指令碼中修改它的最大記憶體數等等。

  • 執行緒數優化:Tomcat的併發連線引數,主要在Tomcat配置檔案中server.xml中配置,比如修改最小空閒連線執行緒數,用於提高系統處理效能等等。

  • 優化快取:開啟壓縮功能,修改引數,比如壓縮的輸出內容大小預設為2KB,可以適當的修改。

手寫一段單例中的懶漢模式和餓漢模式,並且簡單說一下他們的區別

懶漢模式:

public class LazySingleton {
   //懶漢式單例模式
   //在類載入時,不建立例項,因此類載入速度快,但執行時獲取物件的速度慢
   
   
   private static LazySingleton intance = null;
    //靜態私用成員,沒有初始化
   
   private LazySingleton()
   {
       //私有建構函式
   }
   
   public static synchronized LazySingleton getInstance()    
   //靜態,同步,公開訪問點
   {
       if(intance == null)
       {
           intance = new LazySingleton();
       }
       return intance;
   }
}
複製程式碼

餓漢模式:

public class EagerSingleton {
   //餓漢單例模式
   //類載入較慢,但獲取物件的速度快
   
   private static EagerSingleton instance = new EagerSingleton();
 //靜態私有成員,已初始化
   
   private EagerSingleton() 
   {
       //私有建構函式
   }
   
   public static EagerSingleton getInstance()    
   //靜態,不用同步
   {
       return instance;
   }
}
複製程式碼

懶漢模式和餓漢模式的比較:

餓漢模式的特點是載入類時比較慢,但執行時獲取物件的速度比較快,執行緒安全。餓漢式是執行緒安全的,在類建立的同時就已經建立好一個靜態的物件供系統使用,以後不在改變。

懶漢模式的特點是載入類時比較快,但是在執行時獲取物件的速度比較慢,執行緒不安全, 懶漢式如果在建立例項物件時不加上synchronized則會導致物件的訪問不是執行緒安全的。

抽象類和介面的區別,類可以繼承多個類嗎,介面可以繼承多個介面嗎,類可以實現多個介面嗎?

  • 抽象類和介面都不能直接例項化,如果要例項化,抽象類變數必須指向實現所有抽象方法的子類物件,介面變數必須指向實現所有介面方法的類物件。

  • 抽象類要被子類繼承,介面要被類實現。  

  • 介面只能做方法宣告,抽象類中可以做方法宣告,也可以做方法實現

  • 介面裡定義的變數只能是公共的靜態的常量,抽象類中的變數是普通變數。

  • 抽象類裡的抽象方法必須全部被子類所實現,如果子類不能全部實現父類抽象方法,那麼該子類只能是抽象類。同樣,一個實現介面的時候,如不能全部實現介面方法,那麼該 類也只能為抽象類。

  • 抽象方法只能申明,不能實現。abstract void abc();不能寫成abstract void abc(){}。

  • 抽象類裡可以沒有抽象方法 。

  • 如果一個類裡有抽象方法,那麼這個類只能是抽象類 。

  • 抽象方法要被實現,所以不能是靜態的,也不能是私有的。

  • 介面可繼承介面,並可多繼承介面,但類只能單根繼承。

Tomcat,Apache,Jboss的區別?

Tomcat是servlet容器,用於解析jsp,servlet。是一個輕量級的高效的容器;缺點是不支援EJB,只能用於Java應用。

Apache是http伺服器(web伺服器),類似於IIS可以用來建立虛擬站點,編譯處理靜態頁面。支援SSL技術,支援多個虛擬主機等功能。

Jboss是應用伺服器,執行EJB的Javaee應用伺服器,遵循Javaee規範,能夠提供更多平臺的支援和更多整合功能,如資料庫連線,JCA等。其對servlet的支援是通過整合其他servlet容器來實現的。

說出Servlet的生命週期,並說出Servlet和CGI的區別。

Servlet被伺服器例項化後,容器執行其init方法,請求到達時執行其service方法,service方法自動派遣執行與請求對應的doXXX方法(doGet,doPost)等,當伺服器決定將例項銷燬的時候呼叫其destroy()方法。

與CGI的區別在於Servlet處於伺服器程式中,它通過多執行緒方式執行其service方法,一個例項可以服務於多個請求,並且其例項一般不會銷燬,而CGI對每個請求都產生新的程式,服務完成後就銷燬,所以效率上低於Servlet。

談談你對MVC的理解

MVC是Model—View—Controler的簡稱。即模型—檢視—控制器。MVC是一種設計模式,它強制性的把應用程式的輸入、處理和輸出分開。

MVC中的模型、檢視、控制器它們分別擔負著不同的任務。

檢視: 檢視是使用者看到並與之互動的介面。檢視向使用者顯示相關的資料,並接受使用者的輸入。檢視不進行任何業務邏輯處理。

模型: 模型表示業務資料和業務處理,相當於JavaBean。一個模型能為多個檢視提供資料。這提高了應用程式的重用性。

控制器: 當使用者單擊Web頁面中的提交按鈕時,控制器接受請求並呼叫相應的模型去處理請求,然後根據處理的結果呼叫相應的檢視來顯示處理的結果。

MVC的處理過程:首先控制器接受使用者的請求,呼叫相應的模型來進行業務處理,並返回資料給控制器。控制器呼叫相應的檢視來顯示處理的結果。並通過檢視呈現給使用者。

如何避免瀏覽器快取?

  • HTTP資訊頭中包含Cache-Control:no-cache,pragma:no-cache,或Cache-Control:max-age=0等設定瀏覽器不用快取請求。

  • 需要根據Cookie,認證資訊等決定輸入內容的動態請求是不能被快取的。

  • 經過HTTPS安全加密的請求不能被快取。

  • HTTP響應頭中不包含Last-Modified/Etag,也不包含Cache-Control/Expires的請求無法被快取 。

如何防止快取雪崩?

原因:

快取雪崩可能是因為資料未載入到快取中,或者快取同一時間大面積的失效,從而導致所有請求都去查資料庫,導致資料庫CPU和記憶體負載過高,甚至當機。

對應解決:

  • 採用加鎖計數,或者使用合理的佇列數量來避免快取失效時對資料庫造成太大的壓力。這種辦法雖然能緩解資料庫的壓力,但是同時又降低了系統的吞吐量。

  • 分析使用者行為,儘量讓失效時間點均勻分佈。避免快取雪崩的出現。

  • 如果是因為某臺快取伺服器當機,可以考慮做主備,比如:redis主備,但是雙快取涉及到更新事務的問題,update可能讀到髒資料,需要好好解決。

資料庫會死鎖嗎,舉一個死鎖的例子,mysql 怎麼解決死鎖。

產生死鎖的原因主要是:

  • 系統資源不足。

  • 程式執行推進的順序不合適。

  • 資源分配不當等。

如果系統資源充足,程式的資源請求都能夠得到滿足,死鎖出現的可能性就很低,否則就會因爭奪有限的資源而陷入死鎖。其次,程式執行推進順序與速度不同,也可能產生死鎖。

產生死鎖的四個必要條件:

  • 互斥條件:一個資源每次只能被一個程式使用。

  • 請求與保持條件:一個程式因請求資源而阻塞時,對已獲得的資源保持不放。

  • 不剝奪條件:程式已獲得的資源,在末使用完之前,不能強行剝奪。

  • 迴圈等待條件:若干程式之間形成一種頭尾相接的迴圈等待資源關係。

這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。

解決資料庫死鎖的方法:

  • 重啟資料庫。

  • 殺掉搶資源的程式。

Dubbo原始碼使用了哪些設計模式?

工廠模式:

ExtenstionLoader.getExtenstionLoader(Protocol.class).getAdaptiveExtenstion()

裝飾器模式+責任鏈:

以provider的呼叫鏈為例,具體呼叫鏈程式碼是在protocolFilterWrapper的buildInvokeChain完成的,將註解中含有group=provider的Filter實現。

呼叫順序:

  1. EchoFilter

  2. ClassLoaderFilter

  3. GenericFilter

  4. ContextFilter

  5. ExceptionFilter

  6. TimeoutFilter

  7. MonitorFilter

  8. TraceFilter

裝飾器模式和責任鏈混合使用,Echo是回聲測試請求,ClassLoaderFilter則只是在其主功能上新增了功能。

觀察者模式:

provider啟動時需要與註冊中心互動,先註冊自己的服務,再訂閱自己的服務,訂閱時採用了觀察者模式,註冊中心每5s定時檢查是否有服務更新,有更新則向服務提供者傳送1個notify訊息後即可執行NotifyListener的notity方法,執行監聽器方法。

動態代理模式:

擴充套件JDK的ExtensionLoaderdeAdaptive實現,根據呼叫階段動態引數決定呼叫哪個類,生成代理類的程式碼是ExtensionLoader的createAdaptiveExtenstionClassLoader方法。

最後,歡迎關注微信公眾號“Java知音”,回覆面試題,送你一個大驚喜!

相關文章