装饰模式

修饰模式(装饰模式),是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。

装饰模式的UML如下所示:

装饰模式中有四个角色:

  • Component 抽象构件,最基本、最核心、最原始的接口或抽象类
  • ConcreteComponent 具体构件的引用
  • Decorator 装饰角色, 持有对构件的引用
  • ConcreteDecorator 具体装饰角色

Java IO中的装饰模式

Java IO流就是装饰模式的典型应用。

与装饰模式中角色对应的类如下:

  • Component:InputStreamOutputStream
  • ConcreteComponent: FileInputStreamPipeInputStreamByteArrayInputStream ...
  • Decorator:FilterInputStreamFilterOutputStream
  • ConcreteDecorator: DataInputStreamBufferedInputStreamLineNumberInputStream...

FilterInputStreamFilterOutputStream做的事情很简单,只是持有了一个Stream的引用并做了代理:

package java.io;

public
class FilterInputStream extends InputStream { protected volatile InputStream in; protected FilterInputStream(InputStream in) {
this.in = in;
} public int read() throws IOException {
return in.read();
} //...省略掉一些方法
}

BufferedInputStream

来看下BufferedInputStream的代码(当然只是一部分):

package java.io;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public
class BufferedInputStream extends FilterInputStream {
private static int DEFAULT_BUFFER_SIZE = 8192; protected volatile byte buf[]; protected int count; protected int pos; protected int markpos = -1; protected int marklimit; 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];
} private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0; /* no mark: throw away the buffer */
else if (pos >= buffer.length) /* no room left in buffer */
if (markpos > 0) { /* can throw away early part of the buffer */
int sz = pos - markpos;
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else if (buffer.length >= MAX_BUFFER_SIZE) {
throw new OutOfMemoryError("Required array size too large");
} else { /* grow buffer */
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { throw new IOException("Stream closed");
}
buffer = nbuf;
}
count = pos;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
} public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
} public synchronized int read(byte b[], int off, int len)
throws IOException
{
getBufIfOpen(); // Check for closed stream
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
} int n = 0;
for (;;) {
int nread = read1(b, off + n, len - n);
if (nread <= 0)
return (n == 0) ? nread : n;
n += nread;
if (n >= len)
return n;
// if not closed but no bytes available, return
InputStream input = in;
if (input != null && input.available() <= 0)
return n;
}
}
}
  • BufferedInputStream中有一个byte数组作为缓存,存放从制定的InputStream中读出的字节;
  • 它的read放回会先查看buf数组中是否还有可读的字节,如果没有就先调用一次fill()方法从指定的stream中读取字节到buf数组中(或者直接去stream中读取足够的字节,再调用fill()方法);
  • BufferedInputStream支持mark,fill()方法会在buf中保留markpos到pos的这个区间内(包括markpos,不包括pos)的字节,当然前提是markpos有效;
  • 当markpos为0,buf数组中没有空间,buf数组的长度小于等于pos并小于 marklimit和MAX_BUFFER_SIZE,buf将被一个长度为 marklimit、MAX_BUFFER_SIZE和 2 * p中较小值的数组代替(原数组中的字节会被拷贝)。

关于mark的问题

BufferedInputStreammark()方法是这样的:

    /**
* See the general contract of the <code>mark</code>
* method of <code>InputStream</code>.
*
* @param readlimit the maximum limit of bytes that can be read before
* the mark position becomes invalid.
* @see java.io.BufferedInputStream#reset()
*/
public synchronized void mark(int readlimit) {
marklimit = readlimit;
markpos = pos;
}

按照doc的意思,markpos应该在读取的字节数超过了readlimit的时候就应该失效。

但是实际上,只有fill方法中的这一段代码让markpos失效了:

if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
}

也就是说,如果marklimit小于buf数组长度,markpos是不会失效的:

public static void main(String[] args) throws IOException {
byte[] bytes = new byte[]{0, 1, 2, 3};
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
BufferedInputStream bin = new BufferedInputStream(in);
//如果制定了size为1,这段代码将会报错
//BufferedInputStream bin = new BufferedInputStream(in, 1);
bin.mark(1);
bin.read();
bin.read();
bin.reset();
}

当然,之前也有提到,如果markpos为0, buf是有可能扩容的。

参考资料

JDK8源码

《设计模式之禅》第二版

修饰模式

