近5年常考Java面試題及答案整理(三)

 上一篇:近5年常考Java面試題及答案整理(二)

6八、Java中如何實現序列化,有什麼意義?html

答:序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化。能夠對流化後的對象進行讀寫操做,也可將流化後的對象傳輸於網絡之間。序列化是爲了解決對象流讀寫操做時可能引起的問題(若是不進行序列化可能會存在數據亂序的問題)。
要實現序列化,須要讓一個類實現Serializable接口,該接口是一個標識性接口,標註該類對象是可被序列化的,而後使用一個輸出流來構造一個對象輸出流並經過writeObject(Object)方法就能夠將實現對象寫出(即保存其狀態);若是須要反序列化則能夠用一個輸入流創建對象輸入流,而後經過readObject方法從流中讀取對象。序列化除了可以實現對象的持久化以外,還可以用於對象的深度克隆(能夠參考第29題)。前端

6九、Java中有幾種類型的流?java

答:字節流和字符流。字節流繼承於InputStream、OutputStream,字符流繼承於Reader、Writer。在 java.io 包中還有許多其餘的流,主要是爲了提升性能和使用方便。關於Java的I/O須要注意的有兩點:一是兩種對稱性(輸入和輸出的對稱性,字節和字符的對稱性);二是兩種設計模式(適配器模式和裝潢模式)。另外Java中的流不一樣於C#的是它只有一個維度一個方向。mysql

面試題 - 編程實現文件拷貝。(這個題目在筆試的時候常常出現,下面的代碼給出了兩種實現方案)程序員

 1 import java.io.FileInputStream;
 2 import java.io.FileOutputStream;
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 import java.io.OutputStream;
 6 import java.nio.ByteBuffer;
 7 import java.nio.channels.FileChannel;
 8  
 9 public final class MyUtil {
10  
11     private MyUtil() {
12         throw new AssertionError();
13     }
14  
15     public static void fileCopy(String source, String target) throws IOException {
16         try (InputStream in = new FileInputStream(source)) {
17             try (OutputStream out = new FileOutputStream(target)) {
18                 byte[] buffer = new byte[4096];
19                 int bytesToRead;
20                 while((bytesToRead = in.read(buffer)) != -1) {
21                     out.write(buffer, 0, bytesToRead);
22                 }
23             }
24         }
25     }
26  
27     public static void fileCopyNIO(String source, String target) throws IOException {
28         try (FileInputStream in = new FileInputStream(source)) {
29             try (FileOutputStream out = new FileOutputStream(target)) {
30                 FileChannel inChannel = in.getChannel();
31                 FileChannel outChannel = out.getChannel();
32                 ByteBuffer buffer = ByteBuffer.allocate(4096);
33                 while(inChannel.read(buffer) != -1) {
34                     buffer.flip();
35                     outChannel.write(buffer);
36                     buffer.clear();
37                 }
38             }
39         }
40     }
41 }

注意:上面用到Java 7的TWR,使用TWR後能夠不用在finally中釋放外部資源 ,從而讓代碼更加優雅。面試

70、寫一個方法,輸入一個文件名和一個字符串,統計這個字符串在這個文件中出現的次數。正則表達式

答:代碼以下:算法

 1 import java.io.BufferedReader;
 2 import java.io.FileReader;
 3  
 4 public final class MyUtil {
 5  
 6     // 工具類中的方法都是靜態方式訪問的所以將構造器私有不容許建立對象(絕對好習慣)
 7     private MyUtil() {
 8         throw new AssertionError();
 9     }
10  
11     /**
12      * 統計給定文件中給定字符串的出現次數
13      * 
14      * @param filename  文件名
15      * @param word 字符串
16      * @return 字符串在文件中出現的次數
17      */
18     public static int countWordInFile(String filename, String word) {
19         int counter = 0;
20         try (FileReader fr = new FileReader(filename)) {
21             try (BufferedReader br = new BufferedReader(fr)) {
22                 String line = null;
23                 while ((line = br.readLine()) != null) {
24                     int index = -1;
25                     while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) {
26                         counter++;
27                         line = line.substring(index + word.length());
28                     }
29                 }
30             }
31         } catch (Exception ex) {
32             ex.printStackTrace();
33         }
34         return counter;
35     }
36  
37 }

7一、如何用Java代碼列出一個目錄下全部的文件?sql

答:
若是隻要求列出當前文件夾下的文件,代碼以下所示:數據庫

 1 import java.io.File;
 2  
 3 class Test12 {
 4  
 5     public static void main(String[] args) {
 6         File f = new File("/Users/nnngu/Downloads");
 7         for(File temp : f.listFiles()) {
 8             if(temp.isFile()) {
 9                 System.out.println(temp.getName());
10             }
11         }
12     }
13 }

