六、 序列化和反序列化
6.1. Serializable接口
Serializable接口是Java中的一个标记接口,它告诉Java虚拟机(JVM)这个类是可以被序列化的。序列化是将对象转换为字节流的过程,而反序列化则是将字节流转换回对象。 Serializable接口没有任何方法,因此它只是一个标记接口,表示可以被序列化和反序列化。当一个类实现Serializable接口时,JVM会自动处理该类的序列化和反序列化过程。
6.2 ObjectOutputStream和ObjectInputStream
ObjectOutputStream类将Java对象序列化到文件或网络流中。而ObjectInputStream类则用于从文件或网络流中反序列化Java对象。
下面是一个将对象序列化到文件中的示例代码:
try { FileOutputStream fileOut = new FileOutputStream("output.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(new Person("John", 30)); out.close(); fileOut.close(); } catch (IOException e) { e.printStackTrace(); }
这个示例代码创建了一个ObjectOutputStream对象,将一个Person对象序列化到名为"output.ser"的文件中。
下面是一个从文件中反序列化Java对象的示例代码:
try { FileInputStream fileIn = new FileInputStream("output.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); Person person = (Person) in.readObject(); in.close(); fileIn.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }
这个示例代码从名为"output.ser"的文件中读取一个Person对象,并将其反序列化为一个Java对象。
6.3 Externalizable接口
Externalizable接口是Serializable接口的子接口,它提供了更多的序列化和反序列化方法。Externalizable接口定义了writeExternal和readExternal方法,用于将对象序列化和反序列化到字节流中。
下面是一个使用Externalizable接口序列化和反序列化Java对象的示例代码:
try { Person person = new Person("John", 30); DataOutputStream out = new DataOutputStream(new FileOutputStream("output.ser")); person.writeExternal(out); out.close(); FileInputStream in = new FileInputStream("output.ser"); Person newPerson = new Person(); newPerson.readExternal(in); in.close(); } catch (IOException e) { e.printStackTrace(); }
这个示例代码使用DataOutputStream将Person对象序列化到名为"output.ser"的文件中,然后使用DataInputStream从文件中反序列化Person对象。
七、异常处理和资源管理
7.1 异常处理
常见的IO异常包括FileNotFoundException、IOException和EOFException。在处理IO异常时,应该捕获IOException并处理它,因为它是所有IO异常的父类。
下面是一个处理IO异常的示例代码:
try { // 执行 IO 操作 } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
7.2 资源管理
在处理IO流时,应该正确地关闭流和释放资源,以避免资源泄漏。可以使用try-with-resources语句来自动关闭流,例如:
try (FileInputStream fileIn = new FileInputStream("output.ser"); ObjectInputStream in = new ObjectInputStream(fileIn)) { // 读取对象 } catch (IOException e) { e.printStackTrace(); }
这个示例代码使用try-with-resources语句创建了一个FileInputStream和一个ObjectInputStream对象,并在try块结束时自动关闭它们。这样可以确保资源被正确地释放,避免资源泄漏。
八、NIO、BIO、AIO
8.1 什么是NIO、BIO、AIO?
NIO、BIO、AIO是 Java 中不同类型的 I/O 操作。
NIO (Non-blocking I/O) 是非阻塞 I/O,它不会阻塞线程,而是在读写操作完成之后通知线程。
BIO (Blocking I/O) 是阻塞 I/O,它会在读写操作完成之前阻塞线程,等待数据读写完成。
AIO (Asynchronous I/O) 是异步 I/O,它不会阻塞线程,而是使用回调函数通知线程读写操作已完成。
维度 | BIO | NIO | AIO |
IO模型 | 同步阻塞IO | 同步非阻塞IO | 异步非阻塞IO |
编程模型 | 基于流(Stream) | 基于缓冲区(Buffer) | 基于事件(Event) |
代码复杂度 | 简单 | 较复杂 | 最复杂 |
并发性能 | 差 | 较好 | 最好 |
可扩展性 | 差 | 较好 | 最好 |
资源消耗 | 高 | 中 | 低 |
对网络连接数 | 单线程处理多个连接 | 单线程处理多个连接 | 多线程处理多个连接 |
对操作系统支持 | 全面支持 | 仅支持较新版本 | 仅支持Linux和Windows |
应用场景 | 对并发性能要求不高的小型应用 | 对并发性能要求较高的中小型应用 | 对并发性能要求最高的大型应用 |
— | — | — | — |
IO模型 | 同步阻塞IO | 同步非阻塞IO | 异步非阻塞IO |
编程模型 | 基于流(Stream) | 基于缓冲区(Buffer) | 基于事件(Event) |
代码复杂度 | 简单 | 较复杂 | 最复杂 |
并发性能 | 差 | 较好 | 最好 |
可扩展性 | 差 | 较好 | 最好 |
资源消耗 | 高 | 中 | 低 |
对网络连接数 | 单线程处理多个连接 | 单线程处理多个连接 | 多线程处理多个连接 |
对操作系统支持 | 全面支持 | 仅支持较新版本 | 仅支持Linux和Windows |
应用场景 | 对并发性能要求不高的小型应用 | 对并发性能要求较高的中小型应用 | 对并发性能要求最高的大型应用 |
— | — | — | — |
IO模型 | 同步阻塞IO | 同步非阻塞IO | 异步非阻塞IO |
编程模型 | 基于流(Stream) | 基于缓冲区(Buffer) | 基于事件(Event) |
代码复杂度 | 简单 | 较复杂 | 最复杂 |
并发性能 | 差 | 较好 | 最好 |
可扩展性 | 差 | 较好 | 最好 |
资源消耗 | 高 | 中 | 低 |
对网络连接数 | 单线程处理多个连接 | 单线程处理多个连接 | 多线程处理多个连接 |
对操作系统支持 | 全面支持 | 仅支持较新版本 | 仅支持Linux和Windows |
应用场景 | 对并发性能要求不高的小型应用 | 对并发性能要求较高的中小型应用 | 对并发性能要求最高的大型应用 |
8.2 Buffer和Channel
Buffer是一个对象,可以存储特定类型的数据。与之相对的是Channel,它是数据的源或目标,可以与Buffer进行交互。在NIO中,数据通过Buffer在Channel之间传输。
在Java NIO中,常用的Buffer类有以下几种:ByteBuffer: 存储字节数据
CharBuffer: 存储字符数据
ShortBuffer: 存储短整数数据
IntBuffer: 存储整数数据
LongBuffer: 存储长整数数据
FloatBuffer: 存储浮点数数据
DoubleBuffer: 存储双精度浮点数数据
Channel是与数据源或目标进行通信的对象。常用的Channel类型有:FileChannel: 用于文件的读写操作
SocketChannel: 用于通过TCP进行网络通信
DatagramChannel:用于通过UDP进行网络通信
ServerSocketChannel: 用于监听TCP连接请求
下面是一个使用Buffer和Channel进行文件读写的示例代码:
import java.io.*; import java.nio.*; import java.nio.channels.*; public class FileReadWriteExample { public static void main(String[] args) { try { // 打开文件输入流和输出流 FileInputStream fis = new FileInputStream("input.txt"); FileOutputStream fos = new FileOutputStream("output.txt"); // 获取文件输入流和输出流对应的通道 FileChannel inputChannel = fis.getChannel(); FileChannel outputChannel = fos.getChannel(); // 创建缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); // 从输入通道读取数据到缓冲区 int bytesRead = inputChannel.read(buffer); while (bytesRead != -1) { // 切换为读模式 buffer.flip(); // 从缓冲区写入数据到输出通道 outputChannel.write(buffer); // 清空缓冲区,准备下一次读取 buffer.clear(); // 继续从输入通道读取数据 bytesRead = inputChannel.read(buffer); } // 关闭通道和流 inputChannel.close(); outputChannel.close(); fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } }
以下是关于Java IO的常用类的表格:
九、相关面试题
什么是缓冲流?为什么要使用它?
缓冲流(Buffered Stream)是一种性能优化的流,它使用缓冲区来减少对底层资源(如磁盘或网络)的访问次数,从而提高读写效率。
什么是序列化?如何实现Java对象的序列化?
序列化是将对象转换为字节流的过程,以便可以将其存储在文件中或通过网络传输。要实现Java对象的序列化,需要实现Serializable接口,并定义一个特殊的serialVersionUID字段。如何使用Java IO进行网络编程?
可以使用Socket类和ServerSocket类来实现基于TCP/IP的网络编程。Socket类用于创建客户端套接字,ServerSocket类用于创建服务器套接字。通过这些类,可以在网络上发送和接收数据。