标签:val 可变 config 方便 schema 解决 解密 tis 场景
68、Java中如何实现序列化,有什么意义?
答:序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。
要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆(可以参考第29题)。
69、Java中有几种类型的流?
答:字节流和字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。在 java.io
包中还有许多其他的流,主要是为了提高性能和使用方便。关于Java的I/O需要注意的有两点:一是两种对称性(输入和输出的对称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。另外Java中的流不同于C#的是它只有一个维度一个方向。
面试题 - 编程实现文件拷贝。(这个题目在笔试的时候经常出现,下面的代码给出了两种实现方案)
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 }
71、如何用Java代码列出一个目录下所有的文件?
答:
如果只要求列出当前文件夹下的文件,代码如下所示:
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 }
72、用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 }
73、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允许应用程序代码把这些事件逐个拉出来,而不用提供在解析器方便时从解析器中接收事件的处理程序。
74、你在项目中哪些地方用到了XML?
答:XML的主要作用有两个方面:数据交换和信息配置。在做数据交换时,XML将数据用标签组装成起来,然后压缩打包加密后通过网络传送给接收者,接收解密与解压缩后再从XML文件中还原相关信息进行处理,XML曾经是异构系统间交换数据的事实标准,但此项功能几乎已经被JSON(JavaScript Object Notation)取而代之。当然,目前很多软件仍然使用XML来存储配置信息,我们在很多项目中通常也会将作为配置信息的硬代码写在XML文件中,Java的很多框架也是这么做的,而且这些框架都选择了dom4j作为处理XML的工具,因为Sun公司的官方API实在不怎么好用。
补充:现在有很多时髦的软件(如Sublime)已经开始将配置文件书写成JSON格式,我们已经强烈的感受到XML的另一项功能也将逐渐被业界抛弃。
75、阐述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中是可以省略的(自动从类路径中加载驱动),但是我们建议保留。
76、Statement和PreparedStatement有什么区别?哪个性能更好?
答:与Statement相比,①PreparedStatement接口代表预编译的语句,它主要的优势在于可以减少SQL的编译错误并增加SQL的安全性(减少SQL注射攻击的可能性);②PreparedStatement中的SQL语句是可以带参数的,避免了用字符串连接拼接SQL语句的麻烦和不安全;③当批量处理SQL或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,由于数据库可以将编译优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。
补充:为了提供对存储过程的调用,JDBC API中还提供了CallableStatement接口。存储过程(Stored Procedure)是数据库中一组为了完成特定功能的SQL语句的集合,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。虽然调用存储过程会在网络开销、安全性、性能上获得很多好处,但是存在如果底层数据库发生迁移时就会有很多麻烦,因为每种数据库的存储过程在书写上存在不少的差别。
77、使用JDBC操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能?
答:要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的setFetchSize()方法指定每次抓取的记录数(典型的空间换时间策略);要提升更新数据的性能可以使用PreparedStatement语句构建批处理,将若干SQL语句置于一个批处理中执行。
78、在进行数据库编程时,连接池有什么作用?
答:由于创建连接和释放连接都有很大的开销(尤其是数据库服务器不在本地时,每次建立连接都需要进行TCP的三次握手,释放连接需要进行TCP四次握手,造成的开销是不可忽视的),为了提升系统访问数据库的性能,可以事先创建若干连接置于连接池中,需要时直接从连接池获取,使用结束时归还连接池而不必关闭连接,从而避免频繁创建和释放连接所造成的开销,这是典型的用空间换取时间的策略(浪费了空间存储连接,但节省了创建和释放连接的时间)。池化技术在Java开发中是很常见的,在使用线程时创建线程池的道理与此相同。基于Java的开源数据库连接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid等。
补充:在计算机系统中时间和空间是不可调和的矛盾,理解这一点对设计满足性能要求的算法是至关重要的。大型网站性能优化的一个关键就是使用缓存,而缓存跟上面讲的连接池道理非常类似,也是使用空间换时间的策略。可以将热点数据置于缓存中,当用户查询这些数据时可以直接从缓存中得到,这无论如何也快过去数据库中查询。当然,缓存的置换策略等也会对系统性能产生重要影响,对于这个问题的讨论已经超出了这里要阐述的范围。
79、什么是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 | 不允许 | 不允许 | 不允许 | 不允许 | 不允许 |
需要说明的是,事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。
81、JDBC中如何进行事务处理?
答:Connection提供了事务处理的方法,通过调用setAutoCommit(false)可以设置手动提交事务;当事务完成后用commit()显式提交事务;如果在事务处理过程中发生异常则通过rollback()进行事务回滚。除此之外,从JDBC 3.0中还引入了Savepoint(保存点)的概念,允许通过代码设置保存点并让事务回滚到指定的保存点。
82、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 }
83、简述正则表达式及其用途。
答:在编写处理字符串的程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。
说明:计算机诞生初期处理的信息几乎都是数值,但是时过境迁,今天我们使用计算机处理的信息更多的时候不是数值而是字符串,正则表达式就是在进行字符串匹配和处理的时候最为强大的工具,绝大多数语言都提供了对正则表达式的支持。
84、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分钟入门教程》。
85、获得一个类的类对象有哪些方式?
答:
86、如何通过反射创建对象?
答:
87、如何通过反射获取和设置对象私有字段的值?
答:可以通过类对象的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 }
88、如何通过反射调用对象的方法?
答:请看下面的代码:
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 }
89、简述一下面向对象的"六原则一法则"。
答:
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(责任链模式)。
面试被问到关于设计模式的知识时,可以拣最常用的作答,例如:
91、用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容器可以为普通的类创建单例,它是怎么做到的呢?
92、什么是UML?
答:UML是统一建模语言(Unified Modeling Language)的缩写,它发表于1997年,综合了当时已经存在的面向对象的建模语言、方法和过程,是一个支持模型化和软件系统开发的图形化语言,为软件开发的所有阶段提供模型化和可视化支持。使用UML可以帮助沟通与交流,辅助应用设计和文档的生成,还能够阐释系统的结构和行为。
93、UML中有哪些常用的图?
答:UML定义了多种图形化的符号来描述软件系统部分或全部的静态结构和动态结构,包括:用例图(use case diagram)、类图(class diagram)、时序图(sequence diagram)、协作图(collaboration diagram)、状态图(statechart diagram)、活动图(activity diagram)、构件图(component diagram)、部署图(deployment diagram)等。在这些图形化符号中,有三种图最为重要,分别是:用例图(用来捕获需求,描述系统的功能,通过该图可以迅速的了解系统的功能模块及其关系)、类图(描述类以及类与类之间的关系,通过该图可以快速了解系统)、时序图(描述执行特定任务时对象之间的交互关系以及执行顺序,通过该图可以了解对象能接收的消息也就是说对象能够向外界提供的服务)。
用例图:
类图:
时序图:
94、用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 }
95、用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(>>>是逻辑右移,是不带符号位的右移)
标签:val 可变 config 方便 schema 解决 解密 tis 场景
原文地址:https://www.cnblogs.com/aishangJava/p/10334012.html