若是須要對文件夾繼續展開,代碼以下所示:

 1 import java.io.File;
 2  
 3 class Test12 {
 4  
 5     public static void main(String[] args) {
 6         showDirectory(new File("/Users/nnngu/Downloads"));
 7     }
 8  
 9     public static void showDirectory(File f) {
10         _walkDirectory(f, 0);
11     }
12  
13     private static void _walkDirectory(File f, int level) {
14         if(f.isDirectory()) {
15             for(File temp : f.listFiles()) {
16                 _walkDirectory(temp, level + 1);
17             }
18         }
19         else {
20             for(int i = 0; i < level - 1; i++) {
21                 System.out.print("\t");
22             }
23             System.out.println(f.getName());
24         }
25     }
26 }

在Java 7中可使用NIO.2的API來作一樣的事情,代碼以下所示:

 1 class ShowFileTest {
 2  
 3     public static void main(String[] args) throws IOException {
 4         Path initPath = Paths.get("/Users/nnngu/Downloads");
 5         Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {
 6  
 7             @Override
 8             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
 9                     throws IOException {
10                 System.out.println(file.getFileName().toString());
11                 return FileVisitResult.CONTINUE;
12             }
13  
14         });
15     }
16 }

7二、用Java的套接字編程實現一個多線程的回顯(echo)服務器。

答:

 1 import java.io.BufferedReader;
 2 import java.io.IOException;
 3 import java.io.InputStreamReader;
 4 import java.io.PrintWriter;
 5 import java.net.ServerSocket;
 6 import java.net.Socket;
 7  
 8 public class EchoServer {
 9  
10     private static final int ECHO_SERVER_PORT = 6789;
11  
12     public static void main(String[] args) {        
13         try(ServerSocket server = new ServerSocket(ECHO_SERVER_PORT)) {
14             System.out.println("服務器已經啓動...");
15             while(true) {
16                 Socket client = server.accept();
17                 new Thread(new ClientHandler(client)).start();
18             }
19         } catch (IOException e) {
20             e.printStackTrace();
21         }
22     }
23  
24     private static class ClientHandler implements Runnable {
25         private Socket client;
26  
27         public ClientHandler(Socket client) {
28             this.client = client;
29         }
30  
31         @Override
32         public void run() {
33             try(BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
34                     PrintWriter pw = new PrintWriter(client.getOutputStream())) {
35                 String msg = br.readLine();
36                 System.out.println("收到" + client.getInetAddress() + "發送的: " + msg);
37                 pw.println(msg);
38                 pw.flush();
39             } catch(Exception ex) {
40                 ex.printStackTrace();
41             } finally {
42                 try {
43                     client.close();
44                 } catch (IOException e) {
45                     e.printStackTrace();
46                 }
47             }
48         }
49     }
50  
51 }

注意:上面的代碼使用了Java 7的TWR語法,因爲不少外部資源類都間接的實現了AutoCloseable接口(單方法回調接口),所以能夠利用TWR語法在try結束的時候經過回調的方式自動調用外部資源類的close()方法,避免書寫冗長的finally代碼塊。此外,上面的代碼用一個靜態內部類實現線程的功能,使用多線程能夠避免一個用戶I/O操做所產生的中斷影響其餘用戶對服務器的訪問,簡單的說就是一個用戶的輸入操做不會形成其餘用戶的阻塞。固然,上面的代碼使用線程池能夠得到更好的性能,由於頻繁的建立和銷燬線程所形成的開銷也是不可忽視的。

下面是一段回顯客戶端測試代碼:

 1 import java.io.BufferedReader;
 2 import java.io.InputStreamReader;
 3 import java.io.PrintWriter;
 4 import java.net.Socket;
 5 import java.util.Scanner;
 6  
 7 public class EchoClient {
 8  
 9     public static void main(String[] args) throws Exception {
10         Socket client = new Socket("localhost", 6789);
11         Scanner sc = new Scanner(System.in);
12         System.out.print("請輸入內容: ");
13         String msg = sc.nextLine();
14         sc.close();
15         PrintWriter pw = new PrintWriter(client.getOutputStream());
16         pw.println(msg);
17         pw.flush();
18         BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
19         System.out.println(br.readLine());
20         client.close();
21     }
22 }

若是但願用NIO的多路複用套接字實現服務器,代碼以下所示。NIO的操做雖然帶來了更好的性能,可是有些操做是比較底層的,對於初學者來講仍是有些難於理解。

 1 import java.io.IOException;
 2 import java.net.InetSocketAddress;
 3 import java.nio.ByteBuffer;
 4 import java.nio.CharBuffer;
 5 import java.nio.channels.SelectionKey;
 6 import java.nio.channels.Selector;
 7 import java.nio.channels.ServerSocketChannel;
 8 import java.nio.channels.SocketChannel;
 9 import java.util.Iterator;
