2019年Java面試題基礎系列228道前端
第一篇更新1~20題的答案解析java
2019年Java面試題基礎系列228道(1),快看看哪些你還不會?程序員
第二篇更新21~50題答案解析面試
2019年Java面試題基礎系列228道(2),查漏補缺!
正則表達式
本次更新Java 面試題(一)的51~95題答案算法
5一、類 ExampleA 繼承 Exception,類 ExampleB 繼承ExampleA。數據庫
有以下代碼片段:編程
try { throw new ExampleB("b")}catch(ExampleA e){ System.out.println("ExampleA");}catch(Exception e){ System.out.println("Exception");}
**請問執行此段代碼的輸出是什麼?設計模式
答:數組
輸出:ExampleA。(根據里氏代換原則[能使用父類型的地方必定能使用子類型],抓取 ExampleA 類型異常的 catch 塊可以抓住 try 塊中拋出的 ExampleB 類型的異常)
面試題 - 說出下面代碼的運行結果。(此題的出處是《Java 編程思想》一書)
class Annoyance extends Exception {}class Sneeze extends Annoyance {}class Human { public static void main(String[] args) throws Exception { try { try { throw new Sneeze(); } catch ( Annoyance a ) { System.out.println("Caught Annoyance"); throw a; } } catch ( Sneeze s ) { System.out.println("Caught Sneeze"); return ; } finally { System.out.println("Hello World!"); } }}
5二、List、Set、Map 是否繼承自 Collection 接口?
List、Set 是 ,Map 不是。Map 是鍵值對映射容器,與 List 和 Set 有明顯的區別,而 Set 存儲的零散的元素且不容許有重複元素(數學中的集合也是如此),List是線性結構的容器,適用於按數值索引訪問元素的情形。
5三、闡述 ArrayList、Vector、LinkedList 的存儲性能和特性。
ArrayList 和 Vector 都是使用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增長和插入元素,它們都容許直接按序號索引元素,可是插入元素要涉及數組元素移動等內存操做,因此索引數據快而插入數據慢,Vector 中的方法因爲添加了 synchronized 修飾,所以 Vector 是線程安全的容器,但性能上較ArrayList 差,所以已是 Java 中的遺留容器。LinkedList 使用雙向鏈表實現存儲(將內存中零散的內存單元經過附加的引用關聯起來,造成一個能夠按序號索引的線性結構,這種鏈式存儲方式與數組的連續存儲方式相比,內存的利用率更高),按序號索引數據須要進行前向或後向遍歷,可是插入數據時只須要記錄本項的先後項便可,因此插入速度較快。Vector 屬於遺留容器(Java 早期的版本中提供的容器,除此以外,Hashtable、Dictionary、BitSet、Stack、Properties都是遺留容器),已經不推薦使用,可是因爲 ArrayList 和 LinkedListed 都是非線程安全的,若是遇到多個線程操做同一個容器的場景,則能夠經過工具類Collections 中的 synchronizedList 方法將其轉換成線程安全的容器後再使用(這是對裝潢模式的應用,將已有對象傳入另外一個類的構造器中建立新的對象來加強實現)。
補充:遺留容器中的 Properties 類和 Stack 類在設計上有嚴重的問題,Properties是一個鍵和值都是字符串的特殊的鍵值對映射,在設計上應該是關聯一個Hashtable 並將其兩個泛型參數設置爲 String 類型,可是 Java API 中的Properties 直接繼承了 Hashtable,這很明顯是對繼承的濫用。這裏複用代碼的方式應該是 Has-A 關係而不是 Is-A 關係,另外一方面容器都屬於工具類,繼承工具類自己就是一個錯誤的作法,使用工具類最好的方式是 Has-A 關係(關聯)或Use-A 關係(依賴)。同理,Stack 類繼承 Vector 也是不正確的。Sun 公司的工程師們也會犯這種低級錯誤,讓人唏噓不已。
5四、Collection 和 Collections 的區別?
Collection 是一個接口,它是 Set、List 等容器的父接口;Collections 是個一個工具類,提供了一系列的靜態方法來輔助容器操做,這些方法包括對容器的搜索、排序、線程安全化等等。
5五、List、Map、Set 三個接口存取元素時,各有什麼特色?
List 以特定索引來存取元素,能夠有重複元素。Set 不能存放重複元素(用對象的equals()方法來區分元素是否重複)。Map 保存鍵值對(key-value pair)映射,映射關係能夠是一對一或多對一。Set 和 Map 容器都有基於哈希存儲和排序樹的兩種實現版本,基於哈希存儲的版本理論存取時間複雜度爲 O(1),而基於排序樹版本的實如今插入或刪除元素時會按照元素或元素的鍵(key)構成排序樹從而達到排序和去重的效果。
5六、TreeMap 和 TreeSet 在排序時如何比較元素?Collections 工具類中的 sort()方法如何比較元素?
TreeSet 要求存放的對象所屬的類必須實現 Comparable 接口,該接口提供了比較元素的 compareTo()方法,當插入元素時會回調該方法比較元素的大小。TreeMap 要求存放的鍵值對映射的鍵必須實現 Comparable 接口從而根據鍵對元素進 行排 序。Collections 工具類的 sort 方法有兩種重載的形式,第一種要求傳入的待排序容器中存放的對象比較實現 Comparable 接口以實現元素的比較;第二種不強制性的要求容器中的元素必須可比較,可是要求傳入第二個參數,參數是Comparator 接口的子類型(須要重寫 compare 方法實現元素的比較),至關於一個臨時定義的排序規則,其實就是經過接口注入比較元素大小的算法,也是對回調模式的應用(Java 中對函數式編程的支持)。
5七、Thread 類的 sleep()方法和對象的 wait()方法均可以讓線程暫停執行,它們有什麼區別?
sleep()方法(休眠)是線程類(Thread)的靜態方法,調用此方法會讓當前線程暫停執行指定的時間,將執行機會(CPU)讓給其餘線程,可是對象的鎖依然保持,所以休眠時間結束後會自動恢復(線程回到就緒狀態,請參考第 66 題中的線程狀態轉換圖)。wait()是 Object 類的方法,調用對象的 wait()方法致使當前線程放棄對象的鎖(線程暫停執行),進入對象的等待池(wait pool),只有調用對象的 notify()方法(或 notifyAll()方法)時才能喚醒等待池中的線程進入等鎖池(lock pool),若是線程從新得到對象的鎖就能夠進入就緒狀態。
補充:可能很多人對什麼是進程,什麼是線程還比較模糊,對於爲何須要多線程編程也不是特別理解。簡單的說:進程是具備必定獨立功能的程序關於某個數據集合上的一次運行活動,是操做系統進行資源分配和調度的一個獨立單位;線程是進程的一個實體,是 CPU 調度和分派的基本單位,是比進程更小的能獨立運行的基本單位。線程的劃分尺度小於進程,這使得多線程程序的併發性高;進程在執行時一般擁有獨立的內存單元,而線程之間能夠共享內存。使用多線程的編程一般可以帶來更好的性能和用戶體驗,可是多線程的程序對於其餘程序是不友好的,由於它可能佔用了更多的 CPU 資源。固然,也不是線程越多,程序的性能就越好,由於線程之間的調度和切換也會浪費 CPU 時間。時下很時髦的 Node.js就採用了單線程異步 I/O 的工做模式。
5八、線程的 sleep()方法和 yield()方法有什麼區別?
(1) sleep()方法給其餘線程運行機會時不考慮線程的優先級,所以會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;
(2) 線程執行 sleep()方法後轉入阻塞(blocked)狀態,而執行 yield()方法後轉入就緒(ready)狀態;
(3)sleep()方法聲明拋出 InterruptedException,而 yield()方法沒有聲明任何異常;
(4)sleep()方法比 yield()方法(跟操做系統 CPU 調度相關)具備更好的可移植性。
5九、當一個線程進入一個對象的 synchronized 方法 A 以後,其它線程是否可進入此對象的 synchronized 方法 B?
不能。其它線程只能訪問該對象的非同步方法,同步方法則不能進入。由於非靜態方法上的 synchronized 修飾符要求執行方法時要得到對象的鎖,若是已經進入A 方法說明對象鎖已經被取走,那麼試圖進入 B 方法的線程就只能在等鎖池(注意不是等待池哦)中等待對象的鎖。
60、請說出與線程同步以及線程調度相關的方法。
(1) wait():使一個線程處於等待(阻塞)狀態,而且釋放所持有的對象的鎖;
(2)sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理 InterruptedException 異常;
(3)notify():喚醒一個處於等待狀態的線程,固然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由 JVM 肯定喚醒哪一個線程,並且與優先級無關;
(4)notityAll():喚醒全部處於等待狀態的線程,該方法並非將對象的鎖給全部線程,而是讓它們競爭,只有得到鎖的線程才能進入就緒狀態;
補充:Java 5 經過 Lock 接口提供了顯式的鎖機制(explicit lock),加強了靈活性以及對線程的協調。Lock 接口中定義了加鎖(lock())和解鎖(unlock())的方法,同時還提供了 newCondition()方法來產生用於線程之間通訊的 Condition 對象;此外,Java 5 還提供了信號量機制(semaphore),信號量能夠用來限制對某個共享資源進行訪問的線程的數量。在對資源進行訪問以前,線程必須獲得信號量的許可(調用 Semaphore 對象的 acquire()方法);在完成對資源的訪問後,線程必須向信號量歸還許可(調用 Semaphore 對象的 release()方法)。
6一、編寫多線程程序有幾種實現方式?
Java 5 之前實現多線程有兩種實現方法:一種是繼承 Thread 類;另外一種是實現Runnable 接口。兩種方式都要經過重寫 run()方法來定義線程的行爲,推薦使用後者,由於 Java 中的繼承是單繼承,一個類有一個父類,若是繼承了 Thread 類就沒法再繼承其餘類了,顯然使用 Runnable 接口更爲靈活。
補充:Java 5 之後建立線程還有第三種方式:實現 Callable 接口,該接口中的 call方法能夠在線程執行結束時產生一個返回值。
6二、synchronized 關鍵字的用法?
synchronized 關鍵字能夠將對象或者方法標記爲同步,以實現對對象和方法的互斥訪問,能夠用 synchronized(對象) { … }定義同步代碼塊,或者在聲明方法時將 synchronized 做爲方法的修飾符。在第 60 題的例子中已經展現了synchronized 關鍵字的用法。
6三、舉例說明同步和異步。
若是系統中存在臨界資源(資源數量少於競爭資源的線程數量的資源),例如正在寫的數據之後可能被另外一個線程讀到,或者正在讀的數據可能已經被另外一個線程寫過了,那麼這些數據就必須進行同步存取(數據庫操做中的排他鎖就是最好的例子)。當應用程序在對象上調用了一個須要花費很長時間來執行的方法,而且不但願讓程序等待方法的返回時,就應該使用異步編程,在不少狀況下采用異步途徑每每更有效率。事實上,所謂的同步就是指阻塞式操做,而異步就是非阻塞式操做。
6四、啓動一個線程是調用 run()仍是 start()方法?
啓動一個線程是調用 start()方法,使線程所表明的虛擬處理機處於可運行狀態,這意味着它能夠由 JVM 調度並執行,這並不意味着線程就會當即運行。run()方法是線程啓動後要進行回調(callback)的方法。歡迎你們關注個人公種浩【程序員追風】,2019年多家公司java面試題整理了1000多道400多頁pdf文檔,文章都會在裏面更新,整理的資料也會放在裏面。
6五、什麼是線程池(thread pool)?
在面向對象編程中,建立和銷燬對象是很費時間的,由於建立一個對象要獲取內存資源或者其它更多資源。在 Java 中更是如此,虛擬機將試圖跟蹤每個對象,以便可以在對象銷燬後進行垃圾回收。因此提升服務程序效率的一個手段就是儘量減小建立和銷燬對象的次數,特別是一些很耗資源的對象建立和銷燬,這就是」池化資源」技術產生的緣由。線程池顧名思義就是事先建立若干個可執行的線程放入一個池(容器)中,須要的時候從池中獲取線程不用自行建立,使用完畢不須要銷燬線程而是放回池中,從而減小建立和銷燬線程對象的開銷。Java 5+中的 Executor 接口定義一個執行線程的工具。它的子類型即線程池接口是 ExecutorService。要配置一個線程池是比較複雜的,尤爲是對於線程池的原理不是很清楚的狀況下,所以在工具類 Executors 面提供了一些靜態工廠方法,生成一些經常使用的線程池,以下所示:
(1)newSingleThreadExecutor:建立一個單線程的線程池。這個線程池只有一個線程在工做,也就是至關於單線程串行執行全部任務。若是這個惟一的線程由於異常結束,那麼會有一個新的線程來替代它。此線程池保證全部任務的執行順序按照任務的提交順序執行。
(2)newFixedThreadPool:建立固定大小的線程池。每次提交一個任務就建立一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,若是某個線程由於執行異常而結束,那麼線程池會補充一個新線程。
(3) newCachedThreadPool:建立一個可緩存的線程池。若是線程池的大小超過了處理任務所須要的線程,那麼就會回收部分空閒(60 秒不執行任務)的線程,當任務數增長時,此線程池又能夠智能的添加新線程來處理任務。此線程池不會對線程池大小作限制,線程池大小徹底依賴於操做系統(或者說 JVM)可以建立的最大線程大小。
(4)newScheduledThreadPool:建立一個大小無限的線程池。此線程池支持定時以及週期性執行任務的需求。
(5)newSingleThreadExecutor:建立一個單線程的線程池。此線程池支持定時以及週期性執行任務的需求。
第 60 題的例子中演示了經過 Executors 工具類建立線程池並使用線程池執行線程的代碼。若是但願在服務器上使用線程池,強烈建議使用 newFixedThreadPool方法來建立線程池,這樣能得到更好的性能。
6六、線程的基本狀態以及狀態之間的關係?
說明:其中 Running 表示運行狀態,Runnable 表示就緒狀態(萬事俱備,只欠CPU),Blocked 表示阻塞狀態,阻塞狀態又有多種狀況,多是由於調用 wait()方法進入等待池,也多是執行同步方法或同步代碼塊進入等鎖池,或者是調用了 sleep()方法或 join()方法等待休眠或其餘線程結束,或是由於發生了 I/O 中斷。
6七、簡述 synchronized 和 java.util.concurrent.locks.Lock的異同?
Lock 是 Java 5 之後引入的新的 API,和關鍵字 synchronized 相比主要相同點:Lock 能完成 synchronized 所實現的全部功能;主要不一樣點:Lock 有比synchronized 更精確的線程語義和更好的性能,並且不強制性的要求必定要得到鎖。synchronized 會自動釋放鎖,而 Lock 必定要求程序員手工釋放,而且最好在 finally 塊中釋放(這是釋放外部資源的最好的地方)。
6八、Java 中如何實現序列化,有什麼意義?
序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化。能夠對流化後的對象進行讀寫操做,也可將流化後的對象傳輸於網絡之間。序列化是爲了解決對象流讀寫操做時可能引起的問題(若是不進行序列化可能會存在數據亂序的問題)。要實現序列化,須要讓一個類實現 Serializable 接口,該接口是一個標識性接口,標註該類對象是可被序列化的,而後使用一個輸出流來構造一個對象輸出流並經過 writeObject(Object)方法就能夠將實現對象寫出(即保存其狀態);若是須要反序列化則能夠用一個輸入流創建對象輸入流,而後經過 readObject 方法從流中讀取對象。序列化除了可以實現對象的持久化以外,還可以用於對象的深度克隆(能夠參考第 29 題)。
6九、Java 中有幾種類型的流?
字節流和字符流。字節流繼承於 InputStream、OutputStream,字符流繼承於Reader、Writer。在 java.io 包中還有許多其餘的流,主要是爲了提升性能和使用方便。關於 Java 的 I/O 須要注意的有兩點:一是兩種對稱性(輸入和輸出的對稱性,字節和字符的對稱性);二是兩種設計模式(適配器模式和裝潢模式)。另外 Java 中的流不一樣於 C#的是它只有一個維度一個方向。
70、寫一個方法,輸入一個文件名和一個字符串,統計這個字符串在這個文件中出現的次數。
代碼以下:
import java.io.BufferedReader;import java.io.FileReader;public final class MyUtil { // 工具類中的方法都是靜態方式訪問的所以將構造器私有不容許建立對象 (絕對好習慣) private MyUtil() { throw new AssertionError(); } /** * 統計給定文件中給定字符串的出現次數 * * @param filename 文件名 * @param word 字符串 * @return 字符串在文件中出現的次數 */ public static int countWordInFile(String filename, String word) { int counter = 0; try (FileReader fr = new FileReader(filename)) { try (BufferedReader br = new BufferedReader(fr)) { String line = null; while ((line = br.readLine()) != null) { int index = -1; while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) { counter++; line = line.substring(index + word.length()); } } } } catch (Exception ex) { ex.printStackTrace(); } return counter; }}
7一、如何用 Java 代碼列出一個目錄下全部的文件?
若是隻要求列出當前文件夾下的文件,代碼以下所示:
import java.io.File;class Test12 { public static void main(String[] args) { File f = new File("/Users/Hao/Downloads"); for (File temp : f.listFiles()) { if(temp.isFile()) { System.out.println(temp.getName()); } } }}
若是須要對文件夾繼續展開,代碼以下所示:
import java.io.File;class Test12 { public static void main(String[] args) { showDirectory(new File("/Users/Hao/Downloads")); } public static void showDirectory(File f) { _walkDirectory(f, 0); } private static void _walkDirectory(File f, int level) { if(f.isDirectory()) { for (File temp : f.listFiles()) { _walkDirectory(temp, level + 1); } } else { for (int i = 0; i < level - 1; i++) { System.out.print("t");}System.out.println(f.getName());}}}
在 Java 7 中可使用 NIO.2 的 API 來作一樣的事情,代碼以下所示:
class ShowFileTest { public static void main(String[] args) throws IOException { Path initPath = Paths.get("/Users/Hao/Downloads"); Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { System.out.println(file.getFileName().toString()); return FileVisitResult.CONTINUE; } } ); }}
7二、用 Java 的套接字編程實現一個多線程的回顯(echo)服務器。
7三、XML 文檔定義有幾種形式?它們之間有何本質區別?解析XML 文檔有哪幾種方式?
XML 文檔定義分爲 DTD 和 Schema 兩種形式,兩者都是對 XML 語法的約束,其本質區別在於 Schema 自己也是一個 XML 文件,能夠被 XML 解析器解析,並且能夠爲 XML 承載的數據定義類型,約束能力較之 DTD 更強大。對 XML 的解析主要有 DOM(文檔對象模型,Document Object Model)、SAX(Simple API forXML)和 StAX(Java 6 中引入的新的解析 XML 的方式,Streaming API for XML),其中 DOM 處理大型文件時其性能降低的很是厲害,這個問題是由 DOM 樹結構佔用的內存較多形成的,並且 DOM 解析方式必須在解析文件以前把整個文檔裝入內存,適合對 XML 的隨機訪問(典型的用空間換取時間的策略);SAX 是事件驅動型的 XML 解析方式,它順序讀取 XML 文件,不須要一次所有裝載整個文件。當遇到像文件開頭,文檔結束,或者標籤開頭與標籤結束時,它會觸發一個事件,用戶經過事件回調代碼來處理 XML 文件,適合對 XML 的順序訪問;顧名思義,StAX 把重點放在流上,實際上 StAX 與其餘解析方式的本質區別就在於應用程序可以把 XML 做爲一個事件流來處理。將 XML 做爲一組事件來處理的想法並不新穎( SAX 就是這樣作的),但不一樣之處在於 StAX 容許應用程序代碼把這些事件逐個拉出來,而不用提供在解析器方便時從解析器中接收事件的處理程序。
7四、你在項目中哪些地方用到了 XML?
XML 的主要做用有兩個方面:數據交換和信息配置。在作數據交換時,XML 將數據用標籤組裝成起來,而後壓縮打包加密後經過網絡傳送給接收者,接收解密與解壓縮後再從 XML 文件中還原相關信息進行處理,XML 曾經是異構系統間交換數據的事實標準,但此項功能幾乎已經被被JSON(JavaScript Object Notation)取而代之。固然,目前不少軟件仍然使用 XML 來存儲配置信息,咱們在不少項目中一般也會將做爲配置信息的硬代碼寫在 XML 文件中,Java 的不少框架也是這麼作的,並且這些框架都選擇了 dom4j 做爲處理 XML 的工具,由於 Sun 公司的官方API 實在不怎麼好用。
補充:如今有不少時髦的軟件(如 Sublime)已經開始將配置文件書寫成 JSON格式,咱們已經強烈的感覺到 XML 的另外一項功能也將逐漸被業界拋棄。
7五、闡述 JDBC 操做數據庫的步驟。
下面的代碼以鏈接本機的 Oracle 數據庫爲例,演示 JDBC 操做數據庫的步驟。
(1) 加載驅動。
Class.forName("oracle.jdbc.driver.OracleDriver");
(2) 建立鏈接。
Connection con =DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl","scott", "tiger");
(3) 建立語句。
PreparedStatement ps = con.prepareStatement("select * from emp where sal between ? and ?");ps.setint(1, 1000);ps.setint(2, 3000);
(4)執行語句。
ResultSet rs = ps.executeQuery();
(5)處理結果。
while(rs.next()) { System.out.println(rs.getint("empno") + " - " + rs.getString("ename"));}
(6) 關閉資源。
finally { if(con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } }}
提示:關閉外部資源的順序應該和打開的順序相反,也就是說先關閉 ResultSet、再關閉 Statement、在關閉 Connection。上面的代碼只關閉了 Connection(鏈接),雖然一般狀況下在關閉鏈接時,鏈接上建立的語句和打開的遊標也會關閉,但不能保證老是如此,所以應該按照剛纔說的順序分別關閉。此外,第一步加載驅動在 JDBC 4.0 中是能夠省略的(自動從類路徑中加載驅動),可是咱們建議保留。
7六、Statement 和 PreparedStatement 有什麼區別?哪一個性能更好?
與 Statement 相比,①PreparedStatement 接口表明預編譯的語句,它主要的優點在於能夠減小 SQL 的編譯錯誤並增長 SQL 的安全性(減小 SQL 注射的可能性);②PreparedStatement 中的 SQL 語句是能夠帶參數的,避免了用字符串鏈接拼接 SQL 語句的麻煩和不安全;③當批量處理 SQL 或頻繁執行相同的查詢時,PreparedStatement 有明顯的性能上的優點,因爲數據庫能夠將編譯優化後的SQL 語句緩存起來,下次執行相同結構的語句時就會很快(不用再次編譯和生成執行計劃)。
補充:爲了提供對存儲過程的調用,JDBC API 中還提供了 CallableStatement 接口。存儲過程(Stored Procedure)是數據庫中一組爲了完成特定功能的 SQL 語句的集合,經編譯後存儲在數據庫中,用戶經過指定存儲過程的名字並給出參數(若是該存儲過程帶有參數)來執行它。雖然調用存儲過程會在網絡開銷、安全性、性能上得到不少好處,可是存在若是底層數據庫發生遷移時就會有不少麻煩,由於每種數據庫的存儲過程在書寫上存在很多的差異。
7七、使用 JDBC 操做數據庫時,如何提高讀取數據的性能?如何提高更新數據的性能?
要提高讀取數據的性能,能夠指定經過結果集(ResultSet)對象的 setFetchSize()方法指定每次抓取的記錄數(典型的空間換時間策略);要提高更新數據的性能可使用 PreparedStatement 語句構建批處理,將若干 SQL 語句置於一個批處理中執行。
7八、在進行數據庫編程時,鏈接池有什麼做用?
因爲建立鏈接和釋放鏈接都有很大的開銷(尤爲是數據庫服務器不在本地時,每次創建鏈接都須要進行 TCP 的三次握手,釋放鏈接須要進行 TCP 四次握手,形成的開銷是不可忽視的),爲了提高系統訪問數據庫的性能,能夠事先建立若干鏈接置於鏈接池中,須要時直接從鏈接池獲取,使用結束時歸還鏈接池而沒必要關閉鏈接,從而避免頻繁建立和釋放鏈接所形成的開銷,這是典型的用空間換取時間的策略(浪費了空間存儲鏈接,但節省了建立和釋放鏈接的時間)。池化技術在Java 開發中是很常見的,在使用線程時建立線程池的道理與此相同。基於 Java 的開源數據庫鏈接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid 等。
補充:在計算機系統中時間和空間是不可調和的矛盾,理解這一點對設計知足性能要求的算法是相當重要的。大型網站性能優化的一個關鍵就是使用緩存,而緩存跟上面講的鏈接池道理很是相似,也是使用空間換時間的策略。能夠將熱點數據置於緩存中,當用戶查詢這些數據時能夠直接從緩存中獲得,這不管如何也快過去數據庫中查詢。固然,緩存的置換策略等也會對系統性能產生重要影響,對於這個問題的討論已經超出了這裏要闡述的範圍。
7九、什麼是 DAO 模式?
DAO(Data Access Object)顧名思義是一個爲數據庫或其餘持久化機制提供了抽象接口的對象,在不暴露底層持久化方案實現細節的前提下提供了各類數據訪問操做。在實際的開發中,應該將全部對數據源的訪問操做進行抽象化後封裝在一個公共 API 中。用程序設計語言來講,就是創建一個接口,接口中定義了此應用程序中將會用到的全部事務方法。在這個應用程序中,當須要和數據源進行交互的時候則使用這個接口,而且編寫一個單獨的類來實現這個接口,在邏輯上該類對應一個特定的數據存儲。DAO 模式實際上包含了兩個模式,一是 DataAccessor(數據訪問器),二是 Data Object(數據對象),前者要解決如何訪問數據的問題,然後者要解決的是如何用對象封裝數據。
80、事務的 ACID 是指什麼?
(1)原子性(Atomic):事務中各項操做,要麼全作要麼全不作,任何一項操做的失敗都會致使整個事務的失敗;
(2)一致性(Consistent):事務結束後系統狀態是一致的;
(3)隔離性(Isolated):併發執行的事務彼此沒法看到對方的中間狀態;
(4)持久性(Durable):事務完成後所作的改動都會被持久化,即便發生災難性的失敗。經過日誌和同步備份能夠在故障發生後重建數據。
補充:關於事務,在面試中被問到的機率是很高的,能夠問的問題也是不少的。首先須要知道的是,只有存在併發數據訪問時才須要事務。當多個事務訪問同一數據時,可能會存在 5 類問題,包括 3 類數據讀取問題(髒讀、不可重複讀和幻讀)和 2 類數據更新問題(第 1 類丟失更新和第 2 類丟失更新)。
髒讀(Dirty Read):A 事務讀取 B 事務還沒有提交的數據並在此基礎上操做,而 B事務執行回滾,那麼 A 讀取到的數據就是髒數據。
不可重複讀(Unrepeatable Read):事務 A 從新讀取前面讀取過的數據,發現該數據已經被另外一個已提交的事務 B 修改過了。
幻讀(Phantom Read):事務 A 從新執行一個查詢,返回一系列符合查詢條件的行,發現其中插入了被事務 B 提交的行。
第 1 類丟失更新:事務 A 撤銷時,把已經提交的事務 B 的更新數據覆蓋了。
第 2 類丟失更新:事務 A 覆蓋事務 B 已經提交的數據,形成事務 B 所作的操做丟失。
數據併發訪問所產生的問題,在有些場景下多是容許的,可是有些場景下可能就是致命的,數據庫一般會經過鎖機制來解決數據併發訪問問題,按鎖定對象不一樣能夠分爲表級鎖和行級鎖;按併發事務鎖定關係能夠分爲共享鎖和獨佔鎖,具體的內容你們能夠自行查閱資料進行了解。直接使用鎖是很是麻煩的,爲此數據庫爲用戶提供了自動鎖機制,只要用戶指定會話的事務隔離級別,數據庫就會經過分析 SQL 語句而後爲事務訪問的資源加上合適的鎖,此外,數據庫還會維護這些鎖經過各類手段提升系統的性能,這些對用戶來講都是透明的(就是說你不用理解,事實上我確實也不知道)。ANSI/ISOSQL 92 標準定義了 4 個等級的事務隔離級別,以下表所示:
須要說明的是,事務隔離級別和數據訪問的併發性是對立的,事務隔離級別越高併發性就越差。因此要根據具體的應用來肯定合適的事務隔離級別,這個地方沒有萬能的原則。
8一、JDBC 中如何進行事務處理?
Connection 提供了事務處理的方法,經過調用 setAutoCommit(false)能夠設置手動提交事務;當事務完成後用 commit()顯式提交事務;若是在事務處理過程當中發生異常則經過 rollback()進行事務回滾。除此以外,從 JDBC 3.0 中還引入了Savepoint(保存點)的概念,容許經過代碼設置保存點並讓事務回滾到指定的保存點。
8二、JDBC 可否處理 Blob 和 Clob?
Blob 是指二進制大對象(Binary Large Object),而 Clob 是指大字符對象(Character Large Objec),所以其中 Blob 是爲存儲大的二進制數據而設計的,而 Clob 是爲存儲大的文本數據而設計的。JDBC 的 PreparedStatement 和ResultSet 都提供了相應的方法來支持 Blob 和 Clob 操做。
8三、簡述正則表達式及其用途。
在編寫處理字符串的程序時,常常會有查找符合某些複雜規則的字符串的須要。正則表達式就是用於描述這些規則的工具。換句話說,正則表達式就是記錄文本規則的代碼。
說明:計算機誕生初期處理的信息幾乎都是數值,可是時過境遷,今天咱們使用計算機處理的信息更多的時候不是數值而是字符串,正則表達式就是在進行字符串匹配和處理的時候最爲強大的工具,絕大多數語言都提供了對正則表達式的支持。
8四、Java 中是如何支持正則表達式操做的?
Java 中的 String 類提供了支持正則表達式操做的方法,包括:matches()、replaceAll()、replaceFirst()、split()。此外,Java 中能夠用 Pattern 類表示正則表達式對象,它提供了豐富的 API 進行各類正則表達式操做。
面試題: - 若是要從字符串中截取第一個英文左括號以前的字符串,例如:北京市(朝陽區)(西城區)(海淀區),截取結果爲:北京市,那麼正則表達式怎麼寫?
import java.util.regex.Matcher;import java.util.regex.Pattern;class RegExpTest { public static void main(String[] args) { String str = "北京市(朝陽區)(西城區)(海淀區)"; Pattern p = Pattern.compile(".*?(?=\()"); Matcher m = p.matcher(str); if(m.find()) { System.out.println(m.group()); } }}
8五、得到一個類的類對象有哪些方式?
(1)方法 1:類型.class,例如:String.class
(2)方法 2:對象.getClass(),例如:」hello」.getClass()
(3)方法 3:Class.forName(),例如:Class.forName(「java.lang.String」)
8六、如何經過反射建立對象?
方法 1:經過類對象調用 newInstance()方法,例如:String.class.newInstance()
方法 2:經過類對象的 getConstructor()或 getDeclaredConstructor()方法得到構造器(Constructor)對象並調用其 newInstance()方法建立對象,例如:String.class.getConstructor(String.class).newInstance(「Hello」);
8七、如何經過反射獲取和設置對象私有字段的值?
能夠經過類對象的 getDeclaredField()方法字段(Field)對象,而後再經過字段對象的 setAccessible(true)將其設置爲能夠訪問,接下來就能夠經過 get/set 方法來獲取/設置字段的值了。下面的代碼實現了一個反射的工具類,其中的兩個靜態方法分別用於獲取和設置私有字段的值,字段能夠是基本類型也能夠是對象類型且支持多級對象操做。
8八、如何經過反射調用對象的方法?
請看下面的代碼:
import java.lang.reflect.Method;class MethodInvokeTest { public static void main(String[] args) throws Exception { String str = "hello"; Method m = str.getClass().getMethod("toUpperCase"); System.out.println(m.invoke(str)); // HELLO }}
8九、簡述一下面向對象的」六原則一法則」。
(1)單一職責原則:一個類只作它該作的事情。(單一職責原則想表達的就是」高內聚」,寫代碼最終極的原則只有六個字」高內聚、低耦合」,就如同葵花寶典或辟邪劍譜的中心思想就八個字」欲練此功必先自宮」,所謂的高內聚就是一個代碼模塊只完成一項功能,在面向對象中,若是隻讓一個類完成它該作的事,而不涉及與它無關的領域就是踐行了高內聚的原則,這個類就只有單一職責。咱們都知道一句話叫」由於專一,因此專業」,一個對象若是承擔太多的職責,那麼註定它什麼都作很差。這個世界上任何好的東西都有兩個特徵,一個是功能單一,好的相機絕對不是電視裏面賣的那種一個機器有一百多種功能的,它基本上只能照相;另外一個是模塊化,好的自行車是組裝車,從減震叉、剎車到變速器,全部的部件都是能夠拆卸和從新組裝的,好的乒乓球拍也不是成品拍,必定是底板和膠皮能夠拆分和自行組裝的,一個好的軟件系統,它裏面的每一個功能模塊也應該是能夠輕易的拿到其餘系統中使用的,這樣才能實現軟件複用的目標。)
(2)開閉原則:軟件實體應當對擴展開放,對修改關閉。(在理想的狀態下,當咱們須要爲一個軟件系統增長新功能時,只須要從原來的系統派生出一些新類就能夠,不須要修改原來的任何一行代碼。要作到開閉有兩個要點:①抽象是關鍵,一個系統中若是沒有抽象類或接口系統就沒有擴展點;②封裝可變性,將系統中的各類可變因素封裝到一個繼承結構中,若是多個可變因素混雜在一塊兒,系統將變得複雜而換亂,若是不清楚如何封裝可變性,能夠參考《設計模式精解》一書中對橋樑模式的講解的章節。)
(3)依賴倒轉原則:面向接口編程。(該原則說得直白和具體一些就是聲明方法的參數類型、方法的返回類型、變量的引用類型時,儘量使用抽象類型而不用具體類型,由於抽象類型能夠被它的任何一個子類型所替代,請參考下面的里氏替換原則。)
(4)里氏替換原則:任什麼時候候均可以用子類型替換掉父類型。(關於里氏替換原則的描述,Barbara Liskov 女士的描述比這個要複雜得多,但簡單的說就是能用父類型的地方就必定能使用子類型。里氏替換原則能夠檢查繼承關係是否合理,若是一個繼承關係違背了里氏替換原則,那麼這個繼承關係必定是錯誤的,須要對代碼進行重構。例如讓貓繼承狗,或者狗繼承貓,又或者讓正方形繼承長方形都是錯誤的繼承關係,由於你很容易找到違反里氏替換原則的場景。須要注意的是:子類必定是增長父類的能力而不是減小父類的能力,由於子類比父類的能力更多,把能力多的對象當成能力少的對象來用固然沒有任何問題。)
(5)接口隔離原則:接口要小而專,毫不能大而全。(臃腫的接口是對接口的污染,既然接口表示能力,那麼一個接口只應該描述一種能力,接口也應該是高度內聚的。例如,琴棋書畫就應該分別設計爲四個接口,而不該設計成一個接口中的四個方法,由於若是設計成一個接口中的四個方法,那麼這個接口很難用,畢竟琴棋書畫四樣都精通的人仍是少數,而若是設計成四個接口,會幾項就實現幾個接口,這樣的話每一個接口被複用的可能性是很高的。Java 中的接口表明能力、表明約定、表明角色,可否正確的使用接口必定是編程水平高低的重要標識。)
(6)合成聚合複用原則:優先使用聚合或合成關係複用代碼。(經過繼承來複用代碼是面向對象程序設計中被濫用得最多的東西,由於全部的教科書都無一例外的對繼承進行了鼓吹從而誤導了初學者,類與類之間簡單的說有三種關係,Is-A 關係、Has-A 關係、Use-A 關係,分別表明繼承、關聯和依賴。其中,關聯關係根據其關聯的強度又能夠進一步劃分爲關聯、聚合和合成,但說白了都是Has-A 關係,合成聚合複用原則想表達的是優先考慮 Has-A 關係而不是 Is-A 關係複用代碼,緣由嘛能夠本身從百度上找到一萬個理由,須要說明的是,即便在Java 的 API 中也有很多濫用繼承的例子,例如 Properties 類繼承了 Hashtable類,Stack 類繼承了 Vector 類,這些繼承明顯就是錯誤的,更好的作法是在Properties 類中放置一個 Hashtable 類型的成員而且將其鍵和值都設置爲字符串來存儲數據,而 Stack 類的設計也應該是在 Stack 類中放一個 Vector 對象來存儲數據。記住:任什麼時候候都不要繼承工具類,工具是能夠擁有並可使用的,而不是拿來繼承的。)
(7)迪米特法則:迪米特法則又叫最少知識原則,一個對象應當對其餘對象有儘量少的瞭解。(迪米特法則簡單的說就是如何作到」低耦合」,門面模式和調停者模式就是對迪米特法則的踐行。對於門面模式能夠舉一個簡單的例子,你去一家公司洽談業務,你不須要了解這個公司內部是如何運做的,你甚至能夠對這個公司一無所知,去的時候只須要找到公司入口處的前臺美女,告訴她們你要作什麼,她們會找到合適的人跟你接洽,前臺的美女就是公司這個系統的門面。再複雜的系統均可覺得用戶提供一個簡單的門面,Java Web 開發中做爲前端控制器的 Servlet 或 Filter 不就是一個門面嗎,瀏覽器對服務器的運做方式一無所知,可是經過前端控制器就可以根據你的請求獲得相應的服務。調停者模式也能夠舉一個簡單的例子來講明,例如一臺計算機,CPU、內存、硬盤、顯卡、聲卡各類設備須要相互配合才能很好的工做,可是若是這些東西都直接鏈接到一塊兒,計算機的佈線將異常複雜,在這種狀況下,主板做爲一個調停者的身份出現,它將各個設備鏈接在一塊兒而不須要每一個設備之間直接交換數據,這樣就減少了系統的耦合度和複雜度,以下圖所示。迪米特法則用通俗的話來將就是不要和陌生人打交道,若是真的須要,找一個本身的朋友,讓他替你和陌生人打交道。)
90、簡述一下你瞭解的設計模式。
所謂設計模式,就是一套被反覆使用的代碼設計經驗的總結(情境中一個問題通過證明的一個解決方案)。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。設計模式令人們能夠更加簡單方便的複用成功的設計和體系結構。將已證明的技術表述成設計模式也會使新系統開發者更加容易理解其設計思路。
在 GoF 的《Design Patterns: Elements of Reusable Object-OrientedSoftware》中給出了三類(建立型[對類的實例化過程的抽象化]、結構型[描述如何將類或對象結合在一塊兒造成更大的結構]、行爲型[對在不一樣的對象之間劃分責任和算法的抽象化])共 23 種設計模式,包括:Abstract Factory(抽象工廠模式),Builder(建造者模式),Factory Method(工廠方法模式),Prototype(原始模型模式),Singleton(單例模式);Facade(門面模式),Adapter(適配器模式),Bridge(橋樑模式),Composite(合成模式),Decorator(裝飾模式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式),Interpreter(解釋器模式),Visitor(訪問者模式),Iterator(迭代子模式),Mediator(調停者模式),Memento(備忘錄模式),Observer(觀察者模式),State(狀態 模式 ),Strategy(策略 模式 ),Template Method(模板方法模式),Chain Of Responsibility(責任鏈模式)。
面試被問到關於設計模式的知識時,能夠揀最經常使用的做答,例如:
(1)工廠模式:工廠類能夠根據條件生成不一樣的子類實例,這些子類有一個公共的抽象父類而且實現了相同的方法,可是這些方法針對不一樣的數據進行了不一樣的操做(多態方法)。當獲得子類的實例後,開發人員能夠調用基類中的方法而沒必要考慮到底返回的是哪個子類的實例。
(2)代理模式:給一個對象提供一個代理對象,並由代理對象控制原對象的引用。實際開發中,按照使用目的的不一樣,代理能夠分爲:遠程代理、虛擬代理、保護代理、Cache 代理、防火牆代理、同步化代理、智能引用代理。
(3)適配器模式:把一個類的接口變換成客戶端所期待的另外一種接口,從而使本來因接口不匹配而沒法在一塊兒使用的類可以一塊兒工做。
(4)模板方法模式:提供一個抽象類,將部分邏輯以具體方法或構造器的形式實現,而後聲明一些抽象方法來迫使子類實現剩餘的邏輯。不一樣的子類能夠以不一樣的方式實現這些抽象方法(多態實現),從而實現不一樣的業務邏輯。除此以外,還能夠講講上面提到的門面模式、橋樑模式、單例模式、裝潢模式(Collections 工具類和 I/O 系統中都使用裝潢模式)等,反正基本原則就是揀本身最熟悉的、用得最多的做答,以避免言多必失。
9一、用 Java 寫一個單例類。
(1)餓漢式單例
public class Singleton { private Singleton(){ } private static Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance; }}
(2)懶漢式單例
public class Singleton { private static Singleton instance = null; private Singleton() { } public static synchronized Singleton getInstance(){ if (instance == null) instance = new Singleton(); return instance; }}
注意:實現一個單例有兩點注意事項,①將構造器私有,不容許外界經過構造器建立對象;②經過公開的靜態方法向外界返回類的惟一實例。這裏有一個問題能夠思考:Spring 的 IoC 容器能夠爲普通的類建立單例,它是怎麼作到的呢?
9二、什麼是 UML?
UML 是統一建模語言(Unified Modeling Language)的縮寫,它發表於 1997年,綜合了當時已經存在的面向對象的建模語言、方法和過程,是一個支持模型化和軟件系統開發的圖形化語言,爲軟件開發的全部階段提供模型化和可視化支持。使用 UML 能夠幫助溝通與交流,輔助應用設計和文檔的生成,還可以闡釋系統的結構和行爲。
9三、UML 中有哪些經常使用的圖?
UML 定義了多種圖形化的符號來描述軟件系統部分或所有的靜態結構和動態結構,包括:用例圖(use case diagram)、類圖(class diagram)、時序圖(sequencediagram)、協做圖(collaboration diagram)、狀態圖(statechart diagram)、活動圖(activity diagram)、構件圖(component diagram)、部署圖(deploymentdiagram)等。在這些圖形化符號中,有三種圖最爲重要,分別是:用例圖(用來捕獲需求,描述系統的功能,經過該圖能夠迅速的瞭解系統的功能模塊及其關係)、類圖(描述類以及類與類之間的關係,經過該圖能夠快速瞭解系統)、時序圖(描述執行特定任務時對象之間的交互關係以及執行順序,經過該圖能夠了解對象能接收的消息也就是說對象可以向外界提供的服務)。用例圖:
類圖:
時序圖:
9四、用 Java 寫一個冒泡排序。
冒泡排序幾乎是個程序員都寫得出來,可是面試的時候如何寫一個高端的冒泡排序卻不是每一個人都能作到,下面提供一個參考代碼:
import java.util.Comparator;/** * 排序器接口(策略模式: 將算法封裝到具備共同接口的獨立的類中使得它們可 以相互替換) * @author 駱昊 * */public interface Sorter { /** * 排序 * @param list 待排序的數組 */ public <T extends Comparable<T>> void sort(T[] list); /** * 排序 * @param list 待排序的數組 * @param comp 比較兩個對象的比較器 */ public <T> void sort(T[] list, Comparator<T> comp);}import java.util.Comparator;/** * 冒泡排序 * * @author 駱昊 * */public class BubbleSorter implements Sorter { @Override public <T extends Comparable<T>> void sort(T[] list) { Boolean swapped = true; for (int i = 1, len = list.length; i < len && swapped; ++i) { swapped = false; for (int j = 0; j < len - i; ++j) { if (list[j].compareTo(list[j + 1]) > 0) { T temp = list[j]; list[j] = list[j + 1]; list[j + 1] = temp; swapped = true; } } } } @Override public <T> void sort(T[] list, Comparator<T> comp) { Boolean swapped = true; for (int i = 1, len = list.length; i < len && swapped; ++i) { swapped = false; for (int j = 0; j < len - i; ++j) { if (comp.compare(list[j], list[j + 1]) > 0) { T temp = list[j]; list[j] = list[j + 1]; list[j + 1] = temp; swapped = true; } } } }}
9五、用 Java 寫一個折半查找。
折半查找,也稱二分查找、二分搜索,是一種在有序數組中查找某一特定元素的搜索算法。搜素過程從數組的中間元素開始,若是中間元素正好是要查找的元素,則搜素過程結束;若是某一特定元素大於或者小於中間元素,則在數組大於或小於中間元素的那一半中查找,並且跟開始同樣從中間元素開始比較。若是在某一步驟數組已經爲空,則表示找不到指定的元素。這種搜索算法每一次比較都使搜索範圍縮小一半,其時間複雜度是 O(logN)。
import java.util.Comparator;public class MyUtil { public static <T extends Comparable<T>> int binarySearch(T[] x, T key) { return binarySearch(x, 0, x.length- 1, key); } // 使用循環實現的二分查找 public static <T> int binarySearch(T[] x, T key, Comparator<T> comp) { int low = 0; int high = x.length - 1; while (low <= high) { int mid = (low + high) >>> 1; int cmp = comp.compare(x[mid], key); if (cmp < 0) { low= mid + 1; } else if (cmp > 0) { high= mid - 1; } else { return mid; } } return -1; } // 使用遞歸實現的二分查找 private static<T extends Comparable<T>> int binarySearch(T[] x, int low, int high, T key) { if(low <= high) { int mid = low + ((high -low) >> 1); if(key.compareTo(x[mid])== 0) { return mid; } else if(key.compareTo(x[mid])< 0) { return binarySearch(x,low, mid - 1, key); } else { return binarySearch(x,mid + 1, high, key); } } return -1; }}
說明:上面的代碼中給出了折半查找的兩個版本,一個用遞歸實現,一個用循環實現。須要注意的是計算中間位置時不該該使用(high+ low) / 2 的方式,由於加法運算可能致使整數越界,這裏應該使用如下三種方式之一:low + (high - low)/ 2 或 low + (high – low) >> 1 或(low + high) >>> 1(>>>是邏輯右移,是不帶符號位的右移)
最後
歡迎你們一塊兒交流,喜歡文章記得關注我點個贊喲,感謝支持!