IO模型
IO基础知识
字节流
字节流用于处理二进制数据,如图像、音频等。常见的字节流类有:
- InputStream: 用于读取字节数据的抽象类。常用的子类有:
FileInputStream
: 从文件中读取字节数据。ByteArrayInputStream
: 从字节数组中读取数据。DataInputStream
: 允许以机器无关的方式读取基本数据类型。
- OutputStream: 用于写入字节数据的抽象类。常用的子类有:
FileOutputStream
: 将字节数据写入文件。ByteArrayOutputStream
: 将数据写入字节数组。DataOutputStream
: 允许以机器无关的方式写入基本数据类型。
字符流
字符流用于处理字符数据,适合文本文件等数据 。主要的字符流类有:
- Reader: 用于读取字符数据的抽象类。常用的子类有:
FileReader
: 从文件中读取字符数据。BufferedReader
: 提供缓冲机制,增加读取字符、数组和行的效率。InputStreamReader
: 将字节流转换为字符流。
- Writer: 用于写入字符数据的抽象类。常用的子类有:
FileWriter
: 将字符数据写入文件。BufferedWriter
: 提供缓冲机制,增加写入字符、数组和行的效率。OutputStreamWriter
: 将字符流转换为字节流。
字节流和字符流的主要区别
使用字节流: 如果要处理非文本数据或需要保持原始字节的准确性,比如处理二进制文件、图片、音频、视频等,或者在网络传输时需要处理字节数据而不涉及字符编码问题。
使用字符流: 如果要处理文本数据,特别是需要支持国际化的应用,字符流的编码转换功能使得处理不同语言的文本更加方便和安全。字符流在处理文本时还能自动进行字符编码解码,减少编码问题导致的数据丢失或错误。
字节缓冲流
字节缓冲流在标准字节流的基础上增加了缓冲机制,用于提高I/O操作的效率。这些缓冲流类位于java.io
包中。
- BufferedInputStream: 提供缓冲功能的字节输入流。通过先将数据读入缓冲区来减少磁盘I/O操作的次数。
- 例如:
BufferedInputStream(InputStream in)
和BufferedInputStream(InputStream in, int size)
。
- 例如:
- BufferedOutputStream: 提供缓冲功能的字节输出流。数据首先写入缓冲区,只有在缓冲区满或调用
flush()
时,数据才会写入底层的输出流。- 例如:
BufferedOutputStream(OutputStream out)
和BufferedOutputStream(OutputStream out, int size)
。
- 例如:
字符缓冲流
字符缓冲流类似于字节缓冲流,但它们处理的是字符数据。缓冲机制也适用于这些流,主要用于提高读写文本数据的效率。
- BufferedReader: 提供缓冲功能的字符输入流。它支持一次读取一个字符、一个字符数组或一行文本。
- 例如:
BufferedReader(Reader in)
和BufferedReader(Reader in, int size)
。
- 例如:
- BufferedWriter: 提供缓冲功能的字符输出流。字符数据先写入缓冲区,然后再批量写入底层输出流。
- 例如:
BufferedWriter(Writer out)
和BufferedWriter(Writer out, int size)
。
- 例如:
打印流
打印流提供了一种方便的方式来格式化输出数据,支持自动刷新和无异常的打印操作。它们可以处理不同类型的数据,包括对象、文本、数值等。
- PrintStream: 字节打印流,继承自
OutputStream
。它支 持自动刷新和不抛出I/O异常的特性。常用于打印字节数据或文本数据,如标准输出(System.out
)。- 例如:
PrintStream(OutputStream out)
。
- 例如:
- PrintWriter: 字符打印流,继承自
Writer
。与PrintStream
类似,但处理字符数据。它也支持自动刷新和无异常的打印操作。- 例如:
PrintWriter(Writer out)
和PrintWriter(OutputStream out)
。
- 例如:
随机访问流
随机访问流允许在文件的任意位置读取和写入数据。它既可以作为输入流,也可以作为输出流,是一个功能非常强大的流类。
-
RandomAccessFile
: 既支持字节输入也支持字节输出的类。它允许以随机访问方式读取和写入文件内容,支持移动文件指针来指定读写位置。
- 例如:
RandomAccessFile(String name, String mode)
,其中mode
参数指定访问模式(如"r"
只读,"rw"
读写)。
- 例如:
设计模式
1. 装饰者模式(Decorator Pattern)
概念: 装饰者模式用于动态地为对象添加功能,而不改变其结构。在Java I/O中,装饰者模式被广泛用于创建不同的流,通过将流对象包装在其他流中,增强其功能。例如:
BufferedInputStream
和BufferedOutputStream
分别包装了InputStream
和OutputStream
,提供了缓冲功能以提高读取和写入效率。DataInputStream
和DataOutputStream
包装了基础字节流,提供了读取和写入基本数据类型的功能。
InputStream in = new FileInputStream("file.txt");
InputStream bufferedIn = new BufferedInputStream(in);
2. 适配器模式(Adapter Pattern)
概念: 适配器模式用于使接口不兼容的类能够一起工作。在Java I/O中,适配器模式经常用于字节流和字符流之间的转换。
示例:
InputStreamReader
和OutputStreamWriter
分别将字节流适配为字符流,使得字符流能够处理底层的字节数据。
Reader reader = new InputStreamReader(new FileInputStream("file.txt"), "UTF-8");
Writer writer = new OutputStreamWriter(new FileOutputStream("file.txt"), "UTF-8");
3. 工厂模式(Factory Pattern)
概念: 工厂模式用于创建对象的接口,使得子类决定实例化哪个类。在I/O操作中,工厂模式可以用于封装流的创建逻辑,使得代码更灵活和易于扩展。示 例:
- 设计一个工厂类来创建不同类型的流,比如文件流、网络流等。这可以让代码更具可维护性和可扩展性。
public class StreamFactory {
public static InputStream createFileInputStream(String filename) throws FileNotFoundException {
return new FileInputStream(filename);
}
public static OutputStream createFileOutputStream(String filename) throws FileNotFoundException {
return new FileOutputStream(filename);
}
}
4. 策略模式(Strategy Pattern)
概念: 策略模式用于定义一系列算法,并将它们封装在独立的类中,使它们可以互换。在Java I/O中,可以使用策略模式来处理不同的文件格式或编码方式。示例:
- 设计一组类,每个类处理特定的文件格式或数据流的压缩/解压缩策略。然后使用策略模式动态选择合适的处理类。
public interface CompressionStrategy {
OutputStream compress(OutputStream data) throws IOException;
}
public class ZipCompressionStrategy implements CompressionStrategy {
public OutputStream compress(OutputStream data) throws IOException {
return new ZipOutputStream(data);
}
}
public class Compressor {
private CompressionStrategy strategy;
public Compressor(CompressionStrategy strategy) {
this.strategy = strategy;
}
public void compress(OutputStream data) throws IOException {
strategy.compress(data);
}
}
五种模型
UNIX 系统下, IO 模型一共有 5 种:同步阻塞 I/O、同步非阻塞 I/O、I/O 多路复用、信号驱动 I/O 和异步 I/O。
阻塞I/O(Blocking I/O)
- 在阻塞I/O模型中,当一个I/O操作被发起时,调用该操作的进程会被阻塞,直到I/O操作完成为止。也就是说,进程会等待I/O操作完成后才能继续执行其他任务。
- 这种模型简单直观,但容易导致资源的浪费,因为在等待期间,CPU 和其他资源可能处于闲置状态。
非阻塞I/O(Non-blocking I/O)
- 非阻塞I/O模型允许进程在发起I/O操作后立即返回,而不必等待I/O操作完成。进程可以继续执行其他任务,并在稍后检查I/O操作的状态。
- 这种模型可以提高系统的并发性,但也需要进程不断地检查I/O操作的状态(即轮询),这可能会带来额外的开销。
I/O多路复用(I/O Multiplexing)
- I/O多路复用允许一个进程同时监视多个I/O描述符(如文件描述符、套接字等)的状态,并在其中一个或多个描述符就绪时进行相应处理。
- 常用的多路复用机制包括
select
、poll
、epoll
(Linux)、kqueue
(FreeBSD)等。 - 这种模型适合处理大量I/O操作的场景,如服务器处理多个客户端连接。
信号驱动I/O(Signal-driven I/O)
- 信号驱动I/O模型中,进程可以通过设置信号处理程序来处理I/O事件。当I/O操作就绪时,操作系统会发送信号通知进程。
- 这种模型类似于非阻塞I/O,但减少了轮询的开销,进程可以在接收到信号时才进行相应处理。
异步I/O(Asynchronous I/O,AIO)
- 在异步I/O模型中,进程发起I/O操作后可以立即继续执行其他任务,操作系统在I/O操作完成后通知进程。
- 异步I/O提供了更高的并发性和效率,因为它不需要进程等待或轮询,可以在I/O操作完成时进行回调或信号通知。
- 这种模型在需要高性能和高并发的应用中非常有用。
NIO核心
1. 缓冲区(Buffer)
- 缓冲区是一个线性、有限的存储数据的容器。所有的NIO数据操作都通过缓冲区来进行。
- 常用的缓冲区类型包括:
ByteBuffer
(字节缓冲区)、CharBuffer
(字符缓冲区)、IntBuffer
(整数缓冲区)等。它们对应不同的数据类型。 - 缓冲区有三个关键属性:
- 容量(Capacity):缓冲区的最大存储容量,一旦设置就无法改变。
- 位置(Position):指示下一个要读或写的数据单元的位置。
- 限制(Limit):在读模式下,表示缓冲区中可读取的元素数;在写模式下,表示可以写入的最大元素数。
2. 通道(Channel)
- 通道是能够读写数据的双向通道。它类似于传统I/O中的流,但有更多的功能,如非阻塞读写。
- 常见的通道包括:
- FileChannel:用于文件I/O。
- SocketChannel:用于网络I/O。
- ServerSocketChannel:用于服务器端的网络I/O。
- DatagramChannel:用于UDP协议的网络I/O。
- 通道通常与缓冲区结合使用,通过缓冲区来读写数据。
3. 选择器(Selector)
- 选择器允许一个线程管理多个通道。选择器是NIO的核心,支持非阻塞I/O。
- 一个通道可以注册到一个选择器上,并且可以注册多个通道。选择器使用一个事件驱动机制,当一个通道有事件(如读就绪、写就绪、连接建立等)时,选择器会通知注册的通道。
- 常用的方法包括:
select()
:阻塞直到至少有一个通道就绪。selectNow()
:非阻塞地检查通道是否有事件发生。select(long timeout)
:阻塞指定的时间,直到至少有一个通道就绪或超时。
4. 选择键(SelectionKey)
- 选择键表示通道和选择器之间的注册关系。当通道注册到选择器时,会产生一个选择键。
- 选择键包含关于通道的多个信息,如通道注册的事件、通道的I/O操作的兴趣集合,以及一些附加的自定义对象。
- 事件类型包括:
OP_READ
(读就绪)、OP_WRITE
(写就绪)、OP_CONNECT
(连接完成)、OP_ACCEPT
(连接接收)。
5. 异步和非阻塞
- NIO的设计支持非阻塞I/O,这意味着线程不必阻塞等待I/O操作完成。非阻塞I/O允许一个线程同时管理多个I/O通道,提高了系统的并发处理能力。
- 在非阻塞模式下,I/O操作立即返回。如果操作不能立即完成,它们返回0或-1,表示需要稍后重试。