10  
11 public class EchoServerNIO {
12  
13     private static final int ECHO_SERVER_PORT = 6789;
14     private static final int ECHO_SERVER_TIMEOUT = 5000;
15     private static final int BUFFER_SIZE = 1024;
16  
17     private static ServerSocketChannel serverChannel = null;
18     private static Selector selector = null;    // 多路複用選擇器
19     private static ByteBuffer buffer = null;    // 緩衝區
20  
21     public static void main(String[] args) {
22         init();
23         listen();
24     }
25  
26     private static void init() {
27         try {
28             serverChannel = ServerSocketChannel.open();
29             buffer = ByteBuffer.allocate(BUFFER_SIZE);
30             serverChannel.socket().bind(new InetSocketAddress(ECHO_SERVER_PORT));
31             serverChannel.configureBlocking(false);
32             selector = Selector.open();
33             serverChannel.register(selector, SelectionKey.OP_ACCEPT);
34         } catch (Exception e) {
35             throw new RuntimeException(e);
36         }
37     }
38  
39     private static void listen() {
40         while (true) {
41             try {
42                 if (selector.select(ECHO_SERVER_TIMEOUT) != 0) {
43                     Iterator<SelectionKey> it = selector.selectedKeys().iterator();
44                     while (it.hasNext()) {
45                         SelectionKey key = it.next();
46                         it.remove();
47                         handleKey(key);
48                     }
49                 }
50             } catch (Exception e) {
51                 e.printStackTrace();
52             }
53         }
54     }
55  
56     private static void handleKey(SelectionKey key) throws IOException {
57         SocketChannel channel = null;
58  
59         try {
60             if (key.isAcceptable()) {
61                 ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
62                 channel = serverChannel.accept();
63                 channel.configureBlocking(false);
64                 channel.register(selector, SelectionKey.OP_READ);
65             } else if (key.isReadable()) {
66                 channel = (SocketChannel) key.channel();
67                 buffer.clear();
68                 if (channel.read(buffer) > 0) {
69                     buffer.flip();
70                     CharBuffer charBuffer = CharsetHelper.decode(buffer);
71                     String msg = charBuffer.toString();
72                     System.out.println("收到" + channel.getRemoteAddress() + "的消息:" + msg);
73                     channel.write(CharsetHelper.encode(CharBuffer.wrap(msg)));
74                 } else {
75                     channel.close();
76                 }
77             }
78         } catch (Exception e) {
79             e.printStackTrace();
80             if (channel != null) {
81                 channel.close();
82             }
83         }
84     }
85  
86 }

 

 1 import java.nio.ByteBuffer;
 2 import java.nio.CharBuffer;
 3 import java.nio.charset.CharacterCodingException;
 4 import java.nio.charset.Charset;
 5 import java.nio.charset.CharsetDecoder;
 6 import java.nio.charset.CharsetEncoder;
 7  
 8 public final class CharsetHelper {
 9     private static final String UTF_8 = "UTF-8";
10     private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();
11     private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
12  
13     private CharsetHelper() {
14     }
15  
16     public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{
17         return encoder.encode(in);
18     }
19  
20     public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{
21         return decoder.decode(in);
22     }
23 }

7三、XML文檔定義有幾種形式?它們之間有何本質區別?解析XML文檔有哪幾種方式?

答:XML文檔定義分爲DTD和Schema兩種形式,兩者都是對XML語法的約束,其本質區別在於Schema自己也是一個XML文件,能夠被XML解析器解析,並且能夠爲XML承載的數據定義類型,約束能力較之DTD更強大。對XML的解析主要有DOM(文檔對象模型,Document Object Model)、SAX(Simple API for XML)和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操做數據庫的步驟。

加載驅動。

Class.forName("oracle.jdbc.driver.OracleDriver");

建立鏈接。

Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "tiger");

建立語句。

1 PreparedStatement ps = con.prepareStatement("select * from emp where sal between ? and ?");
2 ps.setInt(1, 1000);
3 ps.setInt(2, 3000);

執行語句。

ResultSet rs = ps.executeQuery();

處理結果。

1 while(rs.next()) {
2     System.out.println(rs.getInt("empno") + " - " + rs.getString("ename"));
3 }

關閉資源。

1     finally {
2         if(con != null) {
3             try {
4                 con.close();
5             } catch (SQLException e) {
6                 e.printStackTrace();
7             }
8         }
9     }

提示:關閉外部資源的順序應該和打開的順序相反,也就是說先關閉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模式實際上包含了兩個模式,一是Data Accessor(數據訪問器),二是Data Object(數據對象),前者要解決如何訪問數據的問題,然後者要解決的是如何用對象封裝數據。

80、事務的ACID是指什麼?

答:

  • 原子性(Atomic):事務中各項操做,要麼全作要麼全不作,任何一項操做的失敗都會致使整個事務的失敗;
  • 一致性(Consistent):事務結束後系統狀態是一致的;
  • 隔離性(Isolated):併發執行的事務彼此沒法看到對方的中間狀態;
  • 持久性(Durable):事務完成後所作的改動都會被持久化,即便發生災難性的失敗。經過日誌和同步備份能夠在故障發生後重建數據。

補充:關於事務,在面試中被問到的機率是很高的,能夠問的問題也是不少的。首先須要知道的是,只有存在併發數據訪問時才須要事務。當多個事務訪問同一數據時,可能會存在5類問題,包括3類數據讀取問題(髒讀、不可重複讀和幻讀)和2類數據更新問題(第1類丟失更新和第2類丟失更新)。

