近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(>>>是逻辑右移,是不带符号位的右移)

相关文章
相关标签/搜索