Java/File Input Output/ByteArrayOutputStream

Материал из Java эксперт
Версия от 06:03, 1 июня 2010; Admin (обсуждение | вклад) (1 версия)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

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