第十六章 java IO设计模式总结

文章详细介绍了JAVA中的装饰器模式,如何在不改变原有对象的基础上扩展功能,以IO流中的BufferedInputStream和OutputStream为例。接着讨论了适配器模式,用于接口不兼容类的协调,如InputStreamReader和OutputStreamWriter在字符流与字节流间的转换。此外,还提到了工厂模式在NIO中的应用,如Files类和ZipFileSystem。最后,文章阐述了观察者模式在NIO文件目录监听服务中的使用,通过WatchService接口监控文件变化。
摘要由CSDN通过智能技术生成

装饰器模式

  1. 作用
    装饰器模式可以在不改变原有对象的情况下拓展其功能。
    通过组合替代继承来扩展原始类的功能,在一些继承关系比较复杂的场景,例如:IO这类场景的继承关系。这些场景中更加实用。
  2. 具体对流的作用
    对于字节流来说,FilterInputStream和FileOutputStream是装饰器模式的核心,分别用于增强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];
}

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();
}

注意:没有字符传冲文件输入流BufferedFileInputStream
因为InputStream的子类很多,继承关系较为复杂。如果对每一个子类都定制一个对应的缓冲输入流,不切实际。
补充:
ZipInputStream和ZipOutputStream分别可以增强BufferedInputStream和BufferedOutputStream的能力

BufferedInputStream bis = new BufferedInputStream(new 
FileInputStream(fileName));
ZipInputStream zis = new ZipInputStream(bis);

BufferedOutputStream bos = new BufferedOutputStream(new 
FileOutputStream(fileName));
ZipOutputStream zipOut = new ZipOutputStream(bos);

ZipInputStream 和ZipOutputStream 分别继承自InflaterInputStream 和DeflaterOutputStream。代码如下:

public
class InflaterInputStream extends FilterInputStream {
}

public
class DeflaterOutputStream extends FilterOutputStream {
}

这是装饰器模式很重要的一个特征,可以对原始类嵌套使用多个装饰器
为了实现这个效果,装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口。上述的IO相关 装饰类和原始类共同的父类是:InputStream和OutputStream
对于字符流来说,BufferedReader可以用来增强Reader子类的功能,BufferedWriter可以用来增强Writer子类的功能。

BufferedWriter bw = new BufferedWriter(new 
OutputStreamWriter(new FileOutputStream(fileName), "UTF-8"));

适配器模式

  1. 作用
    用于接口互不兼容的类的协调工作。比如生活中的电源适配器。
    适配器模式中存在被适配的对象或者类,称作:适配者(Adaptee),作用于适配者的对象或者类,称作:适配器(Adapter)。
    适配器分为对象适配器和类适配器。
    类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
  2. IO流中的字符流和字节流的接口不同
    字符流和字节流的接口不同,两者之间可以协调工作是基于适配器模式做的,准确来说是对象适配器。
    通过适配器,可以将字节流对象适配成一个字符流对象,这样的话可以直接通过字节流对象来读取或者写入字符数据

InputStreamReader 和 OutputStreamWriter 就是两个适配器(Adapter), 同时,它们两个也是字节流和字符流之间的桥梁。InputStreamReader 使用 StreamDecoder (流解码器)对字节进行解码,实现字节流到字符流的转换, OutputStreamWriter 使用StreamEncoder(流编码器)对字符进行编码,实现字符流到字节流的转换。

BufferedReader 增强 InputStreamReader 的功能(装饰器模式)

// InputStreamReader 是适配器,FileInputStream 是被适配的类
InputStreamReader isr = new InputStreamReader(new 
FileInputStream(fileName), "UTF-8");
BufferedReader bufferedReader = new BufferedReader(isr);

流解码器和流编码器部分源码如下。(感觉这块会用即可,获取StreamDecoder对象内部还是调用其他的方法。了解Decoder是流解码,解码后要读,而Encoder是流编码,编码后要写)
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();
    }
}

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);
    }
}

适配器模式和装饰器模式区别

  1. 装饰器模式更侧重于动态增强原始类的功能,需要跟原始类继承相同的抽象类或者实现相同的接口。并且,装饰器模式支持对原始类嵌套使用多个装饰器
  2. 适配器模式更侧重让接口不兼容而不能交互的类可以一起工作,当调用适配器对应的方法时,内部会调用适配器类或者适配类相关的类的方法。
    例如:
    StreamDecoder和StreamEncoder分别基于InputStream和OutputStream来获取FileChanner对象,并调用对应的read方法和write方法进行字节数据的读取和写入
StreamDecoder(InputStream in, Object lock, CharsetDecoder dec) {
    // 省略大部分代码
    // 根据 InputStream 对象获取 FileChannel 对象
    ch = getChannel((FileInputStream)in);
}

适配器和适配者两者不需要继承相同的抽象类或者实现相同的接口

FutrueTask类使用了适配器模式,Executors的内部类RunnableAdapter实现属于适配器,用于将Runnable适配成Callable
FutureTask参数包含 Runnable 的一个构造方法:

public FutureTask(Runnable runnable, V result) {
    // 调用 Executors 类的 callable 方法
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;
}

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;
    }
}

工厂模式

用于创建对象,NIO中用到了大量的工厂模式。例如:

  1. Files 类的 newInputStream 方法用于创建 InputStream 对象(静态工厂)
  2. Paths 类的 get 方法创建 Path 对象(静态工厂)
  3. ZipFileSystem 类(sun.nio包下的类,属于 java.nio 相关的一些内部实现)的 getPath 的方法创建 Path 对象(简单工厂)
    举例如下:
InputStream is Files.newInputStream(Paths.get(generatorLogoPath))

观察者模式

NIO中的文件目录监听服务使用到了观察者模式。
NIO中的文件目录监听服务基于WatchService接口和Watchable接口。前者是观察者,后者是被观察者。

Watchable接口定义了用于将对象注册到 观察者,也就是WatchService(监控服务)并绑定监听事件的方法register。通过下述代码,Path接口继承了几个接口,其中有个接口中存在方法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;
}

WatchService 用于监听文件目录的变化,同一个 WatchService 对象能够监听多个文件目录。

// 创建 WatchService 对象
WatchService watchService = 
FileSystems.getDefault().newWatchService();

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

补充:(了解即可)
Paths:通过get()方法返回一个Path对象,Path用于表示文件路径和文件。

        //以当前路径作为Path对象
        Path p = Paths.get(".");
        //使用传入的字符串返回一个Path对象
        Path p2 = Paths.get("D","ReviewIO","URL");
        //对应的路径
        System.out.println("p对象的对应路径:" + p.toString());
        System.out.println("p2对象的对应路径:" + p2.toString());
        //路径数量是以路径名的数量作为标准
        System.out.println("p路径数量:" + p.getNameCount());
        System.out.println("p2路径数量:" + p2.getNameCount());        
        //获取绝对路径
        System.out.println("p绝对路径:" + p.toAbsolutePath());
        System.out.println("p2绝对路径:" + p2.toAbsolutePath());
        //获取父路径
        System.out.println("p父路径:"  + p.getParent());
        System.out.println("p2父路径:" + p2.getParent());
        //获取p2对象的文件名或者文件目录名
        System.out.println(p2.getFileName());
        //通过Path对象返回一个分隔符对象
        Spliterator<Path> split = p2.spliterator();

Path 类 register 方法的第二个参数 events (需要监听的事件)为可变长参数,也就是说我们可以同时监听多种事件。

常用的监听事件

有 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();
}

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);
    }
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值