髒讀(Dirty Read):A事務讀取B事務還沒有提交的數據並在此基礎上操做,而B事務執行回滾,那麼A讀取到的數據就是髒數據。


不可重複讀(Unrepeatable Read):事務A從新讀取前面讀取過的數據,發現該數據已經被另外一個已提交的事務B修改過了。


幻讀(Phantom Read):事務A從新執行一個查詢,返回一系列符合查詢條件的行,發現其中插入了被事務B提交的行。


第1類丟失更新:事務A撤銷時,把已經提交的事務B的更新數據覆蓋了。

時間 取款事務A 轉帳事務B
T1 開始事務
T2   開始事務
T3 查詢帳戶餘額爲1000元
T4   查詢帳戶餘額爲1000元
T5   匯入100元修改餘額爲1100元
T6   提交事務
T7 取出100元將餘額修改成900元
T8 撤銷事務
T9 餘額恢復爲1000元(丟失更新)

第2類丟失更新:事務A覆蓋事務B已經提交的數據,形成事務B所作的操做丟失。

時間 轉帳事務A 取款事務B
T1   開始事務
T2 開始事務
T3   查詢帳戶餘額爲1000元
T4 查詢帳戶餘額爲1000元
T5   取出100元將餘額修改成900元
T6   提交事務
T7 匯入100元將餘額修改成1100元
T8 提交事務
T9 查詢帳戶餘額爲1100元(丟失更新)

數據併發訪問所產生的問題,在有些場景下多是容許的,可是有些場景下可能就是致命的,數據庫一般會經過鎖機制來解決數據併發訪問問題,按鎖定對象不一樣能夠分爲表級鎖和行級鎖;按併發事務鎖定關係能夠分爲共享鎖和獨佔鎖,具體的內容你們能夠自行查閱資料進行了解。
直接使用鎖是很是麻煩的,爲此數據庫爲用戶提供了自動鎖機制,只要用戶指定會話的事務隔離級別,數據庫就會經過分析SQL語句而後爲事務訪問的資源加上合適的鎖,此外,數據庫還會維護這些鎖經過各類手段提升系統的性能,這些對用戶來講都是透明的(就是說你不用理解,事實上我確實也不知道)。ANSI/ISO SQL 92標準定義了4個等級的事務隔離級別,以下表所示:

隔離級別 髒讀 不可重複讀 幻讀 第一類丟失更新 第二類丟失更新
READ UNCOMMITED 容許 容許 容許 不容許 容許
READ COMMITTED 不容許 容許 容許 不容許 容許
REPEATABLE READ 不容許 不容許 容許 不容許 不容許
SERIALIZABLE 不容許 不容許 不容許 不容許 不容許

須要說明的是,事務隔離級別和數據訪問的併發性是對立的,事務隔離級別越高併發性就越差。因此要根據具體的應用來肯定合適的事務隔離級別,這個地方沒有萬能的原則。

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操做。下面的代碼展現瞭如何使用JDBC操做LOB:
下面以MySQL數據庫爲例,建立一個張有三個字段的用戶表,包括編號(id)、姓名(name)和照片(photo),建表語句以下:

1 create table tb_user
2 (
3 id int primary key auto_increment,
4 name varchar(20) unique not null,
5 photo longblob
6 );

下面的Java代碼向數據庫中插入一條記錄:

 1 import java.io.FileInputStream;
 2 import java.io.IOException;
 3 import java.io.InputStream;
 4 import java.sql.Connection;
 5 import java.sql.DriverManager;
 6 import java.sql.PreparedStatement;
 7 import java.sql.SQLException;
 8  
 9 class JdbcLobTest {
10  
11     public static void main(String[] args) {
12         Connection con = null;
13         try {
14             // 1. 加載驅動(Java6以上版本能夠省略)
15             Class.forName("com.mysql.jdbc.Driver");
16             // 2. 創建鏈接
17             con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
18             // 3. 建立語句對象
19             PreparedStatement ps = con.prepareStatement("insert into tb_user values (default, ?, ?)");
20             ps.setString(1, "郭靖");              // 將SQL語句中第一個佔位符換成字符串
21             try (InputStream in = new FileInputStream("test.jpg")) {    // Java 7的TWR
22                 ps.setBinaryStream(2, in);      // 將SQL語句中第二個佔位符換成二進制流
23                 // 4. 發出SQL語句得到受影響行數
24                 System.out.println(ps.executeUpdate() == 1 ? "插入成功" : "插入失敗");
25             } catch(IOException e) {
26                 System.out.println("讀取照片失敗!");
27             }
28         } catch (ClassNotFoundException | SQLException e) {     // Java 7的多異常捕獲
29             e.printStackTrace();
30         } finally { // 釋放外部資源的代碼都應當放在finally中保證其可以獲得執行
31             try {
32                 if(con != null && !con.isClosed()) {
33                     con.close();    // 5. 釋放數據庫鏈接 
34                     con = null;     // 指示垃圾回收器能夠回收該對象
35                 }
36             } catch (SQLException e) {
37                 e.printStackTrace();
38             }
39         }
40     }
41 }

