一、 Java IO (Blocking IO)
-
基本概念:
- Java IO 是 Java 平台提供的用于进行输入和输出操作的 API。
- Java IO 基于 流 (Stream) 的模型,数据像水流一样从一个地方流向另一个地方。
- Java IO 主要是 阻塞式 I/O (Blocking I/O),即线程在执行 I/O 操作时会被阻塞,直到操作完成。
- 传统IO指的是
java.io
包下的部分组件(File, InputStream, OutputStream, Reader, Writer)。
-
IO 流的分类:
-
按数据传输方向:
- 输入流 (Input Stream): 用于从数据源读取数据(例如,从文件、网络连接、键盘等)。 以
InputStream
或Reader
作为基类。 - 输出流 (Output Stream): 用于将数据写入到目标(例如,写入到文件、网络连接、控制台等)。 以
OutputStream
或Writer
作为基类。
- 输入流 (Input Stream): 用于从数据源读取数据(例如,从文件、网络连接、键盘等)。 以
-
按数据传输单位:
- 字节流 (Byte Stream): 以字节 (8 bits) 为单位进行数据传输。 以
InputStream
和OutputStream
作为基类。 适用于处理二进制数据(例如,图片、音频、视频等)。 - 字符流 (Character Stream): 以字符 (16 bits) 为单位进行数据传输。 以
Reader
和Writer
作为基类。 适用于处理文本数据。
- 字节流 (Byte Stream): 以字节 (8 bits) 为单位进行数据传输。 以
-
-
核心类和接口:
-
InputStream
(字节输入流):FileInputStream
: 从文件中读取字节。ByteArrayInputStream
: 从字节数组中读取字节。ObjectInputStream
: 从对象流中读取对象。BufferedInputStream
: 带缓冲的字节输入流,提高读取效率。
-
OutputStream
(字节输出流):FileOutputStream
: 向文件中写入字节。ByteArrayOutputStream
: 向字节数组中写入字节。ObjectOutputStream
: 向对象流中写入对象。BufferedOutputStream
: 带缓冲的字节输出流,提高写入效率。
-
Reader
(字符输入流):FileReader
: 从文件中读取字符。CharArrayReader
: 从字符数组中读取字符。BufferedReader
: 带缓冲的字符输入流,提高读取效率。InputStreamReader
: 将字节输入流转换为字符输入流(需要指定字符编码)。
-
Writer
(字符输出流):FileWriter
: 向文件中写入字符。CharArrayWriter
: 向字符数组中写入字符。BufferedWriter
: 带缓冲的字符输出流,提高写入效率。OutputStreamWriter
: 将字节输出流转换为字符输出流(需要指定字符编码)。
-
File
: 表示文件或目录的抽象表示。
-
-
IO 操作流程 (以读取文件为例):
- 创建
File
对象: 指定要读取的文件路径。 - 创建
FileInputStream
对象: 将File
对象作为参数传递给FileInputStream
的构造方法,创建一个FileInputStream
对象。 - 创建
BufferedInputStream
对象 (可选): 将FileInputStream
对象作为参数传递给BufferedInputStream
的构造方法,创建一个BufferedInputStream
对象,提高读取效率。 - 读取数据: 使用
read()
方法从输入流中读取数据。 - 关闭流: 在完成读取操作后,务必关闭输入流,释放资源(先关闭
BufferedInputStream
,再关闭FileInputStream
)。
- 创建
-
代码示例 (读取文件内容):
java">import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class IOExample { public static void main(String[] args) { File file = new File("test.txt"); // 替换为你的文件路径 try (FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis)) { // 使用 try-with-resources 语句,自动关闭流 byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = bis.read(buffer)) != -1) { // 处理读取到的数据 String data = new String(buffer, 0, bytesRead); System.out.print(data); } } catch (IOException e) { e.printStackTrace(); } } }
二、 Java NIO (Non-blocking IO)
-
基本概念:
- Java NIO 是 Java 1.4 引入的一组新的 I/O API,旨在提供高性能、非阻塞的 I/O 操作。
- NIO 使用 通道 (Channel) 和 缓冲区 (Buffer) 的模型,而不是流。
- NIO 主要是 非阻塞式 I/O (Non-blocking I/O),即线程在执行 I/O 操作时不会被阻塞,而是可以执行其他任务。
- NIO 使用 选择器 (Selector) 来监听多个通道的事件,实现单线程管理多个连接。
-
核心组件:
-
通道 (Channel):
- 通道类似于流,但可以进行双向数据传输(既可以读取数据,也可以写入数据)。
- 常见的通道:
FileChannel
: 用于文件 I/O。SocketChannel
: 用于 TCP 网络 I/O (客户端)。ServerSocketChannel
: 用于 TCP 网络 I/O (服务器端)。DatagramChannel
: 用于 UDP 网络 I/O。
-
缓冲区 (Buffer):
- 缓冲区是用于存储数据的容器,本质上是一个字节数组 (ByteBuffer) 或字符数组 (CharBuffer)。
- NIO 使用缓冲区进行数据传输,而不是直接从通道读取数据或向通道写入数据。
- 常见的缓冲区:
ByteBuffer
: 字节缓冲区。CharBuffer
: 字符缓冲区。ShortBuffer
: 短整型缓冲区。IntBuffer
: 整型缓冲区。LongBuffer
: 长整型缓冲区。FloatBuffer
: 浮点型缓冲区。DoubleBuffer
: 双精度浮点型缓冲区。
-
选择器 (Selector):
- 选择器允许单个线程监听多个通道的事件(例如连接建立、数据可读、数据可写等)。
- 使用选择器可以避免为每个连接创建一个线程,从而提高并发性能。
-
-
NIO 操作流程 (以读取 SocketChannel 数据为例):
- 创建
ServerSocketChannel
: 监听客户端连接。 - 创建
SocketChannel
: 接受客户端连接。 - 将
SocketChannel
注册到Selector
: 指定要监听的事件(例如OP_READ
,OP_WRITE
,OP_CONNECT
,OP_ACCEPT
)。 - 创建
ByteBuffer
: 用于存储读取到的数据。 - 调用
selector.select()
方法: 阻塞等待有事件发生的通道。 - 获取就绪的通道:
selector.selectedKeys()
返回所有就绪通道的集合。 - 处理事件: 遍历就绪通道的集合,根据不同的事件类型执行相应的操作(例如读取数据、写入数据)。
- 读取数据: 调用
channel.read(buffer)
从通道读取数据到缓冲区。 - 处理缓冲区数据: 从缓冲区读取数据并进行处理。
- 关闭通道和选择器: 在完成操作后,务必关闭通道和选择器,释放资源。
- 创建
-
代码示例 (使用 SocketChannel 读取数据):
java">import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class NIOExample { public static void main(String[] args) throws IOException { // 1. 创建 SocketChannel SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("www.example.com", 80)); socketChannel.configureBlocking(false); // 设置为非阻塞模式 // 2. 创建 ByteBuffer ByteBuffer buffer = ByteBuffer.allocate(1024); // 3. 从 Channel 读取数据到 Buffer int bytesRead = socketChannel.read(buffer); while (bytesRead > 0) { // 切换到读模式 buffer.flip(); // 4. 从 Buffer 读取数据 while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } // 清空 Buffer,准备下一次读取 buffer.clear(); bytesRead = socketChannel.read(buffer); } socketChannel.close(); } }
三、 Java IO 与 NIO 的区别
特性 | Java IO (Blocking IO) | Java NIO (Non-blocking IO) |
---|---|---|
数据传输方式 | 基于流 (Stream) | 基于通道 (Channel) 和缓冲区 (Buffer) |
I/O 模型 | 阻塞式 I/O (Blocking I/O) | 非阻塞式 I/O (Non-blocking I/O) |
选择器 | 没有 | 有 (Selector) |
API | 简单易用 | 相对复杂,需要理解通道、缓冲区、选择器等概念 |
性能 | 性能较低 (高并发下) | 性能较高 (高并发下) |
线程模型 | 通常使用多线程模型 (每个连接一个线程) | 通常使用单线程多路复用模型 (一个线程管理多个连接) |
适用场景 | 低并发、连接数较少的应用,或者可以接受阻塞的场景 | 高并发、连接数较多的应用,需要高性能和非阻塞的场景,例如网络服务器、聊天服务器等 |
四、 选择哪种 I/O 模式?
- 如果你的应用是低并发、连接数较少,并且可以接受阻塞,那么 Java IO 仍然是一个不错的选择,因为它简单易用。
- 如果你的应用是高并发、连接数较多,并且对性能要求很高,那么应该使用 Java NIO。
- 一般情况建议直接使用NIO模型,性能更好。
总结
Java IO 和 NIO 都是 Java 平台提供的用于进行输入和输出操作的 API。 Java IO 基于流的模型,使用简单但性能较低; Java NIO 基于通道和缓冲区的模型,提供高性能、非阻塞的 I/O 操作。