IO模型
- 阻塞IO (Blocking IO)
描述
- 应用程序调用IO函数之后,会一直等待数据完成(如read()等)
- 线程会被阻塞,直到数据从内核缓存区传到用户空间。
流程
- 应用程序发起系统调用(如recv())
- 内核等待数据并将数据拷贝到用户空间,注意这是操作系统内核要做的事情
- 系统调用返回,解除阻塞
优点
- 简单直观:模型简单,逻辑清晰
- 成熟稳定:大多数系统的早期实现
缺点
- 低效:线程在等待时无法去做其他工作
- 浪费资源:每个IO操作需要一个独立线程或者进程,无法充分利用CPU资源
- 非阻塞IO(Non-Blocking IO)
描述
- 应用程序在调用IO函数之后,即使数据尚未组装完成,函数也会立刻返回结果
- 应用程序采用轮询的方式(polling)检查数据是否可用
流程
- 应用程序发起系统调用(如recv())
- 如果数据未准备好,立刻返回一个错误码(EWOULDBLOCK)
- 应用程序需要重复调用以检查数据状态
优点
- 线程不会被阻塞:换言之,线程可以去执行其他任务
- 减少线程切换开销:避免阻塞导致的频繁的上下文切换
缺点
- 轮询浪费资源:需要不断检查数据状态,效率低
- 编程复杂:开发者需要实现轮询
- IO多路复用(IO Mutilplexing)
描述
- 使单个线程同时监视多个IO描述符(文件,网络连接等)
- 常用的系统调用有select(), poll(), epoll()
流程
- 应用程序使用多路复用系统调用注册多个IO事件
- 系统阻塞等待任一描述符就绪
- 描述符就绪后,应用程序处理该IO操作
优点
- 高效:一个线程管理多个IO操作,适合高并发的场景
- 资源利用率高:避免为每个连接分配线程
缺点:
- 复杂性增加:多路复用模型编程比较复杂
- 性能瓶颈对部分实现如 select()):当描述符数量很大时,性能下降明显。
- 信号驱动IO(Sizgnal-driven IO)
描述:
- 应用程序注册一个信号处理函数,当IO就绪时,操作系统会发送信号通知应用程序
流程:
- 应用程序注册一个信号处理函数(如 SIGIO)。
- 当 IO 就绪时,内核向应用程序发送信号。
- 应用程序通过信号处理函数完成 IO 操作。
优点:
- 非阻塞模式:线程无需轮询
- 及时响应:信号触发机制减少等待时间
缺点
- 信号处理复杂:信号的优先级、竞争条件处理难度高。
- 移植性差:不同系统对信号机制的支持程度不一致。
- 异步IO(Asynchronous IO)
描述
- 应用程序发起IO操作,立刻返回并继续执行其他任务
- 当IO操作完成,操作系用通知应用程序
流程
- 应用程序发起异步IO请求
- 内核完成数据准备和数据传输后,通知应用程序(通过回调函数)
- 应用程序处理已完成的数据
优点:
- 高效:线程无阻塞或者轮询
- 低延迟:内核和用户空间工作并行
缺点
- 编程服装:需要处理异步回调
- 移植性差:操作系统支持不一,可能影响跨平台兼容性
模型 | 阻塞 | 数据到达通知 | 线程占用 | 适用场景 | 性能 |
---|---|---|---|---|---|
阻塞IO | 是 | 否 | 每个线程阻塞 | 简单任务或者低并发场景 | 性能较低 |
非阻塞IO | 否 | 否 | 多线程轮询 | 数据频繁到达的场景 | 性能一般 |
IO多路复用 | 是(短暂阻塞) | 是 | 单线程管理多连接 | 高并发场景 | 性能较高 |
信号驱动IO | 否 | 是(信号通知) | 单线程异步响应 | 实时性要求高的场景 | 性能较高 |
异步IO | 否 | 是(回调通知) | 单线程异步完成 | 高性能并发或实时场景 | 性能最高 |
Java IO包
- IO的核心结构
JavaIO的基本概念是流(Stream),分为输入流和输出流,所谓的单向流
- 输入流(InputStream/Reader):从数据源读取数据
- 输出流(OutputStream/Reader):将数据写入目标
- 按处理类型分类
面向字节的IO
适用于二进制数据(图片,音频,视频文件)
核心类
InputStream:字节输入流的抽象类
- 主线实现类:FileInputStream,ByteArrayInputStream,BufferedInputStream
OutputStream:字节输出流的抽象类
- 主要实现类:FileOutputStream、ByteArrayOutputStream、BufferedOutputStream
try (FileInputStream fis = new FileInputStream("input".text)) {
int data;
while((data = fis.read())!= -1) {
System.out.print((char) data);
}
} catch(IOException e) {
}
面向字符的IO
适用于处理文本数据(字符流)
核心类
Reader:字符输入流的抽象类
- 主要的实现类:FileReader、BufferedReader、InputStreamReader
Writer:字符输出流的抽象类
- 主要实现类:FileWriter、BufferedWriter、OutputStreamWriter
适用场景
- 适合处理文本文件,如.txt文件
try (BufferReader br = new BufferReader(new FileReader("input.txt")) {
String line;
while((line = br.getLine()) != null) {
System.out.println(line);
}
} catch (IOException) {
}
- 按功能增强分类
(1)缓冲流(Buffered Stream):通过添加缓冲区,提高读写效率
- 字节缓冲流:BufferedInputStream,BufferedOutputStream
- 字符缓冲流:BufferedReader、BufferedWriter
(2)转换流:在字节和字符串之间转换
- InputStreamReader:将字节输入流转化为字符输入流
- OutputStreamWriter:将字节输出流转化为字符输出流
try(InputStreamReader isr = new InputStreamBuffer(new FileInputStream("input.txt"),"UTF-8");
BufferedReader br = new BufferedReader(isr)) {
String line;
while ((line = br.read()) != null) {
System.out.println(line);
}
}
(3) 数据流:用于读写Java的基本数据类型和字符串
- DataInputStream:读取基本数据类型
- DataOutputStream:写入基本数据类型
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) {
dos.writeInt(123);
dos.writeUTF("Hello")
} catch (IOException) {
}
try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
int number = dis.readInt();
String text = dis.readUTF();
} catch (IOException) {
}
(4) 对象流:用于读写对象(需要实现 Serializable 接口)
- ObjectInputStream:读取对象
- ObjectOutputStream:写入对象
class Person implement Serializable {
private static final long serialVersionUID = 1L;
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
try (ObjectOutputStream oos = new ObjectOutStream(new FileOutputStream("person.dat"))){
oos.writeObject(new Person("Alice",25));
} catch (IOException) {
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))){
Person p = (Person) ois.readObject();
} catch (IOException) {
}
(5) 文件流:用于直接操作文件
- FileInputStream/FileOutputStream: 字节流文件操作
- FileReader / FileWriter:字符流文件操作
- 按访问方式分类
(1)顺序流
按照数据的顺序从头到尾读取或写入,例如 FileInputStream。
(2)随机访问流
通过 RandomAccessFile 支持文件的随机读写操作。
try (RandomAccessFile ref = new RandomAccessFile("text.txt","rw")) {
raf.seek(10); // 定位到第10个字节
raf.write("Hello".getBytes());
} catch(IOException e) {
}
Java NIO包
Java NIO是Java在1.4版本引入的一组用于高性能IO操作的API,旨在解决传统Java IO的一些性能瓶颈。NIO提供了更加高效的、非阻塞的IO操作能力,特别适用于高并发,大规模数据传输的场景
- NIO的核心概念
(1)通道(Channel)
- 通道是双向的,同时可以执行读和写操作
- 类似于流,但比流的效率高
核心接口的实现类:
- FileChannel:用于文件数据的读写
- SocketChannel:用于网络数据的读写
- DatagramChannel:用于UDP的数据传输
- ServerSocketChannel:用于服务端的网络数据连接监听
(2)缓冲区(Buffer)
- 缓冲区是NIO数据读写的核心,数据总是从通道写入缓冲区,或者从缓冲区读取
常用的缓冲区类型:
- ByteBuffer(最常用):处理字节
- CharBuffer:处理字符
- IntBuffer,LongBuffer,DoubleBuffer等:处理对应的数据类型
缓冲区的核心属性
- capacity: 缓冲区的容量
- position: 当前的读写位置
- limit:缓冲区的操作范围上限
- filp():切换读写模式
- clear():重置缓冲区,准备写入数据
(3)多路复用器(Selector)
- Selector是NIO的核心,用于管理多个通道
- 它允许一个线程监控多个通道上的事件(如读、写、连接就绪等),实现非阻塞IO操作
支持的事件:
- OP_READ:通道可读
- OP_WRITE:通道可写
- OP_CONNECT:通道连接完成
- OP_ACCEPT: 通道接受连接
IO和NIO区别
特性 | IO | NIO |
---|---|---|
数据处理模式 | 面向流 | 面向缓冲区 |
阻塞于非阻塞 | 阻塞IO | 支持非阻塞IO |
多路复用 | 不支持 | 支持Selector实现多路复用 |
数据访问方式 | 每次读写调用系统 | 批量数据可读,效率更高 |
线程模型 | 每个连接一个线程 | 一个线程可以管理多个连接 |
NIO的主要组件和实现
(1) 通道的使用
文件通道(FileChannel):用于文件的读写操作
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelExample {
public static void main(String[] args) {
try (RandomAccessFile file = new RandomAccessFile("example.txt", "rw");
FileChannel fileChannel = file.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = fileChannel.read(buffer);
while (bytesRead != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
bytesRead = fileChannel.read(buffer);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
网络通道(SocketChannel和ServerSocketChannel):用于处理网络数据
客户端连接
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class SocketChannelExample {
public static void main(String[] args) {
try (SocketChannel socketChannel = SocketChannel.open()) {
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello Server".getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
socketChannel.read(buffer);
buffer.flip();
System.out.println("Received: " + new String(buffer.array(), 0, buffer.limit()));
} catch (IOException e) {
}
}
}
(2) 缓冲区的使用
核心方法
写入数据到缓冲区
- put(byte b): 写入一个字节
- put(byte[] src):写入一个字节数组
从缓冲区读取数据
- get(): 读取一个字节
- get(byte[] dst): 读取一个字节数组
状态切换
- filp(): 从写切换到读
- clear(): 清空缓冲区,准备重新写入
public class ByteBufferExample {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("Hello World".getBytes());
byteBuffer.flip();
while (byteBuffer.hasRemaining()) {
System.out.print((char) byteBuffer.get());
}
byteBuffer.flip();
}
}
(3) Selector的使用
示例:Selector处理多通道事件
public class SelectorExample {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
// 创建服务器通道并注册到 Selector
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(8080));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server is listening on port 8080...");
while (true) {
selector.select(); // 阻塞直到有事件发生
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (key.isAcceptable()) {
// 接受新的客户端连接
SocketChannel client = serverChannel.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 读取客户端数据
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
client.close();
} else {
buffer.flip();
client.write(buffer); // 回显数据
}
}
}
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。