8三、簡述正則表達式及其用途。

答:在編寫處理字符串的程序時,常常會有查找符合某些複雜規則的字符串的須要。正則表達式就是用於描述這些規則的工具。換句話說,正則表達式就是記錄文本規則的代碼。

說明:計算機誕生初期處理的信息幾乎都是數值,可是時過境遷,今天咱們使用計算機處理的信息更多的時候不是數值而是字符串,正則表達式就是在進行字符串匹配和處理的時候最爲強大的工具,絕大多數語言都提供了對正則表達式的支持。

8四、Java中是如何支持正則表達式操做的?

答:Java中的String類提供了支持正則表達式操做的方法,包括:matches()、replaceAll()、replaceFirst()、split()。此外,Java中能夠用Pattern類表示正則表達式對象,它提供了豐富的API進行各類正則表達式操做,請參考下面面試題的代碼。

面試題: - 若是要從字符串中截取第一個英文左括號以前的字符串,例如:北京市(朝陽區)(西城區)(海淀區),截取結果爲:北京市,那麼正則表達式怎麼寫?

 1 import java.util.regex.Matcher;
 2 import java.util.regex.Pattern;
 3  
 4 class RegExpTest {
 5  
 6     public static void main(String[] args) {
 7         String str = "北京市(朝陽區)(西城區)(海淀區)";
 8         Pattern p = Pattern.compile(".*?(?=\\()");
 9         Matcher m = p.matcher(str);
10         if(m.find()) {
11             System.out.println(m.group());
12         }
13     }
14 }

說明:上面的正則表達式中使用了懶惰匹配和前瞻,若是不清楚這些內容,推薦讀一下網上頗有名的《正則表達式30分鐘入門教程》

8五、得到一個類的類對象有哪些方式?

答:

  • 方法1:類型.class,例如:String.class
  • 方法2:對象.getClass(),例如:"hello".getClass()
  • 方法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方法來獲取/設置字段的值了。下面的代碼實現了一個反射的工具類,其中的兩個靜態方法分別用於獲取和設置私有字段的值,字段能夠是基本類型也能夠是對象類型且支持多級對象操做,例如ReflectionUtil.get(dog, "owner.car.engine.id");能夠得到dog對象的主人的汽車的引擎的ID號。

 1 import java.lang.reflect.Constructor;
 2 import java.lang.reflect.Field;
 3 import java.lang.reflect.Modifier;
 4 import java.util.ArrayList;
 5 import java.util.List;
 6  
 7 /**
 8  * 反射工具類
 9  * @author nnngu
10  *
11  */
12 public class ReflectionUtil {
13  
14     private ReflectionUtil() {
15         throw new AssertionError();
16     }
17  
18     /**
19      * 經過反射取對象指定字段(屬性)的值
20      * @param target 目標對象
21      * @param fieldName 字段的名字
22      * @throws 若是取不到對象指定字段的值則拋出異常
23      * @return 字段的值
24      */
25     public static Object getValue(Object target, String fieldName) {
26         Class<?> clazz = target.getClass();
27         String[] fs = fieldName.split("\\.");
28  
29         try {
30             for(int i = 0; i < fs.length - 1; i++) {
31                 Field f = clazz.getDeclaredField(fs[i]);
32                 f.setAccessible(true);
33                 target = f.get(target);
34                 clazz = target.getClass();
35             }
36  
37             Field f = clazz.getDeclaredField(fs[fs.length - 1]);
38             f.setAccessible(true);
39             return f.get(target);
40         }
41         catch (Exception e) {
42             throw new RuntimeException(e);
43         }
44     }
45  
46     /**
47      * 經過反射給對象的指定字段賦值
48      * @param target 目標對象
49      * @param fieldName 字段的名稱
50      * @param value 值
51      */
52     public static void setValue(Object target, String fieldName, Object value) {
53         Class<?> clazz = target.getClass();
54         String[] fs = fieldName.split("\\.");
55         try {
56             for(int i = 0; i < fs.length - 1; i++) {
57                 Field f = clazz.getDeclaredField(fs[i]);
58                 f.setAccessible(true);
59                 Object val = f.get(target);
60                 if(val == null) {
61                     Constructor<?> c = f.getType().getDeclaredConstructor();
62                     c.setAccessible(true);
63                     val = c.newInstance();
64                     f.set(target, val);
65                 }
66                 target = val;
67                 clazz = target.getClass();
68             }
69  
70             Field f = clazz.getDeclaredField(fs[fs.length - 1]);
71             f.setAccessible(true);
72             f.set(target, value);
73         }
74         catch (Exception e) {
75             throw new RuntimeException(e);
76         }
77     }
78  
79 }

8八、如何經過反射調用對象的方法?

答:請看下面的代碼:

 1 import java.lang.reflect.Method;
 2  
 3 class MethodInvokeTest {
 4  
 5     public static void main(String[] args) throws Exception {
 6         String str = "hello";
 7         Method m = str.getClass().getMethod("toUpperCase");
 8         System.out.println(m.invoke(str));  // HELLO
 9     }
10 }

