Java 中如何实现序列化,有什么意义?
答:序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。
要实现序列化,需要让一个类实现 Serializable
接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过 writeObject(Object)
方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过 readObject
方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆(可以参考第 29 题)。
Java 中有几种类型的流?
答:字节流和字符流。字节流继承于 InputStream
、OutputStream
,字符流继承于 Reader
、Writer
。在 java.io
包中还有许多其他的流,主要是为了提高性能和使用方便。关于 Java 的 I/O 需要注意的有两点:一是两种对称性(输入和输出的对称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。另外 Java 中的流不同于 C# 的是它只有一个维度一个方向。
面试题 - 编程实现文件拷贝。(这个题目在笔试的时候经常出现,下面的代码给出了两种实现方案)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel;
public final class MyUtil {
private MyUtil() { throw new AssertionError(); }
public static void fileCopy(String source, String target) throws IOException { try (InputStream in = new FileInputStream(source)) { try (OutputStream out = new FileOutputStream(target)) { byte[] buffer = new byte[4096]; int bytesToRead; while((bytesToRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesToRead); } } } }
public static void fileCopyNIO(String source, String target) throws IOException { try (FileInputStream in = new FileInputStream(source)) { try (FileOutputStream out = new FileOutputStream(target)) { FileChannel inChannel = in.getChannel(); FileChannel outChannel = out.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(4096); while(inChannel.read(buffer) != -1) { buffer.flip(); outChannel.write(buffer); buffer.clear(); } } } } }
|
注意:上面用到 Java 7 的 TWR,使用 TWR 后可以不用在 finally
中释放外部资源 ,从而让代码更加优雅。
写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数。
答:代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import java.io.BufferedReader; import java.io.FileReader;
public final class MyUtil {
private MyUtil() { throw new AssertionError(); }
public static int countWordInFile(String filename, String word) { int counter = 0; try (FileReader fr = new FileReader(filename)) { try (BufferedReader br = new BufferedReader(fr)) { String line = null; while ((line = br.readLine()) != null) { int index = -1; while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) { counter++; line = line.substring(index + word.length()); } } } } catch (Exception ex) { ex.printStackTrace(); } return counter; }
}
|
如何用 Java 代码列出一个目录下所有的文件?
答:
如果只要求列出当前文件夹下的文件,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import java.io.File;
class Test12 {
public static void main(String[] args) { File f = new File("/Users/Hao/Downloads"); for(File temp : f.listFiles()) { if(temp.isFile()) { System.out.println(temp.getName()); } } } }
|
如果需要对文件夹继续展开,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import java.io.File;
class Test12 {
public static void main(String[] args) { showDirectory(new File("/Users/Hao/Downloads")); }
public static void showDirectory(File f) { _walkDirectory(f, 0); }
private static void _walkDirectory(File f, int level) { if(f.isDirectory()) { for(File temp : f.listFiles()) { _walkDirectory(temp, level + 1); } } else { for(int i = 0; i < level - 1; i++) { System.out.print("\t"); } System.out.println(f.getName()); } } }
|
在 Java 7 中可以使用 NIO.2 的 API 来做同样的事情,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class ShowFileTest {
public static void main(String[] args) throws IOException { Path initPath = Paths.get("/Users/Hao/Downloads"); Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {
@Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { System.out.println(file.getFileName().toString()); return FileVisitResult.CONTINUE; }
}); } }
|
用 Java 的套接字编程实现一个多线程的回显(echo)服务器。
答:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket;
public class EchoServer {
private static final int ECHO_SERVER_PORT = 6789;
public static void main(String[] args) { try(ServerSocket server = new ServerSocket(ECHO_SERVER_PORT)) { System.out.println("服务器已经启动..."); while(true) { Socket client = server.accept(); new Thread(new ClientHandler(client)).start(); } } catch (IOException e) { e.printStackTrace(); } }
private static class ClientHandler implements Runnable { private Socket client;
public ClientHandler(Socket client) { this.client = client; }
@Override public void run() { try(BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())); PrintWriter pw = new PrintWriter(client.getOutputStream())) { String msg = br.readLine(); System.out.println("收到" + client.getInetAddress() + "发送的: " + msg); pw.println(msg); pw.flush(); } catch(Exception ex) { ex.printStackTrace(); } finally { try { client.close(); } catch (IOException e) { e.printStackTrace(); } } } }
}
|
注意:上面的代码使用了 Java 7 的 TWR 语法,由于很多外部资源类都间接的实现了 AutoCloseable
接口(单方法回调接口),因此可以利用 TWR 语法在 try
结束的时候通过回调的方式自动调用外部资源类的 close()
方法,避免书写冗长的 finally
代码块。此外,上面的代码用一个静态内部类实现线程的功能,使用多线程可以避免一个用户 I/O 操作所产生的中断影响其他用户对服务器的访问,简单的说就是一个用户的输入操作不会造成其他用户的阻塞。当然,上面的代码使用线程池可以获得更好的性能,因为频繁的创建和销毁线程所造成的开销也是不可忽视的。
下面是一段回显客户端测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner;
public class EchoClient {
public static void main(String[] args) throws Exception { Socket client = new Socket("localhost", 6789); Scanner sc = new Scanner(System.in); System.out.print("请输入内容: "); String msg = sc.nextLine(); sc.close(); PrintWriter pw = new PrintWriter(client.getOutputStream()); pw.println(msg); pw.flush(); BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())); System.out.println(br.readLine()); client.close(); } }
|
如果希望用 NIO 的多路复用套接字实现服务器,代码如下所示。NIO 的操作虽然带来了更好的性能,但是有些操作是比较底层的,对于初学者来说还是有些难于理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator;
public class EchoServerNIO {
private static final int ECHO_SERVER_PORT = 6789; private static final int ECHO_SERVER_TIMEOUT = 5000; private static final int BUFFER_SIZE = 1024;
private static ServerSocketChannel serverChannel = null; private static Selector selector = null; private static ByteBuffer buffer = null;
public static void main(String[] args) { init(); listen(); }
private static void init() { try { serverChannel = ServerSocketChannel.open(); buffer = ByteBuffer.allocate(BUFFER_SIZE); serverChannel.socket().bind(new InetSocketAddress(ECHO_SERVER_PORT)); serverChannel.configureBlocking(false); selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (Exception e) { throw new RuntimeException(e); } }
private static void listen() { while (true) { try { if (selector.select(ECHO_SERVER_TIMEOUT) != 0) { Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = it.next(); it.remove(); handleKey(key); } } } catch (Exception e) { e.printStackTrace(); } } }
private static void handleKey(SelectionKey key) throws IOException { SocketChannel channel = null;
try { if (key.isAcceptable()) { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); channel = serverChannel.accept(); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { channel = (SocketChannel) key.channel(); buffer.clear(); if (channel.read(buffer) > 0) { buffer.flip(); CharBuffer charBuffer = CharsetHelper.decode(buffer); String msg = charBuffer.toString(); System.out.println("收到" + channel.getRemoteAddress() + "的消息:" + msg); channel.write(CharsetHelper.encode(CharBuffer.wrap(msg))); } else { channel.close(); } } } catch (Exception e) { e.printStackTrace(); if (channel != null) { channel.close(); } } }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder;
public final class CharsetHelper { private static final String UTF_8 = "UTF-8"; private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder(); private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
private CharsetHelper() { }
public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{ return encoder.encode(in); }
public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{ return decoder.decode(in); } }
|