A speedy implementation of ByteArrayOutputStream. : ByteArrayOutputStream « File « 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);
        }
    }
}








11.24.ByteArrayOutputStream
11.24.1.ByteArrayOutputStream Demo
11.24.2.A speedy implementation of ByteArrayOutputStream.