panda.io.stream.ByteArrayOutputStream Maven / Gradle / Ivy
package panda.io.stream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import panda.io.Streams;
import panda.lang.Collections;
/**
* This class implements an output stream in which the data is written into a byte array. The buffer
* automatically grows as data is written to it.
*
* The data can be retrieved using toByteArray()
and toString()
.
*
* Closing a ByteArrayOutputStream has no effect. The methods in this class can be called
* after the stream has been closed without generating an IOException.
*
* This is an alternative implementation of the {@link java.io.ByteArrayOutputStream} class. The
* original implementation only allocates 32 bytes at the beginning. As this class is designed for
* heavy duty it starts at 1024 bytes. In contrast to the original it doesn't reallocate the whole
* memory block but allocates additional buffers. This way no buffers need to be garbage collected
* and the contents don't have to be copied to the new buffer. This class is designed to behave
* exactly like the original. The only exception is the deprecated toString(int) method that has
* been ignored.
*
*/
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 buffers = new ArrayList();
/** 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;
/** Flag to indicate if the buffers can be reused after reset */
private boolean reuseBuffers = true;
/**
* 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(final int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: " + size);
}
synchronized (this) {
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(final 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(final byte[] b, final int off, final 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) {
final int newcount = count + len;
int remaining = len;
int inBufferPos = count - filledBufferSum;
while (remaining > 0) {
final 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(final 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
*/
public synchronized int write(final InputStream in) throws IOException {
return write(in, -1);
}
/**
* 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
* @param len the maximum number of bytes to read
* @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
*/
public synchronized int write(final InputStream in, final int len) throws IOException {
int readCount = 0;
int inBufferPos = count - filledBufferSum;
int limit = currentBuffer.length - inBufferPos;
if (len >= 0 && limit > len) {
limit = len;
}
int n = in.read(currentBuffer, inBufferPos, limit);
while (n != -1) {
readCount += n;
inBufferPos += n;
count += n;
if (inBufferPos == currentBuffer.length) {
needNewBuffer(currentBuffer.length);
inBufferPos = 0;
}
if (len >= 0 && readCount >= len) {
break;
}
limit = currentBuffer.length - inBufferPos;
if (len >= 0 && limit > len - readCount) {
limit = len - readCount;
}
n = in.read(currentBuffer, inBufferPos, limit);
}
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 ByteArrayOutputStream has no effect. The methods in this class can be
* called after the stream has been closed without generating an IOException.
*
* @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;
if (reuseBuffers) {
currentBuffer = buffers.get(currentBufferIndex);
}
else {
currentBuffer = null;
int size = buffers.get(0).length;
buffers.clear();
needNewBuffer(size);
reuseBuffers = true;
}
}
/**
* 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(final OutputStream out) throws IOException {
int remaining = count;
for (final byte[] buf : buffers) {
final int c = Math.min(buf.length, remaining);
out.write(buf, 0, c);
remaining -= c;
if (remaining == 0) {
break;
}
}
}
/**
* Fetches entire contents of an InputStream
and represent same data as result
* InputStream.
*
* This method is useful where,
*
* - Source InputStream is slow.
* - It has network resources associated, so we cannot keep it open for long time.
* - It has network timeout associated.
*
* It can be used in favor of {@link #toByteArray()}, since it avoids unnecessary allocation and
* copy of byte[].
* This method buffers the input internally, so there is no need to use a
* BufferedInputStream
.
*
* @param input Stream to be fully buffered.
* @return A fully buffered stream.
* @throws IOException if an I/O error occurs
*/
public static InputStream toBufferedInputStream(final InputStream input) throws IOException {
@SuppressWarnings("resource")
final ByteArrayOutputStream output = new ByteArrayOutputStream();
output.write(input);
return output.toInputStream();
}
/**
* Gets the current contents of this byte stream as a Input Stream. The returned stream is
* backed by buffers of this
stream, avoiding memory allocation and copy, thus
* saving space and time.
*
* @return the current contents of this output stream.
* @see java.io.ByteArrayOutputStream#toByteArray()
* @see #reset()
*/
public synchronized InputStream toInputStream() {
return toInputStream(0, -1);
}
/**
* Gets the current contents of this byte stream as a Input Stream. The returned stream is
* backed by buffers of this
stream, avoiding memory allocation and copy, thus
* saving space and time.
*
* @param offset the offset of the byte array
* @return the current contents of this output stream.
*/
public synchronized InputStream toInputStream(int offset) {
return toInputStream(offset, -1);
}
/**
* Gets the current contents of this byte stream as a Input Stream. The returned stream is
* backed by buffers of this
stream, avoiding memory allocation and copy, thus
* saving space and time.
*
* @param offset the offset of the byte array
* @param length max length of the byte array
* @return the current contents of this output stream.
*/
public synchronized InputStream toInputStream(int offset, int length) {
if (offset < 0) {
offset = 0;
}
if (offset >= count) {
return Streams.closedInputStream();
}
if (length < 0 || length > count - offset) {
length = count - offset;
}
if (length == 0) {
return Streams.closedInputStream();
}
final List list = new ArrayList(buffers.size());
for (final byte[] buf : buffers) {
if (offset >= buf.length) {
offset -= buf.length;
continue;
}
final int c = Math.min(buf.length - offset, length);
list.add(new ByteArrayInputStream(buf, offset, c));
offset = 0;
length -= c;
if (length == 0) {
break;
}
}
reuseBuffers = false;
return new SequenceInputStream(Collections.enumeration(list));
}
/**
* Gets the current 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() {
return toByteArray(0, -1);
}
/**
* Gets the current contents of this byte stream as a byte array. The result is independent of
* this stream.
*
* @param offset the offset of the byte array
* @return the current contents of this output stream, as a byte array
*/
public synchronized byte[] toByteArray(int offset) {
return toByteArray(offset, -1);
}
/**
* Gets the current contents of this byte stream as a byte array. The result is independent of
* this stream.
*
* @param offset the offset of the byte array
* @param length max length of the byte array
* @return the current contents of this output stream, as a byte array
*/
public synchronized byte[] toByteArray(int offset, int length) {
if (offset < 0) {
offset = 0;
}
if (offset >= count) {
return EMPTY_BYTE_ARRAY;
}
if (length < 0 || length > count) {
length = count;
}
if (length == 0) {
return EMPTY_BYTE_ARRAY;
}
final byte newbuf[] = new byte[length];
int pos = 0;
for (final byte[] buf : buffers) {
if (offset >= buf.length) {
offset -= buf.length;
continue;
}
final int c = Math.min(buf.length - offset, length);
System.arraycopy(buf, offset, newbuf, pos, c);
offset = 0;
pos += c;
length -= c;
if (length == 0) {
break;
}
}
return newbuf;
}
/**
* Gets the current 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 current 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(final String enc) throws UnsupportedEncodingException {
return new String(toByteArray(), enc);
}
/**
* Gets the current contents of this byte stream as a string using the specified encoding.
*
* @param charset the character encoding
* @return the string converted from the byte array
* @see java.io.ByteArrayOutputStream#toString(String)
*/
public String toString(final Charset charset) {
return new String(toByteArray(), charset);
}
}