首先我们来分析它里面的的实例变量:
buffers: 可以看成是一个buffer仓库,里面放的是已经读取的所有数据
currentBufferIndex: 就是正在使用的buffer的index
count: 用来存放buffers里面的所有的字节数
currentBuffer: 就是当前的使用buffer,这个比较好理解。
filledBufferSum: 这个起初的时候我特别不理解,后来我理解,主要是用了保存所有的满buffer的字节数的总和。
举个例子:
起始的时候第一个buffer的大小为32,它的filledBufferSum为0,count为0,然后我们给当前的buffer放入5个字节的数据,现在count为5, 下一次我们count - filedBufferSum 就是我们下一次要存储的buffer的指针,比如我们要放25个字节,那么现在count就变成30了,filledBufferSum仍然是0,我们再放入3个字节,现在count变成33,比buffer的初始大小大了,就扩容,新建一个buffer,把老的buffer放到buffers里面,然后filledBufferSum就变成32了,把扩容后剩余的1个字节放到新申请的buffer里面,下一次比如我们想再放入10个字节的数据,count是33,filedBufferSum是32,我们存放的指针应该是1,因为0字节我们存放了上次扩容后的剩余的字节数。
1. 首先来看一下构造函数:
- public ByteArrayOutputStream() {
- this(1024);
- }
- public ByteArrayOutputStream(int size) {
- if (size < 0) {
- throw new IllegalArgumentException(
- "Negative initial size: " + size);
- }
- needNewBuffer(size);
- }
一个是无参数的时候创建一个大小为1024的buffer,一个是根据用户输入的大小创建buffer,这个都比较好理解,关键是needNewBuffer函数,这个放到下面进行讲解。
2. 下面来看一下needNewBuffer函数,这个是这个类的灵魂,我感觉
- <strong>private void needNewBuffer(int newcount) {
- if (currentBufferIndex < buffers.size() - 1) {
- //Recycling old buffer
- filledBufferSum += currentBuffer.length;
- currentBufferIndex++;
- currentBuffer = getBuffer(currentBufferIndex);
- } else {
- //Creating new buffer
- int newBufferSize;
- if (currentBuffer == null) {
- newBufferSize = newcount;
- filledBufferSum = 0;
- } else {
- newBufferSize = Math.max(
- currentBuffer.length << 1,
- newcount - filledBufferSum);
- filledBufferSum += currentBuffer.length;
- }
- currentBufferIndex++;
- currentBuffer = new byte[newBufferSize];
- buffers.add(currentBuffer);
- }
- }</strong>
首先我们为了好理解期间,我们先来讲else分支,if分支我们放到reset函数里讲解。
这个就是我们传统意义上的创建新的buffer。这个里面如果currentBuffer是null的话,
就初始化一个,这个分支是在创建的时候会走这个分支,下一个分支是将当前的buffer
的length乘以2和所需要的大小进行比较取最大值来当成新的buffer的大小,
然后把filledBufferSum的值进行更改。下面几行代码是来真正创建buffer的地方,
并把他加到buffers里面去。
3. 下面我们来看一下write函数
public void write(byte[] b, int off, int len) {
- if ((off < 0)
- || (off > b.length)
- || (len < 0)
- || ((off + len) > b.length)
- || ((off + len) < 0)) {
- throw new IndexOutOfBoundsException();
- } else if (len == 0) {
- return;
- }
- synchronized (this) {
- int newcount = count + len;
- int remaining = len;
- int inBufferPos = count - filledBufferSum;
- while (remaining > 0) {
- int part = Math.min(remaining, currentBuffer.length - inBufferPos);
- System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
- remaining -= part;
- if (remaining > 0) {
- needNewBuffer(newcount);
- inBufferPos = 0;
- }
- }
- count = newcount;
- }
- }
首先是一些检验,放在数组越界,这些判断是和父类里面的判断是相同的。
下面是才真正的实现写操作。 首先来计算新的count,并将要写的字节数当成初始的remaining,并来计算这次要写的指针位置, 就是上次的总大小减去已经存放满的buffer里面的字节数。
在这里将remaining和当前所剩余的空间做了一个比较,取最小值。然后做数组拷贝动作。然后判断是不是已经完全写完,如果没写完的话就是分配空间了,然后执行分配空间动作,最后在循环的写入到buffer里面。
4. 下面我们在巩固一下,理解一下write函数
- <strong> /**
- * Write a byte to byte array.
- * @param b the byte to write
- */
- public synchronized void write(int b) {
- int inBufferPos = count - filledBufferSum;
- if (inBufferPos == currentBuffer.length) {
- needNewBuffer(count + 1);
- inBufferPos = 0;
- }
- currentBuffer[inBufferPos] = (byte) b;
- count++;
- }</strong>
只要理解了上面的,这个函数就特别好理解,首先是得到这次应该写入的指针位置,如果发现没有空间的话进行分配新的空间,然后把数据写到buffer里面,并累加写入的数据总数。
5. write函数的研究
- public synchronized int write(InputStream in) throws IOException {
- int readCount = 0;
- int inBufferPos = count - filledBufferSum;
- int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
- while (n != -1) {
- readCount += n;
- inBufferPos += n;
- count += n;
- if (inBufferPos == currentBuffer.length) {
- needNewBuffer(currentBuffer.length);
- inBufferPos = 0;
- }
- n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
- }
- return readCount;
- }
这个函数其实也比较好理解,主要是计算应该写入的指针位置,然后从InputStream里面读到currentBuffer里面,从inBufferPos开始,总共读最大剩余长度个字节,如果读到的不为-1,则变化当前的指针和readCount,然后如果当前的指针和currentBuffer的length相等时,则进行扩充缓冲区,然后进行循环,直到读完。
6. reset函数
- public synchronized void reset() {
- count = 0;
- filledBufferSum = 0;
- currentBufferIndex = 0;
- currentBuffer = getBuffer(currentBufferIndex);
- }
我起始得时候不理解,现在理解了。比如我们首先对第一个文件进行了写操作,然后缓冲区并没有 释放,我们要读下一个文件,那么我们现在肯定不能再去申请空间了,要对上一次的缓冲区进行 复用,这里就先将一些状态变量清0,然后将当前的buffer设置为第0个buffer,这样下一次操作的 时候就可以使用上次分配的buffer了。为了把问题说明白,我们再来看一下
- needNewBuffer的if分支,
- if (currentBufferIndex < buffers.size() - 1) {
- //Recycling old buffer
- filledBufferSum += currentBuffer.length;
- currentBufferIndex++;
- currentBuffer = getBuffer(currentBufferIndex);
这个里面,我们可以看到调用了这个函数后,currentBufferIndex是0,如果buffers还有其他缓冲区的话 就将当前的bufferIndex加1,并返回下一个缓冲区,这样感觉效率比较高,到这里我们估计你应该彻底 理解这个里面的流程了。
7. 下面我们来看一下writeTo函数的实现
- public synchronized void writeTo(OutputStream out) throws IOException {
- int remaining = count;
- for (int i = 0; i < buffers.size(); i++) {
- byte[] buf = getBuffer(i);
- int c = Math.min(buf.length, remaining);
- out.write(buf, 0, c);
- remaining -= c;
- if (remaining == 0) {
- break;
- }
- }
- }
这个函数主要是实现了把buf中的数据直接写到用户指定的OutputStream里面。这个里面和JDK里面的实现的唯一的差别就是这里会循环使得buffers中的数据会全部写到指定的OutputStream里面。
8. 下面我们来发一下toByteArray的实现。
public synchronized byte[] toByteArray() {
- int remaining = count;
- if (remaining == 0) {
- return EMPTY_BYTE_ARRAY;
- }
- byte newbuf[] = new byte[remaining];
- int pos = 0;
- for (int i = 0; i < buffers.size(); i++) {
- byte[] buf = getBuffer(i);
- int c = Math.min(buf.length, remaining);
- System.arraycopy(buf, 0, newbuf, pos, c);
- pos += c;
- remaining -= c;
- if (remaining == 0) {
- break;
- }
- }
- return newbuf;
这个函数很好理解,就是创建一个count大小的byte数组,然后循环buffers,将每一个缓冲区中的数据都copy到将要返回的字节数组里面。
下面完整的程序:
- public class ByteArrayOutputStream extends OutputStream {
- /** A singleton empty byte array. */
- private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
- /** The list of buffers, which grows and never reduces. */
- private final List<byte[]> buffers = new ArrayList<byte[]>();
- /** The index of the current buffer. */
- private int currentBufferIndex;
- /** The total count of bytes in all the filled buffers. */
- private int filledBufferSum;
- /** The current buffer. */
- private byte[] currentBuffer;
- /** The total count of bytes written. */
- private int count;
- /**
- * Creates a new byte array output stream. The buffer capacity is
- * initially 1024 bytes, though its size increases if necessary.
- */
- public ByteArrayOutputStream() {
- this(1024);
- }
- /**
- * Creates a new byte array output stream, with a buffer capacity of
- * the specified size, in bytes.
- *
- * @param size the initial size
- * @throws IllegalArgumentException if size is negative
- */
- public ByteArrayOutputStream(int size) {
- if (size < 0) {
- throw new IllegalArgumentException(
- "Negative initial size: " + size);
- }
- needNewBuffer(size);
- }
- /**
- * Makes a new buffer available either by allocating
- * a new one or re-cycling an existing one.
- *
- * @param newcount the size of the buffer if one is created
- */
- private void needNewBuffer(int newcount) {
- if (currentBufferIndex < buffers.size() - 1) {
- //Recycling old buffer
- filledBufferSum += currentBuffer.length;
- currentBufferIndex++;
- currentBuffer = buffers.get(currentBufferIndex);
- } else {
- //Creating new buffer
- int newBufferSize;
- if (currentBuffer == null) {
- newBufferSize = newcount;
- filledBufferSum = 0;
- } else {
- newBufferSize = Math.max(
- currentBuffer.length << 1,
- newcount - filledBufferSum);
- filledBufferSum += currentBuffer.length;
- }
- currentBufferIndex++;
- currentBuffer = new byte[newBufferSize];
- buffers.add(currentBuffer);
- }
- }
- /**
- * Write the bytes to byte array.
- * @param b the bytes to write
- * @param off The start offset
- * @param len The number of bytes to write
- */
- @Override
- public void write(byte[] b, int off, int len) {
- if ((off < 0)
- || (off > b.length)
- || (len < 0)
- || ((off + len) > b.length)
- || ((off + len) < 0)) {
- throw new IndexOutOfBoundsException();
- } else if (len == 0) {
- return;
- }
- synchronized (this) {
- int newcount = count + len;
- int remaining = len;
- int inBufferPos = count - filledBufferSum;
- while (remaining > 0) {
- int part = Math.min(remaining, currentBuffer.length - inBufferPos);
- System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
- remaining -= part;
- if (remaining > 0) {
- needNewBuffer(newcount);
- inBufferPos = 0;
- }
- }
- count = newcount;
- }
- }
- /**
- * Write a byte to byte array.
- * @param b the byte to write
- */
- @Override
- public synchronized void write(int b) {
- int inBufferPos = count - filledBufferSum;
- if (inBufferPos == currentBuffer.length) {
- needNewBuffer(count + 1);
- inBufferPos = 0;
- }
- currentBuffer[inBufferPos] = (byte) b;
- count++;
- }
- /**
- * Writes the entire contents of the specified input stream to this
- * byte stream. Bytes from the input stream are read directly into the
- * internal buffers of this streams.
- *
- * @param in the input stream to read from
- * @return total number of bytes read from the input stream
- * (and written to this stream)
- * @throws IOException if an I/O error occurs while reading the input stream
- * @since Commons IO 1.4
- */
- public synchronized int write(InputStream in) throws IOException {
- int readCount = 0;
- int inBufferPos = count - filledBufferSum;
- int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
- while (n != -1) {
- readCount += n;
- inBufferPos += n;
- count += n;
- if (inBufferPos == currentBuffer.length) {
- needNewBuffer(currentBuffer.length);
- inBufferPos = 0;
- }
- n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
- }
- return readCount;
- }
- /**
- * Return the current size of the byte array.
- * @return the current size of the byte array
- */
- public synchronized int size() {
- return count;
- }
- /**
- * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
- * this class can be called after the stream has been closed without
- * generating an <tt>IOException</tt>.
- *
- * @throws IOException never (this method should not declare this exception
- * but it has to now due to backwards compatability)
- */
- @Override
- public void close() throws IOException {
- //nop
- }
- /**
- * @see java.io.ByteArrayOutputStream#reset()
- */
- public synchronized void reset() {
- count = 0;
- filledBufferSum = 0;
- currentBufferIndex = 0;
- currentBuffer = buffers.get(currentBufferIndex);
- }
- /**
- * Writes the entire contents of this byte stream to the
- * specified output stream.
- *
- * @param out the output stream to write to
- * @throws IOException if an I/O error occurs, such as if the stream is closed
- * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
- */
- public synchronized void writeTo(OutputStream out) throws IOException {
- int remaining = count;
- for (byte[] buf : buffers) {
- int c = Math.min(buf.length, remaining);
- out.write(buf, 0, c);
- remaining -= c;
- if (remaining == 0) {
- break;
- }
- }
- }
- /**
- * Fetches entire contents of an <code>InputStream</code> and represent
- * same data as result InputStream.
- * <p>
- * This method is useful where,
- * <ul>
- * <li>Source InputStream is slow.</li>
- * <li>It has network resources associated, so we cannot keep it open for
- * long time.</li>
- * <li>It has network timeout associated.</li>
- * </ul>
- * It can be used in favor of {@link #toByteArray()}, since it
- * avoids unnecessary allocation and copy of byte[].<br>
- * This method buffers the input internally, so there is no need to use a
- * <code>BufferedInputStream</code>.
- *
- * @param input Stream to be fully buffered.
- * @return A fully buffered stream.
- * @throws IOException if an I/O error occurs
- */
- public static InputStream toBufferedInputStream(InputStream input)
- throws IOException {
- ByteArrayOutputStream output = new ByteArrayOutputStream();
- output.write(input);
- return output.toBufferedInputStream();
- }
- /**
- * Gets the current contents of this byte stream as a Input Stream. The
- * returned stream is backed by buffers of <code>this</code> stream,
- * avoiding memory allocation and copy, thus saving space and time.<br>
- *
- * @return the current contents of this output stream.
- * @see java.io.ByteArrayOutputStream#toByteArray()
- * @see #reset()
- * @since Commons IO 2.0
- */
- private InputStream toBufferedInputStream() {
- int remaining = count;
- if (remaining == 0) {
- return new ClosedInputStream();
- }
- List<ByteArrayInputStream> list = new ArrayList<ByteArrayInputStream>(buffers.size());
- for (byte[] buf : buffers) {
- int c = Math.min(buf.length, remaining);
- list.add(new ByteArrayInputStream(buf, 0, c));
- remaining -= c;
- if (remaining == 0) {
- break;
- }
- }
- return new SequenceInputStream(Collections.enumeration(list));
- }
- /**
- * Gets the curent contents of this byte stream as a byte array.
- * The result is independent of this stream.
- *
- * @return the current contents of this output stream, as a byte array
- * @see java.io.ByteArrayOutputStream#toByteArray()
- */
- public synchronized byte[] toByteArray() {
- int remaining = count;
- if (remaining == 0) {
- return EMPTY_BYTE_ARRAY;
- }
- byte newbuf[] = new byte[remaining];
- int pos = 0;
- for (byte[] buf : buffers) {
- int c = Math.min(buf.length, remaining);
- System.arraycopy(buf, 0, newbuf, pos, c);
- pos += c;
- remaining -= c;
- if (remaining == 0) {
- break;
- }
- }
- return newbuf;
- }
- /**
- * Gets the curent contents of this byte stream as a string.
- * @return the contents of the byte array as a String
- * @see java.io.ByteArrayOutputStream#toString()
- */
- @Override
- public String toString() {
- return new String(toByteArray());
- }
- /**
- * Gets the curent contents of this byte stream as a string
- * using the specified encoding.
- *
- * @param enc the name of the character encoding
- * @return the string converted from the byte array
- * @throws UnsupportedEncodingException if the encoding is not supported
- * @see java.io.ByteArrayOutputStream#toString(String)
- */
- public String toString(String enc) throws UnsupportedEncodingException {
- return new String(toByteArray(), enc);
- }
- }