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操做數據庫的步驟。
加載驅動。
建立鏈接。
建立語句。
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是指什麼?
答:
補充:關於事務,在面試中被問到的機率是很高的,能夠問的問題也是不少的。首先須要知道的是,只有存在併發數據訪問時才須要事務。當多個事務訪問同一數據時,可能會存在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五、得到一個類的類對象有哪些方式?
答:
8六、如何經過反射建立對象?
答:
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九、簡述一下面向對象的"六原則一法則"。
答:
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(責任鏈模式)。
面試被問到關於設計模式的知識時,能夠揀最經常使用的做答,例如:
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(>>>是邏輯右移,是不帶符號位的右移)