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