Java 7之传统I/O第3篇 - BufferedInputStream和BufferedOutputStream类

来源:互联网 发布:蓝牙软件免费下载 编辑:程序博客网 时间:2024/05/16 02:01

无论是输入输出的字节流还是字符流,都广泛使用了一种设计模式 - 装饰器模式。其实Java I/O 库中的所有输入流、输出流的类都采用了装饰器模式,它们可以无限次地进行装饰转换,转换的目的就是得到自己想要的数据类型的流对象。


先来看一下带缓冲区输出流的构造函数,如下:

 public BufferedInputStream(InputStream in) {        this(in, defaultBufferSize);    }    public BufferedInputStream(InputStream in, int size) {        super(in);        if (size <= 0) {            throw new IllegalArgumentException("Buffer size <= 0");        }        buf = new byte[size];    }
由于父类的持有的输入流引用是protected的,所以也就通过构造函数输入的输入流得到了引用,同时初始化缓冲区的大小,可以指定,也可以保持默认,默认为8192。

为了节约资源,有时候需要关闭输入流并释放缓冲区,这时候就可以调用close()方法,如下:

private static final        AtomicReferenceFieldUpdater<BufferedInputStream, byte[]>  bufUpdater =        AtomicReferenceFieldUpdater.newUpdater(BufferedInputStream.class,  byte[].class, "buf");

 public void close() throws IOException {        byte[] buffer;        while ( (buffer = buf) != null) {            if (bufUpdater.compareAndSet(this, buffer, null)) {// 会将缓冲区buf的数据清空并释放缓冲区,也就是buf=null                InputStream input = in;                in = null;          // 释放连接的资源                if (input != null)  // 关闭输入流                    input.close();                return;            }            // Else retry in case(防止) a new buf was CASed in fill()        }    }

这里有一个方法非常重要compareAndSet()方法。由于close()方法是异步的,所以就可能在调用close()方法时调用其他的方法,为了阻止这样的情况产生,每次都要对this中的buf和buffer进行比较,如果没有改动时执行缓冲区清空和输入流关闭操作,否则不做任何操作。

来看read()方法,源代码如下:

 public synchronized int read() throws IOException {        if (pos >= count) {            fill();            if (pos >= count)                return -1;        }        return getBufIfOpen()[pos++] & 0xff;    }

来看最重要的read()方法,首先需要看一下fill()方法,源代码如下:

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 {            /* grow buffer */                int nsz = pos * 2;                if (nsz > marklimit)                    nsz = marklimit;                byte nbuf[] = new byte[nsz];                System.arraycopy(buffer, 0, nbuf, 0, pos);                if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {                    // Can't replace buf if there was an async close.                    // Note: This would need to be changed if fill()                    // is ever made accessible to multiple threads.                    // But for now, the only way CAS can fail is via close.                    // assert buf == null;                    throw new IOException("Stream closed");                }                buffer = nbuf;            }        count = pos;        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);        if (n > 0)            count = n + pos;    }

假如有两个线程,一个线程执行read方法(此时刚执行完 System.arraycopy(buffer, 0, nbuf, 0, pos)), 这时候系统调度到另一个线程执行close方法(close方法不是线程同步的)把buffer置为null,然后系统又调度到刚开始那个线程继续执行read方法,这时候执行buffer=nbuf, 好吧,这里就出现内存泄露了(close线程以为它释放了资源,其实不然),当我们加上这段代码:

  if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {    throw new IOException(“Stream closed”);   }
 在执行buffer=nbuf前检查buffer是修改过,就可以保证不会出现上述那种情况。

再来看一下有参数的read()方法,源码如下:

 private int read1(byte[] b, int off, int len) throws IOException {        int avail = count - pos;        if (avail <= 0) {            /* If the requested length is at least as large as the buffer, and               if there is no mark/reset activity, do not bother to copy the               bytes into the local buffer.  In this way buffered streams will               cascade harmlessly. */            if (len >= getBufIfOpen().length && markpos < 0) {                return getInIfOpen().read(b, off, len);            }            fill();            avail = count - pos;            if (avail <= 0) return -1;        }        int cnt = (avail < len) ? avail : len;        System.arraycopy(getBufIfOpen(), pos, b, off, cnt);        pos += cnt;        return cnt;    }    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;        }    }


BufferedOutputStream类是输出流,其中最主要的就是write()方法了,源代码如下:

  // Writes the specified byte to this buffered output stream.    public synchronized void write(int b) throws IOException {        if (count >= buf.length) {            flushBuffer();        }        buf[count++] = (byte)b;    }    /**     * Writes len bytes from the specified byte array     * starting at offset off to this buffered output stream.     */    public synchronized void write(byte b[], int off, int len) throws IOException {        if (len >= buf.length) {            /* If the request length exceeds the size of the output buffer,               flush the output buffer and then write the data directly.               In this way buffered streams will cascade harmlessly. */            flushBuffer();            out.write(b, off, len);            return;        }        if (len > buf.length - count) {            flushBuffer();        }        System.arraycopy(b, off, buf, count, len);        count += len;    }
两个方法非常简单,如果byte缓冲区的字符满了就写入输出流并且刷新缓存,但是应该考虑到,如果在最后一次写入缓冲区时,不论缓冲区是否满了,都要写入并且刷新缓冲区,这时候就要调用flush()方法了,如下:

private void flushBuffer() throws IOException {        if (count > 0) {            out.write(buf, 0, count);            count = 0;        }    }    public synchronized void flush() throws IOException {        flushBuffer();// 写入缓冲区数据并刷        out.flush();  // 关闭输出流    }



















































0 0
原创粉丝点击