8九、簡述一下面向對象的"六原則一法則"。

答:

  • 單一職責原則:一個類只作它該作的事情。(單一職責原則想表達的就是"高內聚",寫代碼最終極的原則只有六個字"高內聚、低耦合",就如同葵花寶典或辟邪劍譜的中心思想就八個字"欲練此功必先自宮",所謂的高內聚就是一個代碼模塊只完成一項功能,在面向對象中,若是隻讓一個類完成它該作的事,而不涉及與它無關的領域就是踐行了高內聚的原則,這個類就只有單一職責。咱們都知道一句話叫"由於專一,因此專業",一個對象若是承擔太多的職責,那麼註定它什麼都作很差。這個世界上任何好的東西都有兩個特徵,一個是功能單一,好的相機絕對不是電視購物裏面賣的那種一個機器有一百多種功能的,它基本上只能照相;另外一個是模塊化,好的自行車是組裝車,從減震叉、剎車到變速器,全部的部件都是能夠拆卸和從新組裝的,好的乒乓球拍也不是成品拍,必定是底板和膠皮能夠拆分和自行組裝的,一個好的軟件系統,它裏面的每一個功能模塊也應該是能夠輕易的拿到其餘系統中使用的,這樣才能實現軟件複用的目標。)
  • 開閉原則:軟件實體應當對擴展開放,對修改關閉。(在理想的狀態下,當咱們須要爲一個軟件系統增長新功能時,只須要從原來的系統派生出一些新類就能夠,不須要修改原來的任何一行代碼。要作到開閉有兩個要點:①抽象是關鍵,一個系統中若是沒有抽象類或接口系統就沒有擴展點;②封裝可變性,將系統中的各類可變因素封裝到一個繼承結構中,若是多個可變因素混雜在一塊兒,系統將變得複雜而混亂,若是不清楚如何封裝可變性,能夠參考《設計模式精解》一書中對橋樑模式的講解的章節。)
  • 依賴倒轉原則:面向接口編程。(該原則說得直白和具體一些就是聲明方法的參數類型、方法的返回類型、變量的引用類型時,儘量使用抽象類型而不用具體類型,由於抽象類型能夠被它的任何一個子類型所替代,請參考下面的里氏替換原則。)
    里氏替換原則:任什麼時候候均可以用子類型替換掉父類型。(關於里氏替換原則的描述,Barbara Liskov女士的描述比這個要複雜得多,但簡單的說就是能用父類型的地方就必定能使用子類型。里氏替換原則能夠檢查繼承關係是否合理,若是一個繼承關係違背了里氏替換原則,那麼這個繼承關係必定是錯誤的,須要對代碼進行重構。例如讓貓繼承狗,或者狗繼承貓,又或者讓正方形繼承長方形都是錯誤的繼承關係,由於你很容易找到違反里氏替換原則的場景。須要注意的是:子類必定是增長父類的能力而不是減小父類的能力,由於子類比父類的能力更多,把能力多的對象當成能力少的對象來用固然沒有任何問題。)
  • 接口隔離原則:接口要小而專,毫不能大而全。(臃腫的接口是對接口的污染,既然接口表示能力,那麼一個接口只應該描述一種能力,接口也應該是高度內聚的。例如,琴棋書畫就應該分別設計爲四個接口,而不該設計成一個接口中的四個方法,由於若是設計成一個接口中的四個方法,那麼這個接口很難用,畢竟琴棋書畫四樣都精通的人仍是少數,而若是設計成四個接口,會幾項就實現幾個接口,這樣的話每一個接口被複用的可能性是很高的。Java中的接口表明能力、表明約定、表明角色,可否正確的使用接口必定是編程水平高低的重要標識。)
  • 合成聚合複用原則:優先使用聚合或合成關係複用代碼。(經過繼承來複用代碼是面向對象程序設計中被濫用得最多的東西,由於全部的教科書都無一例外的對繼承進行了鼓吹從而誤導了初學者,類與類之間簡單的說有三種關係,Is-A關係、Has-A關係、Use-A關係,分別表明繼承、關聯和依賴。其中,關聯關係根據其關聯的強度又能夠進一步劃分爲關聯、聚合和合成,但說白了都是Has-A關係,合成聚合複用原則想表達的是優先考慮Has-A關係而不是Is-A關係複用代碼,緣由嘛能夠本身從百度上找到一萬個理由,須要說明的是,即便在Java的API中也有很多濫用繼承的例子,例如Properties類繼承了Hashtable類,Stack類繼承了Vector類,這些繼承明顯就是錯誤的,更好的作法是在Properties類中放置一個Hashtable類型的成員而且將其鍵和值都設置爲字符串來存儲數據,而Stack類的設計也應該是在Stack類中放一個Vector對象來存儲數據。記住:任什麼時候候都不要繼承工具類,工具是能夠擁有並可使用的,而不是拿來繼承的。)
  • 迪米特法則:迪米特法則又叫最少知識原則,一個對象應當對其餘對象有儘量少的瞭解。(迪米特法則簡單的說就是如何作到"低耦合",門面模式和調停者模式就是對迪米特法則的踐行。對於門面模式能夠舉一個簡單的例子,你去一家公司洽談業務,你不須要了解這個公司內部是如何運做的,你甚至能夠對這個公司一無所知,去的時候只須要找到公司入口處的前臺美女,告訴她們你要作什麼,她們會找到合適的人跟你接洽,前臺的美女就是公司這個系統的門面。再複雜的系統均可覺得用戶提供一個簡單的門面,Java Web開發中做爲前端控制器的Servlet或Filter不就是一個門面嗎,瀏覽器對服務器的運做方式一無所知,可是經過前端控制器就可以根據你的請求獲得相應的服務。調停者模式也能夠舉一個簡單的例子來講明,例如一臺計算機,CPU、內存、硬盤、顯卡、聲卡各類設備須要相互配合才能很好的工做,可是若是這些東西都直接鏈接到一塊兒,計算機的佈線將異常複雜,在這種狀況下,主板做爲一個調停者的身份出現,它將各個設備鏈接在一塊兒而不須要每一個設備之間直接交換數據,這樣就減少了系統的耦合度和複雜度,以下圖所示。迪米特法則用通俗的話來將就是不要和陌生人打交道,若是真的須要,找一個本身的朋友,讓他替你和陌生人打交道。)

