Mega Code Archive

 
Categories / Java / File Input Output
 

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);         }     } }