装饰模式和Java IO的更多相关文章

  1. Java IO设计模式(装饰模式与适配器模式)

    01. 装饰模式 1. 定义 Decorator装饰器,就是动态地给一个对象添加一些额外的职责,动态扩展,和下面继承(静态扩展)的比较.因此,装饰器模式具有如下的特征: 它必须持有一个被装饰的对象(作 ...

  2. java.io包下适配和装饰模式的使用

    如java.io.LineNumberInputStream(deprecated),是装饰模式(decorate)的实现: 如java.io.OutputStreamWriter,是适配器模式(ad ...

  3. java.IO输入输出流:过滤流:buffer流和data流

    java.io使用了适配器模式装饰模式等设计模式来解决字符流的套接和输入输出问题. 字节流只能一次处理一个字节,为了更方便的操作数据,便加入了套接流. 问题引入:缓冲流为什么比普通的文件字节流效率高? ...

  4. java io 流分类表

    Java输入/输出流体系中常用的流分类(表内容来自java疯狂讲义) 注:下表中带下划线的是抽象类,不能创建对象.粗体部分是节点流,其他就是常用的处理流. 流分类 使用分类 字节输入流 字节输出流 字 ...

  5. Java IO 装饰者模式

    装饰模式(Decorator) 装饰模式又名包装(Wrapper)模式. 装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 装饰模式通过创建一个包装对象,也就是装饰,来包裹真实的 ...

  6. JAVA IO 序列化与设计模式

    ➠更多技术干货请戳:听云博客 序列化 什么是序列化 序列化:保存对象的状态 反序列化:读取保存对象的状态 序列化和序列化是Java提供的一种保存恢复对象状态的机制 序列化有什么用 将数据保存到文件或数 ...

  7. Java IO流题库

    一.    填空题 Java IO流可以分为   节点流   和处理流两大类,其中前者处于IO操作的第一线,所有操作必须通过他们进行. 输入流的唯一目的是提供通往数据的通道,程序可以通过这个通道读取数 ...

  8. 【JAVA IO流之字符流】

    一.概述. java对数据的操作是通过流的方式.java用于操作流的对象都在IO包中.流按照操作数据不同分为两种,字节流和字符流.流按照流向分为输入流,输出流. 输入输出的“入”和“出”是相当于内存来 ...

  9. java.io包详细解说

    转自:http://hzxdark.iteye.com/blog/40133 hzxdark的博客 我不知道各位是师弟师妹们学java时是怎样的,就我的刚学java时的感觉,java.io包是最让我感 ...

随机推荐

  1. python 日期时间处理

    # 获取日期: import datetime #调用事件模块 today =datetime.date.today() #获取今天日期 deltadays =datetime.timedelta(d ...

  2. 【转】.NET中的三种Timer的区别和用法

    最近正好做一个WEB中定期执行的程序,而.NET中有3个不同的定时器.所以正好研究研究.这3个定时器分别是: //1.实现按用户定义的时间间隔引发事件的计时器.此计时器最宜用于 Windows 窗体应 ...

  3. CALayer的上动画的暂停和恢复

    CHENYILONG Blog CALayer上动画的暂停和恢复 #pragma mark 暂停CALayer的动画-(void)pauseLayer:(CALayer*)layer{CFTimeIn ...

  4. laravel带条件查询手动分页

    后台php代码: //手动分页 $users = $kaoqin; //打算输出的数组,二维 $perPage = 10; if ($request->has('page')) { $curre ...

  5. NYOJ 119 士兵杀敌(三) (线段树)

    题目链接 描述 南将军统率着N个士兵,士兵分别编号为1~N,南将军经常爱拿某一段编号内杀敌数最高的人与杀敌数最低的人进行比较,计算出两个人的杀敌数差值,用这种方法一方面能鼓舞杀敌数高的人,另一方面也算 ...

  6. 关于runOnUiThread()与Handler两种更新UI的方法

    在Android开发过程中,常需要更新界面的UI.而更新UI是要主线程来更新的,即UI线程更新.如果在主线线程之外的线程中直接更新页面显示常会报错.抛出异常:android.view.ViewRoot ...

  7. 关于bcb调用动态库,contains invalid OMF record, type 0x21 (possibly COFF)问题

    今天用C++Builder6.0 调用三方lib文件时,编译的时候出现如下错误: “contains invalid OMF record, type 0x21 (possibly COFF)” 才知 ...

  8. docker修改docker0 mtu

    由于docker宿主机设置了mtu造成docker镜像中mtu和宿主机mtu不匹配,大包后网络不同.所以需要设置docker0的mtu. 1.修改docker.service vi /usr/lib/ ...

  9. 列表CListCtrl类使用

    CListCtrl是列表控件类,列表控件的每一行叫做一个item,每一列叫做一个subitem.每一行和每一列都有个ID号,可以确定唯一的单元格. 最近使用了这个控件,有心得总结如下: (Dialog ...

  10. 如何学习React--[转]

    如果你是一个 React (或者前端) 新手, 出于以下的原因, 你可能会对这个生态圈感到困惑: React 的目标群体历来是喜欢尝试新事物的开发者和前端专家. Facebook 只开源了他们在实际使 ...