90、簡述一下你瞭解的設計模式。

答:所謂設計模式,就是一套被反覆使用的代碼設計經驗的總結(情境中一個問題通過證明的一個解決方案)。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。設計模式令人們能夠更加簡單方便的複用成功的設計和體系結構。將已證明的技術表述成設計模式也會使新系統開發者更加容易理解其設計思路。
在GoF的《Design Patterns: Elements of Reusable Object-Oriented Software》中給出了三類(建立型[對類的實例化過程的抽象化]、結構型[描述如何將類或對象結合在一塊兒造成更大的結構]、行爲型[對在不一樣的對象之間劃分責任和算法的抽象化])共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(責任鏈模式)。
面試被問到關於設計模式的知識時,能夠揀最經常使用的做答,例如:

  • 工廠模式:工廠類能夠根據條件生成不一樣的子類實例,這些子類有一個公共的抽象父類而且實現了相同的方法,可是這些方法針對不一樣的數據進行了不一樣的操做(多態方法)。當獲得子類的實例後,開發人員能夠調用基類中的方法而沒必要考慮到底返回的是哪個子類的實例。
  • 代理模式:給一個對象提供一個代理對象,並由代理對象控制原對象的引用。實際開發中,按照使用目的的不一樣,代理能夠分爲:遠程代理、虛擬代理、保護代理、Cache代理、防火牆代理、同步化代理、智能引用代理。
  • 適配器模式:把一個類的接口變換成客戶端所期待的另外一種接口,從而使本來因接口不匹配而沒法在一塊兒使用的類可以一塊兒工做。
  • 模板方法模式:提供一個抽象類,將部分邏輯以具體方法或構造器的形式實現,而後聲明一些抽象方法來迫使子類實現剩餘的邏輯。不一樣的子類能夠以不一樣的方式實現這些抽象方法(多態實現),從而實現不一樣的業務邏輯。
    除此以外,還能夠講講上面提到的門面模式、橋樑模式、單例模式、裝潢模式(Collections工具類和I/O系統中都使用裝潢模式)等,反正基本原則就是揀本身最熟悉的、用得最多的做答,以避免言多必失。

9一、用Java寫一個單例類。

答:

  • 餓漢式單例
1 public class Singleton {
2     private Singleton(){}
3     private static Singleton instance = new Singleton();
4     public static Singleton getInstance(){
5         return instance;
6     }
7 }
  • 懶漢式單例
1 public class Singleton {
2     private static Singleton instance = null;
3     private Singleton() {}
4     public static synchronized Singleton getInstance(){
5         if (instance == null) instance = new Singleton();
6         return instance;
7     }
8 }

注意:實現一個單例有兩點注意事項,①將構造器私有,不容許外界經過構造器建立對象;②經過公開的靜態方法向外界返回類的惟一實例。這裏有一個問題能夠思考:Spring的IoC容器能夠爲普通的類建立單例,它是怎麼作到的呢?

9二、什麼是UML?

答:UML是統一建模語言(Unified Modeling Language)的縮寫,它發表於1997年,綜合了當時已經存在的面向對象的建模語言、方法和過程,是一個支持模型化和軟件系統開發的圖形化語言,爲軟件開發的全部階段提供模型化和可視化支持。使用UML能夠幫助溝通與交流,輔助應用設計和文檔的生成,還可以闡釋系統的結構和行爲。

9三、UML中有哪些經常使用的圖?

