Java IO中的设计模式
# 装饰器模式
装饰器(Decorator)模式 可以在不改变原有对象的情况下拓展其功能。
对于字节流来说, FilterInputStream
(对应输入流)和FilterOutputStream
(对应输出流)是装饰器模式的核心,分别用于增强 InputStream
和OutputStream
子类对象的功能
常见的BufferedInputStream
(字节缓冲输入流)、DataInputStream
等等都是FilterInputStream
的子类,BufferedOutputStream
(字节缓冲输出流)、DataOutputStream
等等都是FilterOutputStream
的子类。
比如,可以通过 BufferedInputStream
(字节缓冲输入流)来增强 FileInputStream
的功能。
BufferedInputStream
构造函数如下:
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
2
3
4
5
6
7
8
9
10
11
可以看到,BufferedInputStream
的构造函数其中的一个参数就是 InputStream
。
BufferedInputStream
代码示例:
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.txt"))) {
int content;
long skip = bis.skip(2);
while ((content = bis.read()) != -1) {
System.out.print((char) content);
}
} catch (IOException e) {
e.printStackTrace();
}
2
3
4
5
6
7
8
9
# 适配器模式
适配器(Adapter Pattern)模式 主要用于接口互不兼容的类的协调工作
适配器模式中存在被适配的对象或者类称为 适配者(Adaptee) ,作用于适配者的对象或者类称为适配器(Adapter) 。适配器分为对象适配器和类适配器。类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
对象适配器
如下图所示,我们希望
Something
类拥有Target
类的行为,我们就可以编写一个适配器,将Something
组合到适配器中,然后实现Target
接口,用Something
做实现即可。通俗的理解就是接口的方法用我们聚合的类的方法实现。
Target
类
public interface Target {
void method1();
void method2();
void method3();
}
2
3
4
5
Something
类
public class Something {
public void method2() {
System.out.println("Something method2");
}
public void method3() {
System.out.println("Something method3");
}
}
2
3
4
5
6
7
8
9
SomeAdapter
类
public class SomeAdapter implements Target {
/**
* 用组合关系添加一个something完成转Target的适配
*/
private Something something;
public SomeAdapter(Something something) {
this.something = something;
}
@Override
public void method1() {
something.method2();
}
@Override
public void method2() {
something.method2();
}
@Override
public void method3() {
something.method3();
}
}
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
类适配器、
Something
类想拥有Target
的行为就编写一个适配器,继承Something
、实现Target
,用Target
方法封装Something
适配代码如下所示
public class SomeAdapter2 extends Something implements Target {
/**
* 用Something作为Target的实现从而完成适配
*/
@Override
public void method1() {
method2();
}
}
2
3
4
5
6
7
8
9
IO 流中的字符流和字节流的接口不同,它们之间可以协调工作就是基于适配器模式来做的,更准确点来说是对象适配器。通过适配器,可以将字节流对象适配成一个字符流对象,这样就可以直接通过字节流对象来读取或者写入字符数据。
InputStreamReader
和 OutputStreamWriter
就是两个适配器(Adapter), 同时,它们两个也是字节流和字符流之间的桥梁。InputStreamReader
使用 StreamDecoder
(流解码器)对字节进行解码,实现字节流到字符流的转换, OutputStreamWriter
使用StreamEncoder
(流编码器)对字符进行编码,实现字符流到字节流的转换。
InputStream
和 OutputStream
的子类是被适配者, InputStreamReader
和 OutputStreamWriter
是适配器。
// InputStreamReader 是适配器,FileInputStream 是被适配的类
InputStreamReader isr = new InputStreamReader(new FileInputStream(fileName), "UTF-8");
// BufferedReader 增强 InputStreamReader 的功能(装饰器模式)
BufferedReader bufferedReader = new BufferedReader(isr);
2
3
4
java.io.InputStreamReader
部分源码:
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java.io.OutputStreamWriter
部分源码:
public class OutputStreamWriter extends Writer {
// 用于编码的对象
private final StreamEncoder se;
public OutputStreamWriter(OutputStream out) {
super(out);
try {
// 获取 StreamEncoder 对象
se = StreamEncoder.forOutputStreamWriter(out, this, (String)null);
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
}
// 使用 StreamEncoder 对象做具体的写入工作
public void write(int c) throws IOException {
se.write(c);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
适配器模式和装饰器模式有什么区别呢?
装饰器模式 更侧重于动态地增强原始类的功能,装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口。并且,装饰器模式支持对原始类嵌套使用多个装饰器。
适配器模式 更侧重于让接口不兼容而不能交互的类可以一起工作,当我们调用适配器对应的方法时,适配器内部会调用适配者类或者和适配类相关的类的方法,这个过程透明的。就比如说 StreamDecoder
(流解码器)和StreamEncoder
(流编码器)就是分别基于 InputStream
和 OutputStream
来获取 FileChannel
对象并调用对应的 read
方法和 write
方法进行字节数据的读取和写入。
FutureTask
类使用了适配器模式,Executors
的内部类RunnableAdapter
实现属于适配器,用于将Runnable
适配成Callable
。FutureTask
参数包含Runnable
的一个构造方法:
public FutureTask(Runnable runnable, V result) {
// 调用 Executors 类的 callable 方法
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
2
3
4
5
Executors
中对应的方法和适配器:
// 实际调用的是 Executors 的内部类 RunnableAdapter 的构造方法
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
// 适配器
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 工厂模式
工厂模式用于创建对象,NIO
中大量用到了工厂模式,比如 Files
类的 newInputStream
方法用于创建 InputStream
对象(静态工厂)、 Paths
类的 get
方法创建 Path
对象(静态工厂)、ZipFileSystem
类的 getPath
的方法创建 Path
对象(简单工厂)。
InputStream is = Files.newInputStream(Paths.get(generatorLogoPath))
# 观察者模式
NIO
中的文件目录监听服务使用到了观察者模式。NIO
中的文件目录监听服务基于 WatchService
接口和 Watchable
接口。WatchService
属于观察者,Watchable
属于被观察者。Watchable
接口定义了一个用于将对象注册到 WatchService
(监控服务) 并绑定监听事件的方法 register
。
public interface Path
extends Comparable<Path>, Iterable<Path>, Watchable{
}
public interface Watchable {
WatchKey register(WatchService watcher,
WatchEvent.Kind<?>[] events,
WatchEvent.Modifier... modifiers)
throws IOException;
}
2
3
4
5
6
7
8
9
10
WatchService
用于监听文件目录的变化,同一个 WatchService
对象能够监听多个文件目录。
// 创建 WatchService 对象
WatchService watchService = FileSystems.getDefault().newWatchService();
// 初始化一个被监控文件夹的 Path 类:
Path path = Paths.get("workingDirectory");
// 将这个 path 对象注册到 WatchService(监控服务) 中去
WatchKey watchKey = path.register(
watchService, StandardWatchEventKinds...);
2
3
4
5
6
7
8
Path
类 register
方法的第二个参数 events
(需要监听的事件)为可变长参数,也就是说我们可以同时监听多种事件。
WatchKey register(WatchService watcher,
WatchEvent.Kind<?>... events)
throws IOException;
2
3
常用的监听事件有 3 种:
StandardWatchEventKinds.ENTRY_CREATE
:文件创建。StandardWatchEventKinds.ENTRY_DELETE
: 文件删除。StandardWatchEventKinds.ENTRY_MODIFY
: 文件修改。
register
方法返回 WatchKey
对象,通过WatchKey
对象可以获取事件的具体信息比如文件目录下是创建、删除还是修改了文件、创建、删除或者修改的文件的具体名称是什么。
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
// 可以调用 WatchEvent 对象的方法做一些事情比如输出事件的具体上下文信息
}
key.reset();
}
2
3
4
5
6
7
WatchService
内部是通过一个 daemon thread(守护线程)采用定期轮询的方式来检测文件的变化,简化后的源码如下所示。
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);
}
}
}
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