Java tutorial
/* * 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); } } }