答:UML定義了多種圖形化的符號來描述軟件系統部分或所有的靜態結構和動態結構,包括:用例圖(use case diagram)、類圖(class diagram)、時序圖(sequence diagram)、協做圖(collaboration diagram)、狀態圖(statechart diagram)、活動圖(activity diagram)、構件圖(component diagram)、部署圖(deployment diagram)等。在這些圖形化符號中,有三種圖最爲重要,分別是:用例圖(用來捕獲需求,描述系統的功能,經過該圖能夠迅速的瞭解系統的功能模塊及其關係)、類圖(描述類以及類與類之間的關係,經過該圖能夠快速瞭解系統)、時序圖(描述執行特定任務時對象之間的交互關係以及執行順序,經過該圖能夠了解對象能接收的消息也就是說對象可以向外界提供的服務)。
用例圖:

類圖:

時序圖:

9四、用Java寫一個冒泡排序。

答:冒泡排序幾乎是個程序員都寫得出來,可是面試的時候如何寫一個逼格高的冒泡排序卻不是每一個人都能作到,下面提供一個參考代碼:

 1 import java.util.Comparator;
 2  
 3 /**
 4  * 排序器接口(策略模式: 將算法封裝到具備共同接口的獨立的類中使得它們能夠相互替換)
 5  * @author nnngu
 6  *
 7  */
 8 public interface Sorter {
 9  
10    /**
11     * 排序
12     * @param list 待排序的數組
13     */
14    public <T extends Comparable<T>> void sort(T[] list);
15  
16    /**
17     * 排序
18     * @param list 待排序的數組
19     * @param comp 比較兩個對象的比較器
20     */
21    public <T> void sort(T[] list, Comparator<T> comp);
22 }

 

 1 import java.util.Comparator;
 2  
 3 /**
 4  * 冒泡排序
 5  * 
 6  * @author nnngu
 7  *
 8  */
 9 public class BubbleSorter implements Sorter {
10  
11     @Override
12     public <T extends Comparable<T>> void sort(T[] list) {
13         boolean swapped = true;
14         for (int i = 1, len = list.length; i < len && swapped; ++i) {
15             swapped = false;
16             for (int j = 0; j < len - i; ++j) {
17                 if (list[j].compareTo(list[j + 1]) > 0) {
18                     T temp = list[j];
19                     list[j] = list[j + 1];
20                     list[j + 1] = temp;
21                     swapped = true;
22                 }
23             }
24         }
25     }
26  
27     @Override
28     public <T> void sort(T[] list, Comparator<T> comp) {
29         boolean swapped = true;
30         for (int i = 1, len = list.length; i < len && swapped; ++i) {
31             swapped = false;
32             for (int j = 0; j < len - i; ++j) {
33                 if (comp.compare(list[j], list[j + 1]) > 0) {
34                     T temp = list[j];
35                     list[j] = list[j + 1];
36                     list[j + 1] = temp;
37                     swapped = true;
38                 }
39             }
40         }
41     }
42 }

9五、用Java寫一個折半查找。

答:折半查找,也稱二分查找、二分搜索,是一種在有序數組中查找某一特定元素的搜索算法。搜素過程從數組的中間元素開始,若是中間元素正好是要查找的元素,則搜素過程結束;若是某一特定元素大於或者小於中間元素,則在數組大於或小於中間元素的那一半中查找,並且跟開始同樣從中間元素開始比較。若是在某一步驟數組已經爲空,則表示找不到指定的元素。這種搜索算法每一次比較都使搜索範圍縮小一半,其時間複雜度是O(logN)。

 1 import java.util.Comparator;
 2  
 3 public class MyUtil {
 4  
 5    public static <T extends Comparable<T>> int binarySearch(T[] x, T key) {
 6       return binarySearch(x, 0, x.length- 1, key);
 7    }
 8  
 9    // 使用循環實現的二分查找
10    public static <T> int binarySearch(T[] x, T key, Comparator<T> comp) {
11       int low = 0;
12       int high = x.length - 1;
13       while (low <= high) {
14           int mid = (low + high) >>> 1;
15           int cmp = comp.compare(x[mid], key);
16           if (cmp < 0) {
17             low= mid + 1;
18           }
19           else if (cmp > 0) {
20             high= mid - 1;
21           }
22           else {
23             return mid;
24           }
25       }
26       return -1;
27    }
28  
29    // 使用遞歸實現的二分查找
30    private static<T extends Comparable<T>> int binarySearch(T[] x, int low, int high, T key) {
31       if(low <= high) {
32         int mid = low + ((high - low) >> 1);
33         if(key.compareTo(x[mid])== 0) {
34            return mid;
35         }
36         else if(key.compareTo(x[mid])< 0) {
37            return binarySearch(x,low, mid - 1, key);
38         }
39         else {
40            return binarySearch(x,mid + 1, high, key);
41         }
42       }
43       return -1;
44    }
45 }

說明:上面的代碼中給出了折半查找的兩個版本,一個用遞歸實現,一個用循環實現。須要注意的是計算中間位置時不該該使用(high+ low) / 2的方式,由於加法運算可能致使整數越界,這裏應該使用如下三種方式之一:low + (high - low) / 2或low + (high – low) >> 1或(low + high) >>> 1(>>>是邏輯右移,是不帶符號位的右移)

相關文章
相關標籤/搜索