Dawn's Blogs

分享技术 记录成长

0%

Java面试之IO (2) IO设计模式

IO 设计模式

在 Java IO 中,所用到的设计模式总结。

装饰器模式

装饰器(Decorator)模式 可以在不改变原有对象的情况下拓展其功能。

对于字节流,FilterInputStream 和 FilterOutputStream 是装饰器模式的核心,用于增强 InputStream 和 OutputStream 子类对象的功能。

对于装饰器类而言,构造器的输入为被装饰类,同时可以嵌套多个装饰器。所以,装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口

举例,BufferedInputStream 为 InputStream 提供了缓冲功能,ZipOutputStream 为 OutputStream 提供了 zip 压缩功能,他们的构造函数都是输出一个 InputStream 或者 OutputStream。

适配器模式

适配器(Adapter Pattern)模式 主要用于接口互不兼容的类的协调工作,适配器模式中存在被适配的对象或者类称为适配者(Adaptee) ,作用于适配者的对象或者类称为适配器(Adapter) 。适配器分为对象适配器和类适配器。类适配器使用继承关系来实现,对象适配器使用组合关系来实现。

IO 流中的字符流和字节流的接口不同,它们之间可以协调工作就是基于适配器模式来做的,更准确点来说是对象适配器。通过适配器,可以将字节流对象适配成一个字符流对象,可以直接通过字节流对象来读取或者写入字符数据。

InputStreamReader OutputStreamWriter

之前提到的 InputStreamReaderOutputStreamWriter 就是两个适配器,是字节流和字符流之间的桥梁

  • InputStreamReader 使用 StreamDecoder(流解码器)对字节进行解码,实现字节流到字符流的转换
  • OutputStreamWriter 使用 StreamEncoder(流编码器)对字节进行编码,实现字符流到字节流的转换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class InputStreamReader extends Reader {
//用于解码的对象
private final StreamDecoder sd;
public InputStreamReader(InputStream in) {
super(in);
try {
// 获取 StreamDecoder 对象
sd = StreamDecoder.forInputStreamReader(in, this, (String)null);
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
}
// 使用 StreamDecoder 对象做具体的读取工作
public int read() throws IOException {
return sd.read();
}
}

工厂模式

工厂模式用于创建对象,NIO 中大量用到了工厂模式,比如 Files 类的 newInputStream 方法用于创建 InputStream 对象(静态工厂)、 Paths 类的 get 方法创建 Path 对象(静态工厂)、ZipFileSystem 类(sun.nio包下的类,属于 java.nio 相关的一些内部实现)的 getPath 的方法创建 Path 对象(简单工厂)。

1
InputStream is = Files.newInputStream(Paths.get(generatorLogoPath))

观察者模式

NIO 中的文件目录监听服务使用到了观察者模式。NIO 中的文件目录监听服务基于 WatchService 接口和 Watchable 接口。WatchService 属于观察者,Watchable 属于被观察者。使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建 WatchService 对象
WatchService watchService = FileSystems.getDefault().newWatchService();

// 初始化一个被监控文件夹的 Path 类:
Path path = Paths.get("workingDirectory");
// 将这个 path 对象注册到 WatchService(监控服务) 中去
WatchKey watchKey = path.register(watchService, StandardWatchEventKinds...);

while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
// 可以调用 WatchEvent 对象的方法做一些事情比如输出事件的具体上下文信息
}
key.reset();
}

Watchable

Path 实现了 Watchable 接口,说明 Path 是被观察者。Watchable 接口中,watcher 指定了观察者,events 指定了被观察的事件(监听事件)。常用的监听事件:StandardWatchEventKinds.ENTRY_CREATE:文件创建;StandardWatchEventKinds.ENTRY_DELETE : 文件删除;StandardWatchEventKinds.ENTRY_MODIFY : 文件修改。

1
2
3
4
5
6
7
8
9
10
public interface Path
extends Comparable<Path>, Iterable<Path>, Watchable{
}

public interface Watchable {
WatchKey register(WatchService watcher,
WatchEvent.Kind<?>[] events,
WatchEvent.Modifier... modifiers)
throws IOException;
}

WatchService

WatchService 内部通过一个 daemon thread(守护线程)采用定期轮询的方式来检测文件的变化,伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class PollingWatchService
extends AbstractWatchService
{
// 定义一个 daemon thread(守护线程)轮询检测文件变化
private final ScheduledExecutorService scheduledExecutor;

PollingWatchService() {
scheduledExecutor = Executors
.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}});
}

void enable(Set<? extends WatchEvent.Kind<?>> events, long period) {
synchronized (this) {
// 更新监听事件
this.events = events;

// 开启定期轮询
Runnable thunk = new Runnable() { public void run() { poll(); }};
this.poller = scheduledExecutor
.scheduleAtFixedRate(thunk, period, period, TimeUnit.SECONDS);
}
}
}