IO 即 Input/0utput ,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储 (比如数据库,文件,远程主机)的过程即输出。
数据传输过程类似于水流,因此称为 IO 流。
Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的:
InputStream
/Reader
: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。OutputStream
/Writer
: 所有输出流的基类,前者是字节输出流,后者是字符输出流装饰器模式是一种结构型设计模式,允许在运行时动态地将责任附加到对象上。
是一种通过组合替代继承来扩展原始类的功能,可以在不改变原有对象的情况下增强其功能的模式。
// 输入
// 将FileInputStream对象 装饰 成了一个带有缓冲区的输入流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName));
// 将带有缓冲区的输入流对象 再次装饰 成了一个能够处理ZIP文件的输入流对象
ZipInputStream zis = new ZipInputStream(bis);
// 输出
// 将FileOutputStream对象 装饰 成了一个带有缓冲区的输出流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileName));
// 将带有缓冲区的输出流对象 再次装饰 成了一个能够处理ZIP文件的输出流对象。
ZipOutputStream zipOut = new ZipOutputStream(bos);
其中 BufferedInputStream、ZipInputStream、BufferedOutputStream 和 ZipOutputStream 都是 Java 中的装饰器类。
可以用于将不兼容的类或接口转换为兼容的类或接口,从而使它们能够协同工作。
适配器模式中存在被适配的对象或者类称为 适配者(Adaptee) ,作用于适配者的对象或者类称为适配器(Adapter) 。
// InputStreamReader 是适配器,用于将 FileInputStream 实现的字节流接口适配成字符流接口
InputStreamReader isr = new InputStreamReader(new FileInputStream(fileName), "UTF-8");
// BufferedReader 增强 InputStreamReader 的功能(装饰器模式)
BufferedReader bufferedReader = new BufferedReader(isr);
适配器模式和装饰器模式虽然都属于结构型设计模式,但是它们的设计目的和实现方式有所不同。
设计目的
实现方式
适配器模式通常是通过一个适配器类来转换接口,而装饰器模式通常是通过继承或者组合的方式来实现功能的添加。
Java IO 中的工厂模式指的是:通过工厂类来创建不同类型的输入输出流对象。从而实现更加灵活的 IO 操作。
常用的 IO 工厂模式有 InputStreamFactory、OutputStreamFactory、ReaderFactory 和 WriterFactory 等。
代码示例如下:
public class IOFactoryDemo {
public static void main(String[] args) {
// 创建输入流工厂对象
InputStreamFactory inputStreamFactory = new FileInputStreamFactory();
// 创建输出流工厂对象
OutputStreamFactory outputStreamFactory = new FileOutputStreamFactory();
// 创建字符输入流工厂对象
ReaderFactory readerFactory = new FileReaderFactory();
// 创建字符输出流工厂对象
WriterFactory writerFactory = new FileWriterFactory();
// 创建文件输入流对象
InputStream inputStream = inputStreamFactory.createInputStream("C:\\myfiles\\input.txt");
// 创建文件输出流对象
OutputStream outputStream = outputStreamFactory.createOutputStream("C:\\myfiles\\output.txt");
// 创建字符输入流对象
Reader reader = readerFactory.createReader("C:\\myfiles\\input.txt");
// 创建字符输出流对象
Writer writer = writerFactory.createWriter("C:\\myfiles\\output.txt");
// 从文件输入流中读取数据
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// 向文件输出流中写入数据
try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
String data = "Hello, world!";
byte[] bytes = data.getBytes();
bufferedOutputStream.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
// 从字符输入流中读取数据
try (BufferedReader bufferedReader = new BufferedReader(reader)) {
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// 向字符输出流中写入数据
try (BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
String data = "Hello, world!";
bufferedWriter.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用工厂模式的好处是什么?
通过工厂模式创建输入输出流对象,可以避免直接使用具体的类来创建对象,降低了客户端和具体类的耦合度,提高了系统的灵活性和扩展性。此外,工厂方法还可以对输入输出流对象进行统一管理和控制,从而实现一些特定的功能,如数据压缩、加密等。
IO 观察者模式是一种设计模式,用于监控文件或目录的变化。
在 Java 中,可以使用 java.nio.file
包中的 WatchService
类来实现 IO 观察者模式。
WatchService
类充当观察者,用于监控指定目录中的文件或子目录的变化。当目录中的文件或子目录发生变化时,WatchService
会通过 Path 事件通知注册的观察者。
具体地说,使用 WatchService
实现 IO 观察者模式的步骤如下:
创建一个 WatchService
对象,通过 Path
对象的 register()
方法将其注册到需要监控的目录中。
创建一个线程,不断从 WatchService
对象中获取事件,并处理这些事件。
当 WatchService
对象检测到目录中的文件或子目录发生变化时,会生成一个 Path
事件,并将其添加到 WatchService
对象的事件队列中。
在处理事件的线程中,可以通过 WatchEvent
对象获取事件类型、事件发生的路径等信息,并根据这些信息进行相应的处理。
/**
* 文件观察者类,用于监测指定目录中文件的变化
*/
public class FileWatcher implements Runnable {
private Path path; // 目录路径
private WatchService watchService; // WatchService对象用于监测目录中文件的变化
/**
* 构造函数,创建一个FileWatcher对象
* @param path 目录路径
* @throws IOException 抛出IO异常
*/
public FileWatcher(Path path) throws IOException {
this.path = path;
// 获取默认的WatchService对象
this.watchService = FileSystems.getDefault().newWatchService();
// 注册监测事件类型
this.path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
}
/**
* run方法,用于启动监测线程
*/
@Override
public void run() {
try {
while (true) {
// 获取WatchKey对象,它代表了被监测的目录中的事件队列
WatchKey key = watchService.take();
// 遍历事件队列
for (WatchEvent<?> event : key.pollEvents()) {
// 输出事件类型和受影响的文件名
System.out.println("Event kind: " + event.kind() + ". File affected: " + event.context() + ".");
}
// 重置WatchKey对象,以便继续监测
key.reset();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* main方法,用于启动文件监测程序
*/
public static void main(String[] args) throws IOException {
// 创建监测的目录路径
Path path = Paths.get("/path/to/monitor");
// 创建FileWatcher对象
FileWatcher fileWatcher = new FileWatcher(path);
// 创建监测线程,并启动
Thread thread = new Thread(fileWatcher);
thread.start();
}
}
IO 观察者模式可以用于监控文件或目录的变化,从而实现一些特定的功能,如自动备份、自动同步等。在实际开发中,IO 观察者模式可以提高代码的可维护性和可扩展性,使得代码更加灵活。
Java 中有 3 种常见 IO 模型:
BIO 属于同步阻塞 IO 模型 。
同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。
NIO 属于同步非阻塞 IO 模型
NIO(New I/O)是 Java NIO(New Input/Output)API 的简称,是 Java SE 1.4 中引入的一组新的 I/O API,用于替代 Java 标准 I/O API(java.io 包)中的一部分功能。
NIO 提供了一种基于通道(Channel)和缓冲区(Buffer)的 I/O 模型,相较于传统的基于流的 I/O 模型,它具有更高的性能和更好的扩展性。
在同步非阻塞I/O模型中,应用程序发起 read 调用后(轮询操作),线程不会被阻塞。相反,线程会立即返回,并继续执行后续的操作,而不必等待数据从内核空间拷贝到用户空间。
通过不断发起 read 调用来检查是否有新的数据可用
但是它可以通过多路复用技术来实现高效的 IO 操作,减少无效的系统调用,减少了对 CPU 资源的消耗。
IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。
AIO 是异步 IO 模型
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
它们主要的区别在于数据的读取和处理方式不同。
BIO(Blocked IO):**BIO 是同步阻塞 I/O 模型。**在该模型下,所有的 I/O 操作都是阻塞的,即当一个线程在进行 I/O 操作时,它会一直等待直到数据准备就绪或者发生异常。(只会发起一次 read 调用,但会阻塞)
BIO 模型适用于连接数比较少且通信线程比较短的场景,例如传统的客户端/服务器模型。
NIO(Non-Blocked IO):**NIO 是同步非阻塞 I/O 模型。**在该模型下,所有的 I/O 操作都是非阻塞的,即当一个线程在进行 I/O 操作时,它会立即返回一个特定的状态(通常是非阻塞状态,如EAGAIN或EWOULDBLOCK),表示当前没有可用的数据,而不会等待数据准备就绪。(会一直发起 read 调用)
NIO 模型适用于连接数比较多且通信线程比较长的场景,例如聊天室和在线游戏。
AIO(Asynchronous IO):**AIO 是异步非阻塞 I/O 模型。**在该模型下,所有的 I/O 操作都是异步的,即当一个线程在进行 I/O 操作时,它不需要等待数据准备就绪,而是通过回调函数的方式来处理数据。(只会发起一次 read 调用)
AIO 模型适用于连接数非常多且通信线程比较长的场景,例如高性能的网络服务器。
总的来说,
Java基础常见面试题总结(下)