1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.io.output; 18 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.io.OutputStream; 22 import java.io.UnsupportedEncodingException; 23 import java.util.ArrayList; 24 import java.util.List; 25 26 /** 27 * This class implements an output stream in which the data is 28 * written into a byte array. The buffer automatically grows as data 29 * is written to it. 30 * <p> 31 * The data can be retrieved using <code>toByteArray()</code> and 32 * <code>toString()</code>. 33 * <p> 34 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in 35 * this class can be called after the stream has been closed without 36 * generating an <tt>IOException</tt>. 37 * <p> 38 * This is an alternative implementation of the java.io.ByteArrayOutputStream 39 * class. The original implementation only allocates 32 bytes at the beginning. 40 * As this class is designed for heavy duty it starts at 1024 bytes. In contrast 41 * to the original it doesn't reallocate the whole memory block but allocates 42 * additional buffers. This way no buffers need to be garbage collected and 43 * the contents don't have to be copied to the new buffer. This class is 44 * designed to behave exactly like the original. The only exception is the 45 * deprecated toString(int) method that has been ignored. 46 * 47 * @author <a href="mailto:jeremias (at) apache.org">Jeremias Maerki</a> 48 * @author Holger Hoffstatte 49 * @version $Id: ByteArrayOutputStream.java 610010 2008-01-08 14:50:59Z niallp $ 50 */ 51 public class ByteArrayOutputStream extends OutputStream { 52 53 /** A singleton empty byte array. */ 54 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 55 56 /** The list of buffers, which grows and never reduces. */ 57 private List<byte[]> buffers = new ArrayList<byte[]>(); 58 /** The index of the current buffer. */ 59 private int currentBufferIndex; 60 /** The total count of bytes in all the filled buffers. */ 61 private int filledBufferSum; 62 /** The current buffer. */ 63 private byte[] currentBuffer; 64 /** The total count of bytes written. */ 65 private int count; 66 67 /** 68 * Creates a new byte array output stream. The buffer capacity is 69 * initially 1024 bytes, though its size increases if necessary. 70 */ 71 public ByteArrayOutputStream() { 72 this(1024); 73 } 74 75 /** 76 * Creates a new byte array output stream, with a buffer capacity of 77 * the specified size, in bytes. 78 * 79 * @param size the initial size 80 * @throws IllegalArgumentException if size is negative 81 */ 82 public ByteArrayOutputStream(int size) { 83 if (size < 0) { 84 throw new IllegalArgumentException( 85 "Negative initial size: " + size); 86 } 87 needNewBuffer(size); 88 } 89 90 /** 91 * Return the appropriate <code>byte[]</code> buffer 92 * specified by index. 93 * 94 * @param index the index of the buffer required 95 * @return the buffer 96 */ 97 private byte[] getBuffer(int index) { 98 return buffers.get(index); 99 } 100 101 /** 102 * Makes a new buffer available either by allocating 103 * a new one or re-cycling an existing one. 104 * 105 * @param newcount the size of the buffer if one is created 106 */ 107 private void needNewBuffer(int newcount) { 108 if (currentBufferIndex < buffers.size() - 1) { 109 //Recycling old buffer 110 filledBufferSum += currentBuffer.length; 111 112 currentBufferIndex++; 113 currentBuffer = getBuffer(currentBufferIndex); 114 } else { 115 //Creating new buffer 116 int newBufferSize; 117 if (currentBuffer == null) { 118 newBufferSize = newcount; 119 filledBufferSum = 0; 120 } else { 121 newBufferSize = Math.max( 122 currentBuffer.length << 1, 123 newcount - filledBufferSum); 124 filledBufferSum += currentBuffer.length; 125 } 126 127 currentBufferIndex++; 128 currentBuffer = new byte[newBufferSize]; 129 buffers.add(currentBuffer); 130 } 131 } 132 133 /** 134 * Write the bytes to byte array. 135 * @param b the bytes to write 136 * @param off The start offset 137 * @param len The number of bytes to write 138 */ 139 @Override 140 public void write(byte[] b, int off, int len) { 141 if ((off < 0) 142 || (off > b.length) 143 || (len < 0) 144 || ((off + len) > b.length) 145 || ((off + len) < 0)) { 146 throw new IndexOutOfBoundsException(); 147 } else if (len == 0) { 148 return; 149 } 150 synchronized (this) { 151 int newcount = count + len; 152 int remaining = len; 153 int inBufferPos = count - filledBufferSum; 154 while (remaining > 0) { 155 int part = Math.min(remaining, currentBuffer.length - inBufferPos); 156 System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part); 157 remaining -= part; 158 if (remaining > 0) { 159 needNewBuffer(newcount); 160 inBufferPos = 0; 161 } 162 } 163 count = newcount; 164 } 165 } 166 167 /** 168 * Write a byte to byte array. 169 * @param b the byte to write 170 */ 171 @Override 172 public synchronized void write(int b) { 173 int inBufferPos = count - filledBufferSum; 174 if (inBufferPos == currentBuffer.length) { 175 needNewBuffer(count + 1); 176 inBufferPos = 0; 177 } 178 currentBuffer[inBufferPos] = (byte) b; 179 count++; 180 } 181 182 /** 183 * Writes the entire contents of the specified input stream to this 184 * byte stream. Bytes from the input stream are read directly into the 185 * internal buffers of this streams. 186 * 187 * @param in the input stream to read from 188 * @return total number of bytes read from the input stream 189 * (and written to this stream) 190 * @throws IOException if an I/O error occurs while reading the input stream 191 * @since Commons IO 1.4 192 */ 193 public synchronized int write(InputStream in) throws IOException { 194 int readCount = 0; 195 int inBufferPos = count - filledBufferSum; 196 int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 197 while (n != -1) { 198 readCount += n; 199 inBufferPos += n; 200 count += n; 201 if (inBufferPos == currentBuffer.length) { 202 needNewBuffer(currentBuffer.length); 203 inBufferPos = 0; 204 } 205 n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 206 } 207 return readCount; 208 } 209 210 /** 211 * Return the current size of the byte array. 212 * @return the current size of the byte array 213 */ 214 public synchronized int size() { 215 return count; 216 } 217 218 /** 219 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in 220 * this class can be called after the stream has been closed without 221 * generating an <tt>IOException</tt>. 222 * 223 * @throws IOException never (this method should not declare this exception 224 * but it has to now due to backwards compatability) 225 */ 226 @Override 227 public void close() throws IOException { 228 //nop 229 } 230 231 /** 232 * @see java.io.ByteArrayOutputStream#reset() 233 */ 234 public synchronized void reset() { 235 count = 0; 236 filledBufferSum = 0; 237 currentBufferIndex = 0; 238 currentBuffer = getBuffer(currentBufferIndex); 239 } 240 241 /** 242 * Writes the entire contents of this byte stream to the 243 * specified output stream. 244 * 245 * @param out the output stream to write to 246 * @throws IOException if an I/O error occurs, such as if the stream is closed 247 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) 248 */ 249 public synchronized void writeTo(OutputStream out) throws IOException { 250 int remaining = count; 251 for (int i = 0; i < buffers.size(); i++) { 252 byte[] buf = getBuffer(i); 253 int c = Math.min(buf.length, remaining); 254 out.write(buf, 0, c); 255 remaining -= c; 256 if (remaining == 0) { 257 break; 258 } 259 } 260 } 261 262 /** 263 * Gets the curent contents of this byte stream as a byte array. 264 * The result is independent of this stream. 265 * 266 * @return the current contents of this output stream, as a byte array 267 * @see java.io.ByteArrayOutputStream#toByteArray() 268 */ 269 public synchronized byte[] toByteArray() { 270 int remaining = count; 271 if (remaining == 0) { 272 return EMPTY_BYTE_ARRAY; 273 } 274 byte newbuf[] = new byte[remaining]; 275 int pos = 0; 276 for (int i = 0; i < buffers.size(); i++) { 277 byte[] buf = getBuffer(i); 278 int c = Math.min(buf.length, remaining); 279 System.arraycopy(buf, 0, newbuf, pos, c); 280 pos += c; 281 remaining -= c; 282 if (remaining == 0) { 283 break; 284 } 285 } 286 return newbuf; 287 } 288 289 /** 290 * Gets the curent contents of this byte stream as a string. 291 * @return the contents of the byte array as a String 292 * @see java.io.ByteArrayOutputStream#toString() 293 */ 294 @Override 295 public String toString() { 296 return new String(toByteArray()); 297 } 298 299 /** 300 * Gets the curent contents of this byte stream as a string 301 * using the specified encoding. 302 * 303 * @param enc the name of the character encoding 304 * @return the string converted from the byte array 305 * @throws UnsupportedEncodingException if the encoding is not supported 306 * @see java.io.ByteArrayOutputStream#toString(String) 307 */ 308 public String toString(String enc) throws UnsupportedEncodingException { 309 return new String(toByteArray(), enc); 310 } 311 312 } 313