裝飾模式和Java IO

裝飾模式

修飾模式(裝飾模式),是面向對象編程領域中,一種動態地往一個類中添加新的行爲的設計模式。就功能而言,修飾模式相比生成子類更爲靈活,這樣能夠給某個對象而不是整個類添加一些功能。java

裝飾模式的UML以下所示:
編程

裝飾模式中有四個角色:設計模式

  • Component 抽象構件,最基本、最核心、最原始的接口或抽象類
  • ConcreteComponent 具體構件的引用
  • Decorator 裝飾角色, 持有對構件的引用
  • ConcreteDecorator 具體裝飾角色

Java IO中的裝飾模式

Java IO流就是裝飾模式的典型應用。數組

與裝飾模式中角色對應的類以下:緩存

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

FilterInputStreamFilterOutputStream作的事情很簡單,只是持有了一個Stream的引用並作了代理:ui

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的代碼(固然只是一部分):this

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()方法是這樣的:atom

/**
     * 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源碼
《設計模式之禪》第二版
修飾模式

相關文章
相關標籤/搜索