Java/File Input Output/ByteArrayOutputStream
Версия от 18:01, 31 мая 2010; (обсуждение)
Содержание
An unsynchronized version of java.io.ByteArrayOutputStream
/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
*
* This program and the accompanying materials are made available under
* the terms of the Common Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/cpl-v10.html
*
* $Id: ByteArrayOStream.java,v 1.1.1.1 2004/05/09 16:57:52 vlad_r Exp $
*/
import java.io.IOException;
import java.io.OutputStream;
// ----------------------------------------------------------------------------
/**
* An unsynchronized version of java.io.ByteArrayOutputStream that can expose
* the underlying byte array without a defensive clone and can also be converted
* to a {@link ByteArrayIStream} without intermediate array copies.<p>
*
* All argument validation is disabled in release mode.<p>
*
* NOTE: copy-on-write not supported
*
* @author (C) 2001, Vlad Roubtsov
*/
public
final class ByteArrayOStream extends OutputStream
{
// public: ................................................................
/**
* Callee takes ownership of "buf".
*/
public ByteArrayOStream (final int initialCapacity)
{
m_buf = new byte [initialCapacity];
}
public final void write2 (final int b1, final int b2)
{
final int pos = m_pos;
final int capacity = pos + 2;
byte [] mbuf = m_buf;
final int mbuflen = mbuf.length;
if (mbuflen < capacity)
{
final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
if (pos < NATIVE_COPY_THRESHOLD)
for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
else
System.arraycopy (mbuf, 0, newbuf, 0, pos);
m_buf = mbuf = newbuf;
}
mbuf [pos] = (byte) b1;
mbuf [pos + 1] = (byte) b2;
m_pos = capacity;
}
public final void write3 (final int b1, final int b2, final int b3)
{
final int pos = m_pos;
final int capacity = pos + 3;
byte [] mbuf = m_buf;
final int mbuflen = mbuf.length;
if (mbuflen < capacity)
{
final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
if (pos < NATIVE_COPY_THRESHOLD)
for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
else
System.arraycopy (mbuf, 0, newbuf, 0, pos);
m_buf = mbuf = newbuf;
}
mbuf [pos] = (byte) b1;
mbuf [pos + 1] = (byte) b2;
mbuf [pos + 2] = (byte) b3;
m_pos = capacity;
}
public final void write4 (final int b1, final int b2, final int b3, final int b4)
{
final int pos = m_pos;
final int capacity = pos + 4;
byte [] mbuf = m_buf;
final int mbuflen = mbuf.length;
if (mbuflen < capacity)
{
final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
if (pos < NATIVE_COPY_THRESHOLD)
for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
else
System.arraycopy (mbuf, 0, newbuf, 0, pos);
m_buf = mbuf = newbuf;
}
mbuf [pos] = (byte) b1;
mbuf [pos + 1] = (byte) b2;
mbuf [pos + 2] = (byte) b3;
mbuf [pos + 3] = (byte) b4;
m_pos = capacity;
}
public final void writeTo (final OutputStream out)
throws IOException
{
out.write (m_buf, 0, m_pos);
}
// public final void readFully (final InputStream in)
// throws IOException
// {
// while (true)
// {
// int chunk = in.available ();
//
// System.out.println ("available = " + chunk);
//
// // TODO: this case is handled poorly (on EOF)
// if (chunk == 0) chunk = READ_CHUNK_SIZE;
//
// // read at least "available" bytes: extend the capacity as needed
//
// int free = m_buf.length - m_pos;
//
// final int read;
// if (free > chunk)
// {
// // try reading more than "chunk" anyway:
// read = in.read (m_buf, m_pos, free);
// }
// else
// {
// // extend the capacity to match "chunk":
// {
// System.out.println ("reallocation");
// final byte [] newbuf = new byte [m_pos + chunk];
//
// if (m_pos < NATIVE_COPY_THRESHOLD)
// for (int i = 0; i < m_pos; ++ i) newbuf [i] = m_buf [i];
// else
// System.arraycopy (m_buf, 0, newbuf, 0, m_pos);
//
// m_buf = newbuf;
// }
//
// read = in.read (m_buf, m_pos, chunk);
// }
//
// if (read < 0)
// break;
// else
// m_pos += read;
// }
// }
// public final void addCapacity (final int extraCapacity)
// {
// final int pos = m_pos;
// final int capacity = pos + extraCapacity;
// byte [] mbuf = m_buf;
// final int mbuflen = mbuf.length;
//
// if (mbuflen < capacity)
// {
// final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
//
// if (pos < NATIVE_COPY_THRESHOLD)
// for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
// else
// System.arraycopy (mbuf, 0, newbuf, 0, pos);
//
// m_buf = newbuf;
// }
// }
public final byte [] getByteArray ()
{
return m_buf;
}
/**
*
* @return [result.length = size()]
*/
public final byte [] copyByteArray ()
{
final int pos = m_pos;
final byte [] result = new byte [pos];
final byte [] mbuf = m_buf;
if (pos < NATIVE_COPY_THRESHOLD)
for (int i = 0; i < pos; ++ i) result [i] = mbuf [i];
else
System.arraycopy (mbuf, 0, result, 0, pos);
return result;
}
public final int size ()
{
return m_pos;
}
public final int capacity ()
{
return m_buf.length;
}
/**
* Does not reduce the current capacity.
*/
public final void reset ()
{
m_pos = 0;
}
// OutputStream:
public final void write (final int b)
{
final int pos = m_pos;
final int capacity = pos + 1;
byte [] mbuf = m_buf;
final int mbuflen = mbuf.length;
if (mbuflen < capacity)
{
final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
if (pos < NATIVE_COPY_THRESHOLD)
for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
else
System.arraycopy (mbuf, 0, newbuf, 0, pos);
m_buf = mbuf = newbuf;
}
mbuf [pos] = (byte) b;
m_pos = capacity;
}
public final void write (final byte [] buf, final int offset, final int length)
{
final int pos = m_pos;
final int capacity = pos + length;
byte [] mbuf = m_buf;
final int mbuflen = mbuf.length;
if (mbuflen < capacity)
{
final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
if (pos < NATIVE_COPY_THRESHOLD)
for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
else
System.arraycopy (mbuf, 0, newbuf, 0, pos);
m_buf = mbuf = newbuf;
}
if (length < NATIVE_COPY_THRESHOLD)
for (int i = 0; i < length; ++ i) mbuf [pos + i] = buf [offset + i];
else
System.arraycopy (buf, offset, mbuf, pos, length);
m_pos = capacity;
}
/**
* Equivalent to {@link #reset()}.
*/
public final void close ()
{
reset ();
}
// protected: .............................................................
// package: ...............................................................
// private: ...............................................................
private byte [] m_buf;
private int m_pos;
// private static final int READ_CHUNK_SIZE = 16 * 1024;
private static final int NATIVE_COPY_THRESHOLD = 9;
} // end of class
// ----------------------------------------------------------------------------
A speedy implementation of ByteArrayOutputStream.
/*
* Copyright (c) 2002-2003 by OpenSymphony
* All rights reserved.
*/
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.Writer;
import java.util.Iterator;
import java.util.LinkedList;
/**
* A speedy implementation of ByteArrayOutputStream. It"s not synchronized, and it
* does not copy buffers when it"s expanded. There"s also no copying of the internal buffer
* if it"s contents is extracted with the writeTo(stream) method.
*
* @author Rickard ?berg
* @author Brat Baker (Atlassian)
* @author Alexey
* @version $Date: 2008-01-19 10:09:56 +0800 (Sat, 19 Jan 2008) $ $Id: FastByteArrayOutputStream.java 3000 2008-01-19 02:09:56Z tm_jee $
*/
public class FastByteArrayOutputStream extends OutputStream {
// Static --------------------------------------------------------
private static final int DEFAULT_BLOCK_SIZE = 8192;
private LinkedList buffers;
// Attributes ----------------------------------------------------
// internal buffer
private byte[] buffer;
// is the stream closed?
private boolean closed;
private int blockSize;
private int index;
private int size;
// Constructors --------------------------------------------------
public FastByteArrayOutputStream() {
this(DEFAULT_BLOCK_SIZE);
}
public FastByteArrayOutputStream(int aSize) {
blockSize = aSize;
buffer = new byte[blockSize];
}
public int getSize() {
return size + index;
}
public void close() {
closed = true;
}
public byte[] toByteArray() {
byte[] data = new byte[getSize()];
// Check if we have a list of buffers
int pos = 0;
if (buffers != null) {
Iterator iter = buffers.iterator();
while (iter.hasNext()) {
byte[] bytes = (byte[]) iter.next();
System.arraycopy(bytes, 0, data, pos, blockSize);
pos += blockSize;
}
}
// write the internal buffer directly
System.arraycopy(buffer, 0, data, pos, index);
return data;
}
public String toString() {
return new String(toByteArray());
}
// OutputStream overrides ----------------------------------------
public void write(int datum) throws IOException {
if (closed) {
throw new IOException("Stream closed");
} else {
if (index == blockSize) {
addBuffer();
}
// store the byte
buffer[index++] = (byte) datum;
}
}
public void write(byte[] data, int offset, int length) throws IOException {
if (data == null) {
throw new NullPointerException();
} else if ((offset < 0) || ((offset + length) > data.length) || (length < 0)) {
throw new IndexOutOfBoundsException();
} else if (closed) {
throw new IOException("Stream closed");
} else {
if ((index + length) > blockSize) {
int copyLength;
do {
if (index == blockSize) {
addBuffer();
}
copyLength = blockSize - index;
if (length < copyLength) {
copyLength = length;
}
System.arraycopy(data, offset, buffer, index, copyLength);
offset += copyLength;
index += copyLength;
length -= copyLength;
} while (length > 0);
} else {
// Copy in the subarray
System.arraycopy(data, offset, buffer, index, length);
index += length;
}
}
}
// Public
public void writeTo(OutputStream out) throws IOException {
// Check if we have a list of buffers
if (buffers != null) {
Iterator iter = buffers.iterator();
while (iter.hasNext()) {
byte[] bytes = (byte[]) iter.next();
out.write(bytes, 0, blockSize);
}
}
// write the internal buffer directly
out.write(buffer, 0, index);
}
public void writeTo(RandomAccessFile out) throws IOException {
// Check if we have a list of buffers
if (buffers != null) {
Iterator iter = buffers.iterator();
while (iter.hasNext()) {
byte[] bytes = (byte[]) iter.next();
out.write(bytes, 0, blockSize);
}
}
// write the internal buffer directly
out.write(buffer, 0, index);
}
public void writeTo(Writer out, String encoding) throws IOException {
/*
There is design tradeoff between being fast, correct and using too much memory when decoding bytes to strings.
The rules are thus :
1. if there is only one buffer then its a simple String conversion
REASON : Fast!!!
2. uses full buffer allocation annd System.arrayCopy() to smooosh together the bytes
and then use String conversion
REASON : Fast at the expense of a known amount of memory (eg the used memory * 2)
*/
if (buffers != null)
{
// RULE 2 : a balance between using some memory and speed
writeToViaSmoosh(out, encoding);
}
else
{
// RULE 1 : fastest!
writeToViaString(out, encoding);
}
}
/**
* This can <b>ONLY</b> be called if there is only a single buffer to write, instead
* use {@link #writeTo(java.io.Writer, String)}, which auto detects if
* {@link #writeToViaString(java.io.Writer, String)} is to be used or
* {@link #writeToViaSmoosh(java.io.Writer, String)}.
*
* @param out the JspWriter
* @param encoding the encoding
* @throws IOException
*/
void writeToViaString(Writer out, String encoding) throws IOException
{
byte[] bufferToWrite = buffer; // this is always the last buffer to write
int bufferToWriteLen = index; // index points to our place in the last buffer
writeToImpl(out, encoding, bufferToWrite, bufferToWriteLen);
}
/**
* This is recommended to be used where there"s more than 1 buffer to write, instead
* use {@link #writeTo(java.io.Writer, String)} which auto detects if
* {@link #writeToViaString(java.io.Writer, String)} is to be used or
* {@link #writeToViaSmoosh(java.io.Writer, String)}.
*
* @param out
* @param encoding
* @throws IOException
*/
void writeToViaSmoosh(Writer out, String encoding) throws IOException
{
byte[] bufferToWrite = toByteArray();
int bufferToWriteLen = bufferToWrite.length;
writeToImpl(out, encoding, bufferToWrite, bufferToWriteLen);
}
/**
* Write <code>bufferToWriteLen</code> of bytes from <code>bufferToWrite</code> to
* <code>out</code> encoding it at the same time.
*
* @param out
* @param encoding
* @param bufferToWrite
* @param bufferToWriteLen
* @throws IOException
*/
private void writeToImpl(Writer out, String encoding, byte[] bufferToWrite, int bufferToWriteLen)
throws IOException
{
String writeStr;
if (encoding != null)
{
writeStr = new String(bufferToWrite, 0, bufferToWriteLen, encoding);
}
else
{
writeStr = new String(bufferToWrite, 0, bufferToWriteLen);
}
out.write(writeStr);
}
/**
* Create a new buffer and store the
* current one in linked list
*/
protected void addBuffer() {
if (buffers == null) {
buffers = new LinkedList();
}
buffers.addLast(buffer);
buffer = new byte[blockSize];
size += index;
index = 0;
}
}
////////////////////////////
import java.io.IOException;
import javax.servlet.jsp.JspWriter;
/**
* A test class for {@link webwork.util.FastByteArrayOutputStream}
*
* @author Brad Baker (Atlassian)
* @since $Date$ $Id$
*/
public class FastByteArrayOutputStreamTestCase extends AbstractEncodingTestCase
{
public void testLatinCharsets() throws Exception
{
assertEncoding(ASCII_TEXT, LATIN);
assertEncoding(ASCII_TEXT, ASCII);
}
public void testRussianCharsets() throws Exception
{
assertEncoding(RUSSIAN_DESC_SHORT, KOI8_R);
assertEncoding(RUSSIAN_DESC1, KOI8_R);
assertEncoding(RUSSIAN_DESC_SHORT, WINDOWS_CYRILLIC);
assertEncoding(RUSSIAN_DESC1, WINDOWS_CYRILLIC);
}
public void testUnicodeCharsets() throws Exception
{
String[] testStrs = {ASCII_TEXT_SHORT, ASCII_TEXT, RUSSIAN_DESC_SHORT, RUSSIAN_DESC1, CHINESE_TIMETRACKING, HUNGRIAN_APPLET_PROBLEM, };
String[] encodings = { UTF_8, UTF_16, UBIG_ENDIAN, ULITTLE_ENDIAN, UBIG_ENDIAN_UNMARKED, ULITTLE_ENDIAN_UNMARKED };
assertEncodings(testStrs, encodings);
}
protected void implementEncodingTest(final String srcStr, final String encoding, final int bufferSize)
throws Exception
{
FastByteArrayOutputStream bout = new FastByteArrayOutputStream(bufferSize);
byte[] bytes = srcStr.getBytes(encoding);
bout.write(bytes);
JspWriter writer = new StringCapturingJspWriter();
bout.writeTo(writer, encoding);
String actualStr = writer.toString();
String expectedStr = new String(bytes, encoding);
assertTheyAreEqual(expectedStr, actualStr, encoding);
}
/**
* Before it was changed to use {@link java.nio.charset.CharsetDecoder} is took an this time
* <p/>
* Total Call Time = 1112.0ms
* Average Call Time = 0.001112ms
* <p/>
* Now with the change it takes this time
* <p/>
* <p/>
* The idea is that it did not get significantly worse in performance
*
* @throws IOException
*/
public void testPerformanceOfWriteToJspWriter() throws IOException
{
final String NINEK_STR = makeRoughly(ASCII, 9 * K);
final String FIFTYK_STR = makeRoughly(ASCII, 50 * K);
final String ONEHUNDREDK_STR = makeRoughly(ASCII, 100 * K);
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With < than 8K of data";
}
public String getText(final int times)
{
return ASCII_TEXT;
}
});
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With > than 8K of data";
}
public String getText(final int times)
{
return NINEK_STR;
}
});
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With a 2/3 mix of small data and 1/3 > 8K of data";
}
public String getText(final int times)
{
if (times % 3 == 0)
{
return NINEK_STR;
}
return ASCII_TEXT;
}
});
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With a 1/2 mix of small data and 1/2 > 8K of data";
}
public String getText(final int times)
{
if (times % 2 == 0)
{
return NINEK_STR;
}
return ASCII_TEXT;
}
});
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With 50K of data";
}
public String getText(final int times)
{
return FIFTYK_STR;
}
});
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With 100K of data";
}
public String getText(final int times)
{
return ONEHUNDREDK_STR;
}
});
}
public void testPerformanceOfWriteToJspWriter(TextSource textSource) throws IOException
{
NoopJspWriter noopJspWriter = new NoopJspWriter();
String[] methods = {
"writeTo (using hueristics)",
"writeToViaSmoosh",
};
System.out.println(textSource.getDesc());
System.out.println();
float bestTime = Float.MAX_VALUE;
String bestMethod = methods[0];
for (int methodIndex = 0; methodIndex < methods.length; methodIndex++)
{
String method = methods[methodIndex];
float totalTime = 0;
final int MAX_TIMES = 10;
final int MAX_ITERATIONS = 100;
for (int times = 0; times < MAX_TIMES; times++)
{
String srcText = textSource.getText(times);
for (int i = 0; i < MAX_ITERATIONS; i++)
{
FastByteArrayOutputStream bout = new FastByteArrayOutputStream();
bout.write(srcText.getBytes(UTF_8));
// just time the JspWriter output. And let it warm u first as well
if (times > 3)
{
long then = System.currentTimeMillis();
switch (methodIndex)
{
case 0:
bout.writeTo(noopJspWriter, UTF_8);
break;
case 1:
bout.writeToViaSmoosh(noopJspWriter, UTF_8);
break;
}
long now = System.currentTimeMillis();
totalTime += (now - then);
}
}
}
float avgTime = totalTime / MAX_TIMES / MAX_ITERATIONS;
System.out.println(method + " - Total Call Time = " + totalTime + "ms");
System.out.println(method + " - Average Call Time = " + avgTime + "ms");
System.out.println();
if (avgTime < bestTime) {
bestTime = avgTime;
bestMethod = method;
}
}
System.out.println(bestMethod + " was the best method - Average Call Time = " + bestTime + "ms");
System.out.println("____________________\n");
}
interface TextSource
{
String getDesc();
String getText(int times);
}
static class StringCapturingJspWriter extends NoopJspWriter
{
StringCapturingJspWriter()
{
super(true);
}
}
static class NoopJspWriter extends JspWriter
{
final StringBuffer sb = new StringBuffer();
final boolean capture;
NoopJspWriter()
{
this(false);
}
NoopJspWriter(boolean capture)
{
super(0, false);
this.capture = capture;
}
NoopJspWriter(final int i, final boolean b)
{
super(i, b);
this.capture = false;
}
public String toString()
{
return sb.toString();
}
public void clear() throws IOException
{
}
public void clearBuffer() throws IOException
{
}
public void close() throws IOException
{
}
public void flush() throws IOException
{
}
public int getRemaining()
{
return 0;
}
public void newLine() throws IOException
{
}
public void print(final char c) throws IOException
{
if (capture)
{
sb.append(c);
}
}
public void print(final double v) throws IOException
{
if (capture)
{
sb.append(v);
}
}
public void print(final float v) throws IOException
{
if (capture)
{
sb.append(v);
}
}
public void print(final int i) throws IOException
{
if (capture)
{
sb.append(i);
}
}
public void print(final long l) throws IOException
{
if (capture)
{
sb.append(l);
}
}
public void print(final Object o) throws IOException
{
if (capture)
{
sb.append(o);
}
}
public void print(final String s) throws IOException
{
if (capture)
{
sb.append(s);
}
}
public void print(final boolean b) throws IOException
{
if (capture)
{
sb.append(b);
}
}
public void print(final char[] chars) throws IOException
{
if (capture)
{
sb.append(chars);
}
}
public void println() throws IOException
{
print("\n");
}
public void println(final char c) throws IOException
{
print(c);
println();
}
public void println(final double v) throws IOException
{
print(v);
println();
}
public void println(final float v) throws IOException
{
print(v);
println();
}
public void println(final int i) throws IOException
{
print(i);
println();
}
public void println(final long l) throws IOException
{
print(l);
println();
}
public void println(final Object o) throws IOException
{
print(o);
println();
}
public void println(final String s) throws IOException
{
print(s);
println();
}
public void println(final boolean b) throws IOException
{
print(b);
println();
}
public void println(final char[] chars) throws IOException
{
print(chars);
println();
}
public void write(final char cbuf[], final int off, final int len) throws IOException
{
String s = new String(cbuf, off, len);
print(s);
}
}
}
Provides true Closable semantics ordinarily missing in a java.io.ByteArrayOutputStream
/* Copyright (c) 2001-2009, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
/* $Id: ClosableByteArrayOutputStream.java 2946 2009-03-22 17:44:48Z fredt $ */
/**
* @todo - finer-grained synchronization to reduce average
* potential monitor contention
*/
/**
* Provides true Closable semantics ordinarily missing in a
* {@link java.io.ByteArrayOutputStream}. <p>
*
* Accumulates output in a byte array that automatically grows as needed.<p>
*
* Data is retrieved using <tt>toByteArray()</tt>,
* <tt>toByteArrayInputStream()</tt>, <tt>toString()</tt> and
* <tt>toString(encoding)</tt>. <p>
*
* {@link #close() Closing} a <tt>ClosableByteArrayOutputStream</tt> prevents
* further write operations, but all other operations may succeed until after
* the first invocation of {@link #free() free()}.<p>
*
* Freeing a <tt>ClosableByteArrayOutputStream</tt> closes the stream and
* releases the internal buffer, preventing successful invocation of all
* operations, with the exception of <tt>size()<tt>, <tt>close()</tt>,
* <tt>isClosed()</tt>, <tt>free()</tt> and <tt>isFreed()</tt>. <p>
*
* This class is especially useful when an accumulating output stream must be
* handed off to an extenal client under contract that the stream should
* exhibit true Closable behaviour in response both to internally tracked
* events and to client invocation of the <tt>OutputStream.close()</tt> method.
*
* @author boucherb@users
* @version 1.9.0
* @since 1.9.0
*/
public class ClosableByteArrayOutputStream extends OutputStream {
/**
* Data buffer.
*/
protected byte[] buf;
/**
* # of valid bytes in buffer.
*/
protected int count;
/**
* Whether this stream is closed.
*/
protected boolean closed;
/**
* Whether this stream is freed.
*/
protected boolean freed;
/**
* Creates a new output stream. <p>
*
* The buffer capacity is initially 32 bytes, though its size increases
* if necessary.
*/
public ClosableByteArrayOutputStream() {
this(32);
}
/**
* Creates a new output stream with a buffer capacity of the specified
* <tt>size</tt>, in bytes.
*
* @param size the initial size.
* @exception IllegalArgumentException if size is negative.
*/
public ClosableByteArrayOutputStream(int size)
throws IllegalArgumentException {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size); // NOI18N
}
buf = new byte[size];
}
/**
* Writes the specified single byte.
*
* @param b the single byte to be written.
* @throws java.io.IOException if an I/O error occurs.
* In particular, an <tt>IOException</tt> may be thrown
* if this output stream has been {@link #close() closed}.
*/
public synchronized void write(int b) throws IOException {
checkClosed();
int newcount = count + 1;
if (newcount > buf.length) {
buf = copyOf(buf, Math.max(buf.length << 1, newcount));
}
buf[count] = (byte) b;
count = newcount;
}
/**
* Writes the specified portion of the designated octet sequence. <p>
*
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
* @throws java.io.IOException if an I/O error occurs.
* In particular, an <tt>IOException</tt> may be thrown
* if this output stream has been {@link #close() closed}.
*/
public synchronized void write(byte b[], int off,
int len) throws IOException {
checkClosed();
if ((off < 0) || (off > b.length) || (len < 0)
|| ((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
int newcount = count + len;
if (newcount > buf.length) {
buf = copyOf(buf, Math.max(buf.length << 1, newcount));
}
System.arraycopy(b, off, buf, count, len);
count = newcount;
}
/**
* By default, does nothing. <p>
*
* @throws java.io.IOException if an I/O error occurs.
* In particular, an <tt>IOException</tt> may be thrown
* if this output stream has been {@link #close() closed}.
*/
public void flush() throws IOException {
checkClosed();
}
/**
* Writes the complete contents of this stream"s accumulated data to the
* specified output stream. <p>
*
* The operation occurs as if by calling <tt>out.write(buf, 0, count)</tt>.
*
* @param out the output stream to which to write the data.
* @throws java.io.IOException if an I/O error occurs.
* In particular, an <tt>IOException</tt> may be thrown
* if this output stream has been {@link #free() freed}.
*/
public synchronized void writeTo(OutputStream out) throws IOException {
checkFreed();
out.write(buf, 0, count);
}
/**
* Returns the current capacity of this stream"s data buffer.
*
* @return the length of the internal data array
* @throws java.io.IOException if an I/O error occurs.
* In particular, an <tt>IOException</tt> may be thrown
* if this output stream has been {@link #free() freed}.
*/
public synchronized int capacity() throws IOException {
checkFreed();
return buf.length;
}
/**
* Resets the <tt>count</tt> field of this output stream to zero, so that
* all currently accumulated data is effectively discarded. <p>
*
* Further write operations will reuse the allocated buffer space. <p>
*
* @see #count
* @throws java.io.IOException if an I/O error occurs.
* In particular, an <tt>IOException</tt> may be thrown
* if this output stream has been {@link #close() closed}.
*/
public synchronized void reset() throws IOException {
checkClosed();
count = 0;
}
/**
* Attempts to reduce this stream"s capacity to its current size. <p>
*
* If the data buffer is larger than necessary to hold its current sequence
* of bytes, then it may be resized to become more space efficient.
* Calling this method may, but is not required to, affect the value
* returned by a subsequent call to the {@link #capacity()} method. <p>
*
* @throws java.io.IOException if an I/O error occurs.
* In particular, an <tt>IOException</tt> may be thrown
* if this output stream has been {@link #free() freed}.
*/
public synchronized void trimToSize() throws IOException {
checkFreed();
if (buf.length > count) {
buf = copyOf(buf, count);
}
}
/**
* Retrieves a copy of this stream"s accumated data, as a byte array.
*
* @return a copy of this stream"s accumated data, as a byte array.
* @see #size()
* @throws java.io.IOException if an I/O error occurs.
* In particular, an <tt>IOException</tt> may be thrown
* if this output stream has been {@link #free() freed}.
*/
public synchronized byte[] toByteArray() throws IOException {
checkFreed();
return copyOf(buf, count);
}
/**
* Returns the current size of this stream"s accumated data.
*
* @return the value of the <tt>count</tt> field, which is the number
* of valid bytes in this output stream.
* @see #count
* @throws java.io.IOException never
*/
public synchronized int size() throws IOException {
return count;
}
/**
* Sets the size of this stream"s accumulated data. <p>
*
* @param newSize the new size
* @throws ArrayIndexOutOfBoundsException if new size is negative
*/
public synchronized void setSize(int newSize) {
if (newSize < 0) {
throw new ArrayIndexOutOfBoundsException(newSize);
} else if (newSize > buf.length) {
buf = copyOf(buf, Math.max(buf.length << 1, newSize));
}
count = newSize;
}
/**
* Performs an effecient (zero-copy) conversion of the data accumulated in
* this output stream to an input stream. <p>
*
* To ensure the future integrity of the resulting input stream, {@link
* #free() free} is invoked upon this output stream as a side-effect.
*
* @return an input stream representing this output stream"s accumulated
* data
* @throws java.io.IOException if an I/O error occurs.
* In particular, an <tt>IOException</tt> may be thrown
* if this output stream has been {@link #free() freed}.
*/
public synchronized ByteArrayInputStream toByteArrayInputStream()
throws IOException {
checkFreed();
ByteArrayInputStream inputStream = new ByteArrayInputStream(buf, 0,
count);
free();
return inputStream;
}
/**
* Converts this stream"s accumuated data into a string, translating bytes
* into characters according to the platform"s default character encoding.
*
* @return String translated from this stream"s accumuated data.
* @throws RuntimeException may be thrown if this output stream has been
* {@link #free() freed}.
*/
public synchronized String toString() {
try {
checkFreed();
} catch (IOException ex) {
throw new RuntimeException(ex.toString());
}
return new String(buf, 0, count);
}
/**
* Converts this stream"s accumuated data into a string, translating bytes
* into characters according to the specified character encoding.
*
* @return String translated from the buffer"s contents.
* @param enc a character-encoding name.
* @throws java.io.IOException may be thrown if this output stream has been
* {@link #free() freed}.
* @throws UnsupportedEncodingException If the named encoding is not
* supported.
*/
public synchronized String toString(String enc)
throws IOException, UnsupportedEncodingException {
checkFreed();
return new String(buf, 0, count, enc);
}
/**
* Closes this object for further writing. <p>
*
* Other operations may continue to succeed until after the first invocation
* of {@link #free() free()}. <p>
*
* @throws java.io.IOException if an I/O error occurs (default: never)
*/
public synchronized void close() throws IOException {
closed = true;
}
/**
* Retrieves whether this stream is closed. <p>
* @return <tt>true</tt> if this stream is closed, else <tt>false</tt>
*/
public synchronized boolean isClosed() {
return closed;
}
/**
* Closes this object and releases the underlying buffer for
* garbage collection. <p>
*
* @throws java.io.IOException if an I/O error occurs while closing
* this stream (default: never).
*/
public synchronized void free() throws IOException {
closed = true;
freed = true;
buf = null;
count = 0;
}
/**
* Retrieves whether this stream is freed. <p>
*
* @return <tt>true</tt> if this stream is freed; else <tt>false</tt>.
*/
public synchronized boolean isFreed() {
return freed;
}
/**
* Tests whether this stream is closed. <p>
*
* @throws java.io.IOException if this stream is closed.
*/
protected synchronized void checkClosed() throws IOException {
if (closed) {
throw new IOException("stream is closed."); // NOI18N
}
}
/**
* Tests whether this stream is freed. <p>
*
* @throws java.io.IOException if this stream is freed.
*/
protected synchronized void checkFreed() throws IOException {
if (freed) {
throw new IOException("stream buffer is freed."); // NOI18N
}
}
/**
* Retrieves a copy of <tt>original</tt> with the given
* <tt>newLength</tt>. <p>
*
* @param original the object to copy
* @param newLength the length of the copy
* @return copy of <tt>original</tt> with the given <tt>newLength</tt>
*/
protected byte[] copyOf(byte[] original, int newLength) {
byte[] copy = new byte[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
}
Use ByteArrayOutputStream
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ByteArrayIOApp {
public static void main(String args[]) throws IOException {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
String s = "This is a test.";
for (int i = 0; i < s.length(); ++i)
outStream.write(s.charAt(i));
System.out.println("outstream: " + outStream);
System.out.println("size: " + outStream.size());
ByteArrayInputStream inStream;
inStream = new ByteArrayInputStream(outStream.toByteArray());
int inBytes = inStream.available();
System.out.println("inStream has " + inBytes + " available bytes");
byte inBuf[] = new byte[inBytes];
int bytesRead = inStream.read(inBuf, 0, inBytes);
System.out.println(bytesRead + " bytes were read");
System.out.println("They are: " + new String(inBuf));
}
}